From bebcf4908f94510cf5cc958c6f780b4f3dc4dfe4 Mon Sep 17 00:00:00 2001 From: mannipje <135017126+mannipje@users.noreply.github.com> Date: Tue, 3 Dec 2024 11:42:28 +0000 Subject: [PATCH 01/14] feat(web): Add search input to organization standalone theme mobile frontpage (#17095) * Add search input to organization standalone theme frontpage * chore: nx format:write update dirty files --------- Co-authored-by: andes-it Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../standalone/components/Header.tsx | 104 +++++++++++------- .../organization/standalone/standalone.tsx | 1 + 2 files changed, 66 insertions(+), 39 deletions(-) diff --git a/apps/web/layouts/organization/standalone/components/Header.tsx b/apps/web/layouts/organization/standalone/components/Header.tsx index 919361d42d5f..0ec899b9ea58 100644 --- a/apps/web/layouts/organization/standalone/components/Header.tsx +++ b/apps/web/layouts/organization/standalone/components/Header.tsx @@ -2,8 +2,17 @@ import { useEffect, useState } from 'react' import { useWindowSize } from 'react-use' import cn from 'classnames' -import { ResponsiveSpace, Text, TextProps } from '@island.is/island-ui/core' +import { + Box, + Hidden, + ResponsiveSpace, + Stack, + Text, + TextProps, +} from '@island.is/island-ui/core' import { theme } from '@island.is/island-ui/theme' +import { SearchInput } from '@island.is/web/components' +import { useI18n } from '@island.is/web/i18n' import * as styles from './Header.css' @@ -21,6 +30,7 @@ export interface HeaderProps { imageObjectPosition?: 'left' | 'center' | 'right' className?: string isFrontpage?: boolean + organizationSlug?: string } export const Header: React.FC> = ({ @@ -36,8 +46,10 @@ export const Header: React.FC> = ({ imageObjectPosition = 'center', className, isFrontpage = false, + organizationSlug, }) => { const { width } = useWindowSize() + const { activeLocale } = useI18n() const imageProvided = !!image const [isMobile, setIsMobile] = useState(false) @@ -46,50 +58,64 @@ export const Header: React.FC> = ({ setIsMobile(width < theme.breakpoints.lg) }, [width]) return ( -
+
- {underTitle && isFrontpage && ( -
- - {underTitle} - -
+
+ {underTitle && isFrontpage && ( +
+ + {underTitle} + +
+ )} +
+ {imageProvided && isFrontpage && ( + )}
- {imageProvided && isFrontpage && ( - - )}
-
+ {isFrontpage && ( + + + + + + )} + ) } diff --git a/apps/web/layouts/organization/standalone/standalone.tsx b/apps/web/layouts/organization/standalone/standalone.tsx index e170c447de52..0546ad67422a 100644 --- a/apps/web/layouts/organization/standalone/standalone.tsx +++ b/apps/web/layouts/organization/standalone/standalone.tsx @@ -67,6 +67,7 @@ export const StandaloneLayout = ({ mobileBackground: organizationPage?.themeProperties.mobileBackgroundColor, isFrontpage: isFrontpage, underTitle: bannerTitle, + organizationSlug: organizationPage?.organization?.slug ?? '', } const { activeLocale } = useI18n() From 5f20addb1bbf63c56472a116767d5717519d0e0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnea=20R=C3=BAn=20Vignisd=C3=B3ttir?= Date: Tue, 3 Dec 2024 12:00:20 +0000 Subject: [PATCH 02/14] fix(user-notifications): Run worker after bootstrap (#17072) * run worker after bootstrap * chore: nx format:write update dirty files * fix tests * chore: nx format:write update dirty files --------- Co-authored-by: andes-it Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../notificationsWorker.service.spec.ts | 4 +++- .../notificationsWorker/notificationsWorker.service.ts | 10 ++-------- apps/services/user-notification/src/main.ts | 8 ++++++++ 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/apps/services/user-notification/src/app/modules/notifications/notificationsWorker/notificationsWorker.service.spec.ts b/apps/services/user-notification/src/app/modules/notifications/notificationsWorker/notificationsWorker.service.spec.ts index ce392468eb87..df1a95d6b828 100644 --- a/apps/services/user-notification/src/app/modules/notifications/notificationsWorker/notificationsWorker.service.spec.ts +++ b/apps/services/user-notification/src/app/modules/notifications/notificationsWorker/notificationsWorker.service.spec.ts @@ -116,10 +116,12 @@ describe('NotificationsWorkerService', () => { queue = app.get(getQueueServiceToken('notifications')) notificationModel = app.get(getModelToken(Notification)) notificationsService = app.get(NotificationsService) - notificationsWorkerService = app.get(NotificationsWorkerService) userProfileApi = app.get(V2UsersApi) nationalRegistryService = app.get(NationalRegistryV3ClientService) companyRegistryService = app.get(CompanyRegistryClientService) + + notificationsWorkerService = await app.resolve(NotificationsWorkerService) + notificationsWorkerService.run() }) beforeEach(async () => { diff --git a/apps/services/user-notification/src/app/modules/notifications/notificationsWorker/notificationsWorker.service.ts b/apps/services/user-notification/src/app/modules/notifications/notificationsWorker/notificationsWorker.service.ts index 847bf71116af..fd353149f63d 100644 --- a/apps/services/user-notification/src/app/modules/notifications/notificationsWorker/notificationsWorker.service.ts +++ b/apps/services/user-notification/src/app/modules/notifications/notificationsWorker/notificationsWorker.service.ts @@ -55,7 +55,7 @@ type HandleNotification = { } @Injectable() -export class NotificationsWorkerService implements OnApplicationBootstrap { +export class NotificationsWorkerService { constructor( private readonly notificationDispatch: NotificationDispatchService, private readonly messageProcessor: MessageProcessorService, @@ -83,12 +83,6 @@ export class NotificationsWorkerService implements OnApplicationBootstrap { private readonly notificationModel: typeof Notification, ) {} - onApplicationBootstrap() { - if (this.config.isWorker) { - void this.run() - } - } - async handleDocumentNotification({ profile, messageId, @@ -311,7 +305,7 @@ export class NotificationsWorkerService implements OnApplicationBootstrap { } } - async run() { + public async run() { await this.worker.run( async (message, job): Promise => { const messageId = job.id diff --git a/apps/services/user-notification/src/main.ts b/apps/services/user-notification/src/main.ts index 7f2628e37533..a1efe5e63d7d 100644 --- a/apps/services/user-notification/src/main.ts +++ b/apps/services/user-notification/src/main.ts @@ -1,6 +1,7 @@ import { bootstrap, processJob } from '@island.is/infra-nest-server' import { AppModule } from './app/app.module' import { openApi } from './openApi' +import { NotificationsWorkerService } from './app/modules/notifications/notificationsWorker/notificationsWorker.service' const job = processJob() @@ -15,5 +16,12 @@ if (job === 'cleanup') { healthCheck: { database: true, }, + }).then(async ({ app }) => { + if (job === 'worker') { + const notificationsWorkerService = await app.resolve( + NotificationsWorkerService, + ) + await notificationsWorkerService.run() + } }) } From 692e9d86996fe892255619d819ff961286661e47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9Eorkell=20M=C3=A1ni=20=C3=9Eorkelsson?= Date: Tue, 3 Dec 2024 12:13:18 +0000 Subject: [PATCH 03/14] fix(license-service): check app version (#16855) * fix: check app version * fix: add app ver constant * chore: add comment * fix: import type * chore: update version * chore: add MOCKS * chore: nullcheck app * fix: reverse flag * fix: wrong resoler method * fix: add table * fix: wrong resolver method * fix: add logger * Update firearmLicenseMapper.ts * fix: conditional return * Update firearmLicenseMapper.ts * fix: erase mocks --------- Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../src/lib/licenceService.type.ts | 2 ++ .../src/lib/licenseService.service.ts | 8 ++++- .../src/lib/mappers/firearmLicenseMapper.ts | 32 +++++++++---------- .../resolvers/licenseCollection.resolver.ts | 31 ++++++++++++------ .../src/lib/resolvers/userLicense.resolver.ts | 5 +++ .../src/lib/utils/appCompatibilityMode.ts | 15 +++++++++ 6 files changed, 67 insertions(+), 26 deletions(-) create mode 100644 libs/api/domains/license-service/src/lib/utils/appCompatibilityMode.ts diff --git a/libs/api/domains/license-service/src/lib/licenceService.type.ts b/libs/api/domains/license-service/src/lib/licenceService.type.ts index 3423724deb9a..8cb7e33f39f8 100644 --- a/libs/api/domains/license-service/src/lib/licenceService.type.ts +++ b/libs/api/domains/license-service/src/lib/licenceService.type.ts @@ -2,6 +2,7 @@ import { Locale } from '@island.is/shared/types' import { GenericLicenseError } from './dto/GenericLicenseError.dto' import { Payload } from './dto/Payload.dto' import { GenericUserLicense as GenericUserLicenseModel } from './dto/GenericUserLicense.dto' +import { UserAgent } from '@island.is/nest/core' export interface GenericLicenseMappedPayloadResponse { licenseName: string @@ -145,5 +146,6 @@ export interface GenericLicenseMapper { parsePayload: ( payload: Array, locale: Locale, + userAgent?: UserAgent, ) => Promise> } diff --git a/libs/api/domains/license-service/src/lib/licenseService.service.ts b/libs/api/domains/license-service/src/lib/licenseService.service.ts index c6cd333d783e..404777a2cf97 100644 --- a/libs/api/domains/license-service/src/lib/licenseService.service.ts +++ b/libs/api/domains/license-service/src/lib/licenseService.service.ts @@ -47,6 +47,7 @@ import { BarcodeService, TOKEN_EXPIRED_ERROR, } from '@island.is/services/license' +import { UserAgent } from '@island.is/nest/core' const LOG_CATEGORY = 'license-service' @@ -96,6 +97,7 @@ export class LicenseService { user: User, locale: Locale, { includedTypes, excludedTypes, onlyList }: GetGenericLicenseOptions = {}, + userAgent?: UserAgent, ): Promise { const fetchPromises = AVAILABLE_LICENSES.map(async (license) => { if (excludedTypes && excludedTypes.indexOf(license.type) >= 0) { @@ -107,7 +109,7 @@ export class LicenseService { } if (!onlyList) { - return this.getLicensesOfType(user, locale, license.type) + return this.getLicensesOfType(user, locale, license.type, userAgent) } return null @@ -140,6 +142,7 @@ export class LicenseService { user: User, locale: Locale, licenseType: GenericLicenseType, + agent?: UserAgent, ): Promise { const licenseTypeDefinition = AVAILABLE_LICENSES.find( (i) => i.type === licenseType, @@ -187,6 +190,7 @@ export class LicenseService { const licensesPayload = await mapper.parsePayload( licensesFetchResponse.data, locale, + agent, ) const mappedLicenses: Array = await Promise.all( @@ -246,11 +250,13 @@ export class LicenseService { locale: Locale, licenseType: GenericLicenseType, licenseId?: string, + agent?: UserAgent, ): Promise { const licensesOfType = await this.getLicensesOfType( user, locale, licenseType, + agent, ) if (!licensesOfType) { diff --git a/libs/api/domains/license-service/src/lib/mappers/firearmLicenseMapper.ts b/libs/api/domains/license-service/src/lib/mappers/firearmLicenseMapper.ts index 089afc2143b3..dd22f02159f1 100644 --- a/libs/api/domains/license-service/src/lib/mappers/firearmLicenseMapper.ts +++ b/libs/api/domains/license-service/src/lib/mappers/firearmLicenseMapper.ts @@ -15,12 +15,17 @@ import { GenericUserLicenseMetaLinksType, } from '../licenceService.type' import { FirearmLicenseDto } from '@island.is/clients/license-client' -import { Injectable } from '@nestjs/common' +import { Inject, Injectable } from '@nestjs/common' import { isDefined } from '@island.is/shared/utils' import { FormatMessage, IntlService } from '@island.is/cms-translations' import { m } from '../messages' import { expiryTag, formatDate } from '../utils' import { GenericLicenseDataField } from '../dto/GenericLicenseDataField.dto' +import { UserAgent } from '@island.is/nest/core' +import { enableAppCompatibilityMode } from '../utils/appCompatibilityMode' +import { LOGGER_PROVIDER, type Logger } from '@island.is/logging' + +const APP_VERSION_CUTOFF = '1.4.7' @Injectable() export class FirearmLicensePayloadMapper implements GenericLicenseMapper { @@ -28,9 +33,16 @@ export class FirearmLicensePayloadMapper implements GenericLicenseMapper { async parsePayload( payload: Array, locale: Locale = 'is', + userAgent?: UserAgent, ): Promise> { if (!payload) return Promise.resolve([]) + //App version before 1.4.8 doesn't know how to handle table + const enableAppCompatibility = enableAppCompatibilityMode( + userAgent?.app?.version, + APP_VERSION_CUTOFF, + ) + const typedPayload = payload as Array const { formatMessage } = await this.intlService.useIntl( @@ -99,21 +111,9 @@ export class FirearmLicensePayloadMapper implements GenericLicenseMapper { : null, properties ? { - type: GenericLicenseDataFieldType.Group, - hideFromServicePortal: true, - label: formatMessage(m.firearmProperties), - fields: (properties.properties ?? []).map((property) => ({ - type: GenericLicenseDataFieldType.Category, - fields: this.parseProperties( - property, - formatMessage, - )?.filter(isDefined), - })), - } - : null, - properties - ? { - type: GenericLicenseDataFieldType.Table, + type: enableAppCompatibility + ? GenericLicenseDataFieldType.Group + : GenericLicenseDataFieldType.Table, label: formatMessage(m.firearmProperties), fields: (properties.properties ?? []).map((property) => ({ type: GenericLicenseDataFieldType.Category, diff --git a/libs/api/domains/license-service/src/lib/resolvers/licenseCollection.resolver.ts b/libs/api/domains/license-service/src/lib/resolvers/licenseCollection.resolver.ts index 2cc6e34ef319..102d14857c0f 100644 --- a/libs/api/domains/license-service/src/lib/resolvers/licenseCollection.resolver.ts +++ b/libs/api/domains/license-service/src/lib/resolvers/licenseCollection.resolver.ts @@ -9,19 +9,24 @@ import { ApiScope } from '@island.is/auth/scopes' import { Audit } from '@island.is/nest/audit' import type { Locale } from '@island.is/shared/types' -import { UseGuards } from '@nestjs/common' +import { Inject, UseGuards } from '@nestjs/common' import { Args, Query, Resolver } from '@nestjs/graphql' import { GetGenericLicensesInput } from '../dto/GetGenericLicenses.input' import { LicenseService } from '../licenseService.service' import { LicenseCollection } from '../dto/GenericLicenseCollection.dto' import { GenericUserLicense } from '../dto/GenericUserLicense.dto' +import { LOGGER_PROVIDER, type Logger } from '@island.is/logging' +import { ParsedUserAgent, type UserAgent } from '@island.is/nest/core' @UseGuards(IdsUserGuard, ScopesGuard) @Scopes(ApiScope.internal, ApiScope.licenses) @Resolver(() => LicenseCollection) @Audit({ namespace: '@island.is/api/license-service' }) export class LicenseCollectionResolver { - constructor(private readonly licenseServiceService: LicenseService) {} + constructor( + private readonly licenseServiceService: LicenseService, + @Inject(LOGGER_PROVIDER) private readonly logger: Logger, + ) {} @Query(() => [GenericUserLicense], { deprecationReason: 'Use genericLicenseCollection instead', @@ -29,6 +34,7 @@ export class LicenseCollectionResolver { @Audit() async genericLicenses( @CurrentUser() user: User, + @ParsedUserAgent() agent: UserAgent, @Args('locale', { type: () => String, nullable: true }) locale: Locale = 'is', @Args('input', { nullable: true }) input?: GetGenericLicensesInput, @@ -43,6 +49,7 @@ export class LicenseCollectionResolver { force: input?.force, onlyList: input?.onlyList, }, + agent, ) return collection.licenses @@ -54,18 +61,24 @@ export class LicenseCollectionResolver { @Audit() async genericLicenseCollection( @CurrentUser() user: User, + @ParsedUserAgent() agent: UserAgent, @Args('locale', { type: () => String, nullable: true }) locale: Locale = 'is', @Args('input') input: GetGenericLicensesInput, ) { const licenseCollection = - await this.licenseServiceService.getLicenseCollection(user, locale, { - ...input, - includedTypes: input?.includedTypes ?? ['DriversLicense'], - excludedTypes: input?.excludedTypes, - force: input?.force, - onlyList: input?.onlyList, - }) + await this.licenseServiceService.getLicenseCollection( + user, + locale, + { + ...input, + includedTypes: input?.includedTypes ?? ['DriversLicense'], + excludedTypes: input?.excludedTypes, + force: input?.force, + onlyList: input?.onlyList, + }, + agent, + ) return licenseCollection } } diff --git a/libs/api/domains/license-service/src/lib/resolvers/userLicense.resolver.ts b/libs/api/domains/license-service/src/lib/resolvers/userLicense.resolver.ts index ba5f75e6c076..cc42079d6065 100644 --- a/libs/api/domains/license-service/src/lib/resolvers/userLicense.resolver.ts +++ b/libs/api/domains/license-service/src/lib/resolvers/userLicense.resolver.ts @@ -15,6 +15,8 @@ import { GenericUserLicense } from '../dto/GenericUserLicense.dto' import { GetGenericLicenseInput } from '../dto/GetGenericLicense.input' import { LicenseService } from '../licenseService.service' import { GenericLicenseError } from '../dto/GenericLicenseError.dto' +import { logger } from '@island.is/logging' +import { ParsedUserAgent, type UserAgent } from '@island.is/nest/core' @UseGuards(IdsUserGuard, ScopesGuard) @Scopes(ApiScope.internal, ApiScope.licenses) @@ -29,6 +31,8 @@ export class UserLicenseResolver { @Audit() async genericLicense( @CurrentUser() user: User, + @ParsedUserAgent() + agent: UserAgent, @Args('locale', { type: () => String, nullable: true }) locale: Locale = 'is', @Args('input') input: GetGenericLicenseInput, @@ -38,6 +42,7 @@ export class UserLicenseResolver { locale, input.licenseType, input.licenseId, + agent, ) if (license instanceof GenericLicenseError) { diff --git a/libs/api/domains/license-service/src/lib/utils/appCompatibilityMode.ts b/libs/api/domains/license-service/src/lib/utils/appCompatibilityMode.ts new file mode 100644 index 000000000000..ff1cec1c61e6 --- /dev/null +++ b/libs/api/domains/license-service/src/lib/utils/appCompatibilityMode.ts @@ -0,0 +1,15 @@ +export const enableAppCompatibilityMode = ( + version?: string, + versionToCompare?: string, +): boolean => { + if (!version || !versionToCompare) { + return false + } + + const versionComparison = version.localeCompare(versionToCompare, undefined, { + numeric: true, + sensitivity: 'base', + }) + + return versionComparison <= 0 +} From e7c1747f3d43cd78de214cf84811149f9db885c7 Mon Sep 17 00:00:00 2001 From: norda-gunni <161026627+norda-gunni@users.noreply.github.com> Date: Tue, 3 Dec 2024 12:34:03 +0000 Subject: [PATCH 04/14] feat(application-system): Add notifications support to pruning (#17029) * Initial notification test * Make version of notificationconfig that works for application lifecycle * Add notification capability for application pruning * Add testing * Add notificationTemplateId and create notifications prior to pruning * Fix circular dependency * Fix tests * Make body vars optional for PruningNotification * Minor type fix * more testing * readme correction * PR related improvements * Pr comments --------- Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../lifecycle/application-lifecycle.module.ts | 12 +- .../application-lifecycle.service.ts | 100 ++++- ...application-lifecycle-notification.spec.ts | 348 ++++++++++++++++++ .../test/application-lifecycle-unit.spec.ts | 1 + .../lib/application/application.service.ts | 14 +- libs/application/core/src/lib/constants.ts | 15 +- .../template-api-modules/src/lib/index.ts | 3 + libs/application/templates/README.md | 27 ++ .../types/src/lib/ApplicationLifecycle.ts | 19 + .../application/types/src/lib/StateMachine.ts | 10 +- libs/clients/user-notification/src/index.ts | 1 + .../src/lib/eagerApiConfiguration.ts | 26 ++ .../lib/userNotificationEagerClient.module.ts | 9 + 13 files changed, 567 insertions(+), 18 deletions(-) create mode 100644 apps/application-system/api/src/app/modules/application/lifecycle/test/application-lifecycle-notification.spec.ts create mode 100644 libs/clients/user-notification/src/lib/eagerApiConfiguration.ts create mode 100644 libs/clients/user-notification/src/lib/userNotificationEagerClient.module.ts diff --git a/apps/application-system/api/src/app/modules/application/lifecycle/application-lifecycle.module.ts b/apps/application-system/api/src/app/modules/application/lifecycle/application-lifecycle.module.ts index e65e7a39aaaf..b6ead098509f 100644 --- a/apps/application-system/api/src/app/modules/application/lifecycle/application-lifecycle.module.ts +++ b/apps/application-system/api/src/app/modules/application/lifecycle/application-lifecycle.module.ts @@ -16,6 +16,10 @@ import { environment } from '../../../../environments' import { SequelizeConfigService } from '../../../sequelizeConfig.service' import { ApplicationChargeModule } from '../charge/application-charge.module' import { ApplicationLifeCycleService } from './application-lifecycle.service' +import { + UserNotificationClientConfig, + UserNotificationEagerClientModule, +} from '@island.is/clients/user-notification' @Module({ imports: [ @@ -26,10 +30,16 @@ import { ApplicationLifeCycleService } from './application-lifecycle.service' LoggingModule, ApplicationChargeModule, ApplicationFilesModule, + UserNotificationEagerClientModule, AuditModule.forRoot(environment.audit), ConfigModule.forRoot({ isGlobal: true, - load: [signingModuleConfig, ApplicationFilesConfig, FileStorageConfig], + load: [ + UserNotificationClientConfig, + signingModuleConfig, + ApplicationFilesConfig, + FileStorageConfig, + ], }), ], providers: [ApplicationLifeCycleService], diff --git a/apps/application-system/api/src/app/modules/application/lifecycle/application-lifecycle.service.ts b/apps/application-system/api/src/app/modules/application/lifecycle/application-lifecycle.service.ts index b0276442b07d..332c163b2555 100644 --- a/apps/application-system/api/src/app/modules/application/lifecycle/application-lifecycle.service.ts +++ b/apps/application-system/api/src/app/modules/application/lifecycle/application-lifecycle.service.ts @@ -7,19 +7,26 @@ import { LOGGER_PROVIDER } from '@island.is/logging' import type { Logger } from '@island.is/logging' import { ApplicationChargeService } from '../charge/application-charge.service' import { FileService } from '@island.is/application/api/files' +import { + CreateHnippNotificationDto, + NotificationsApi, +} from '@island.is/clients/user-notification' +import { getApplicationTemplateByTypeId } from '@island.is/application/template-loader' +import { + ApplicationWithAttachments, + PruningApplication, +} from '@island.is/application/types' export interface ApplicationPruning { pruned: boolean - application: Pick< - Application, - 'id' | 'attachments' | 'answers' | 'externalData' | 'typeId' | 'state' - > + application: PruningApplication failedAttachments: object } @Injectable() export class ApplicationLifeCycleService { private processingApplications: ApplicationPruning[] = [] + private pruneNotifications = new Map() constructor( @Inject(LOGGER_PROVIDER) @@ -27,6 +34,7 @@ export class ApplicationLifeCycleService { private applicationService: ApplicationService, private fileService: FileService, private applicationChargeService: ApplicationChargeService, + private readonly notificationApi: NotificationsApi, ) { this.logger = logger.child({ context: 'ApplicationLifeCycleService' }) } @@ -38,7 +46,7 @@ export class ApplicationLifeCycleService { await this.pruneAttachments() await this.pruneApplicationCharge() await this.pruneApplicationData() - this.reportResults() + await this.reportResults() this.logger.info(`Application pruning done.`) } @@ -48,10 +56,7 @@ export class ApplicationLifeCycleService { private async fetchApplicationsToBePruned() { const applications = - (await this.applicationService.findAllDueToBePruned()) as Pick< - Application, - 'id' | 'attachments' | 'answers' | 'externalData' | 'typeId' | 'state' - >[] + (await this.applicationService.findAllDueToBePruned()) as PruningApplication[] this.logger.info(`Found ${applications.length} applications to be pruned.`) @@ -62,6 +67,13 @@ export class ApplicationLifeCycleService { failedAttachments: {}, } }) + + for (const { application } of this.processingApplications) { + const notification = await this.preparePrunedNotification(application) + if (notification) { + this.pruneNotifications.set(application.id, notification) + } + } } private async pruneAttachments() { @@ -106,7 +118,7 @@ export class ApplicationLifeCycleService { }, ) - prune.application = updatedApplication + prune.application = updatedApplication as PruningApplication } catch (error) { prune.pruned = false this.logger.error( @@ -117,7 +129,7 @@ export class ApplicationLifeCycleService { } } - private reportResults() { + private async reportResults() { const failed = this.processingApplications.filter( (application) => !application.pruned, ) @@ -126,6 +138,72 @@ export class ApplicationLifeCycleService { (application) => application.pruned, ) + for (const { application } of success) { + const notification = this.pruneNotifications.get(application.id) + if (notification) { + await this.sendPrunedNotification(notification, application.id) + } + } + this.logger.info(`Successful: ${success.length}, Failed: ${failed.length}`) } + + private async preparePrunedNotification( + application: PruningApplication, + ): Promise { + const template = await getApplicationTemplateByTypeId(application.typeId) + const stateConfig = template.stateMachineConfig.states[application.state] + const lifeCycle = stateConfig.meta?.lifecycle + if (lifeCycle && lifeCycle.shouldBePruned && lifeCycle.pruneMessage) { + try { + const pruneMessage = + typeof lifeCycle.pruneMessage === 'function' + ? lifeCycle.pruneMessage(application as ApplicationWithAttachments) + : lifeCycle.pruneMessage + const notification = { + recipient: application.applicant, + templateId: pruneMessage.notificationTemplateId, + args: [ + { + key: 'externalBody', + value: pruneMessage.externalBody || '', + }, + { + key: 'internalBody', + value: pruneMessage.internalBody || '', + }, + ], + } + return notification + } catch (error) { + this.logger.error( + `Failed to prepare pruning notification for application ${application.id}`, + error, + ) + return null + } + } + return null + } + + private async sendPrunedNotification( + notification: CreateHnippNotificationDto, + applicationId: string, + ) { + try { + const response = + await this.notificationApi.notificationsControllerCreateHnippNotification( + { + createHnippNotificationDto: notification, + }, + ) + this.logger.info( + `Prune notification sent with response: ${JSON.stringify(response)}`, + ) + } catch (error) { + this.logger.error( + `Failed to send pruning notification with error: ${error} for application ${applicationId}`, + ) + } + } } diff --git a/apps/application-system/api/src/app/modules/application/lifecycle/test/application-lifecycle-notification.spec.ts b/apps/application-system/api/src/app/modules/application/lifecycle/test/application-lifecycle-notification.spec.ts new file mode 100644 index 000000000000..149ceaf0a3db --- /dev/null +++ b/apps/application-system/api/src/app/modules/application/lifecycle/test/application-lifecycle-notification.spec.ts @@ -0,0 +1,348 @@ +import { LOGGER_PROVIDER } from '@island.is/logging' +import { FileService } from '@island.is/application/api/files' +import { + CreateNotificationResponse, + NotificationsApi, +} from '@island.is/clients/user-notification' +import { Test } from '@nestjs/testing' +import { getApplicationTemplateByTypeId } from '@island.is/application/template-loader' +import { TestApp } from '@island.is/testing/nest' +import { + ApplicationTypes, + PruningApplication, +} from '@island.is/application/types' + +import { ApplicationLifeCycleService } from '../application-lifecycle.service' +import { ApplicationService } from '@island.is/application/api/core' +import { ApplicationChargeService } from '../../charge/application-charge.service' + +jest.mock('@island.is/application/template-loader') + +describe('ApplicationLifeCycleService', () => { + let service: ApplicationLifeCycleService + + const mockLogger = { + info: jest.fn(), + error: jest.fn(), + child: () => mockLogger, + } + + beforeEach(async () => { + const module = await Test.createTestingModule({ + providers: [ + ApplicationLifeCycleService, + { + provide: LOGGER_PROVIDER, + useValue: mockLogger, + }, + { + provide: ApplicationService, + useValue: {}, + }, + { + provide: FileService, + useValue: {}, + }, + { + provide: ApplicationChargeService, + useValue: {}, + }, + { + provide: NotificationsApi, + useValue: {}, + }, + ], + }).compile() + + service = module.get( + ApplicationLifeCycleService, + ) + }) + + describe('preparePrunedNotification', () => { + it('should return notification when all required fields are present', async () => { + const mockApplication = { + id: '123', + typeId: ApplicationTypes.EXAMPLE, + state: 'draft', + applicant: 'user123', + answers: {}, + externalData: {}, + attachments: [], + } + + const mockTemplate = { + stateMachineConfig: { + states: { + draft: { + meta: { + lifecycle: { + shouldBePruned: true, + pruneMessage: { + externalBody: 'external message', + internalBody: 'internal message', + notificationTemplateId: 'template123', + }, + }, + }, + }, + }, + }, + } + + ;(getApplicationTemplateByTypeId as jest.Mock).mockResolvedValue( + mockTemplate, + ) + + const result = await service['preparePrunedNotification'](mockApplication) + + expect(result).toEqual({ + recipient: 'user123', + templateId: 'template123', + args: [ + { + key: 'externalBody', + value: 'external message', + }, + { + key: 'internalBody', + value: 'internal message', + }, + ], + }) + }) + + it('should handle function-based pruneMessage', async () => { + const mockApplication = { + id: '123', + typeId: ApplicationTypes.EXAMPLE, + state: 'draft', + applicant: 'user123', + answers: {}, + externalData: {}, + attachments: [], + } + + const mockTemplate = { + stateMachineConfig: { + states: { + draft: { + meta: { + lifecycle: { + shouldBePruned: true, + pruneMessage: (app: PruningApplication) => ({ + externalBody: `external message for ${app.id}`, + internalBody: `internal message for ${app.id}`, + notificationTemplateId: 'template123', + }), + }, + }, + }, + }, + }, + } + + ;(getApplicationTemplateByTypeId as jest.Mock).mockResolvedValue( + mockTemplate, + ) + + const result = await service['preparePrunedNotification'](mockApplication) + + expect(result).toEqual({ + recipient: 'user123', + templateId: 'template123', + args: [ + { + key: 'externalBody', + value: 'external message for 123', + }, + { + key: 'internalBody', + value: 'internal message for 123', + }, + ], + }) + }) + + it('should return null when required fields are missing', async () => { + const mockApplication = { + id: '123', + typeId: ApplicationTypes.EXAMPLE, + state: 'draft', + applicant: 'user123', + answers: {}, + externalData: {}, + attachments: [], + } + + const mockTemplate = { + stateMachineConfig: { + states: { + draft: { + meta: { + lifecycle: { + shouldBePruned: false, // Missing required field + }, + }, + }, + }, + }, + } + + ;(getApplicationTemplateByTypeId as jest.Mock).mockResolvedValue( + mockTemplate, + ) + + const result = await service['preparePrunedNotification'](mockApplication) + + expect(result).toBeNull() + }) + }) +}) + +describe('ApplicationLifeCycleService', () => { + let service: ApplicationLifeCycleService + let notificationApi: jest.Mocked + let mockLogger: jest.Mocked + + beforeEach(async () => { + mockLogger = { + info: jest.fn(), + error: jest.fn(), + child: jest.fn().mockReturnThis(), + } + + notificationApi = { + notificationsControllerCreateHnippNotification: jest.fn(), + } as any + + const module = await Test.createTestingModule({ + providers: [ + ApplicationLifeCycleService, + { + provide: LOGGER_PROVIDER, + useValue: mockLogger, + }, + { + provide: ApplicationService, + useValue: {}, + }, + { + provide: FileService, + useValue: {}, + }, + { + provide: ApplicationChargeService, + useValue: {}, + }, + { + provide: NotificationsApi, + useValue: notificationApi, + }, + ], + }).compile() + + service = module.get( + ApplicationLifeCycleService, + ) + }) + + describe('reportResults', () => { + it('should successfully send notification when API call succeeds', async () => { + // Setup + const mockNotification = { + recipient: 'test-user', + templateId: 'test-template', + args: [], + } + const mockApplicationId = 'test-id' + + const mockResponse = { id: '123' } + + notificationApi.notificationsControllerCreateHnippNotification.mockResolvedValue( + mockResponse, + ) + + // Execute + await service['sendPrunedNotification']( + mockNotification, + mockApplicationId, + ) + + // Assert + expect( + notificationApi.notificationsControllerCreateHnippNotification, + ).toHaveBeenCalledWith({ + createHnippNotificationDto: mockNotification, + }) + expect(mockLogger.info).toHaveBeenCalledWith( + `Prune notification sent with response: ${JSON.stringify( + mockResponse, + )}`, + ) + }) + + it('should log error when notification API call fails', async () => { + // Setup + const mockNotification = { + recipient: 'test-user', + templateId: 'test-template', + args: [], + } + const mockApplicationId = 'test-id' + const mockError = new Error('API Error') + + notificationApi.notificationsControllerCreateHnippNotification.mockRejectedValue( + mockError, + ) + + // Execute + await service['sendPrunedNotification']( + mockNotification, + mockApplicationId, + ) + + // Assert + expect(mockLogger.error).toHaveBeenCalledWith( + `Failed to send pruning notification with error: ${mockError} for application ${mockApplicationId}`, + ) + }) + + it('should handle malformed pruneMessage function', async () => { + const mockApplication = { + id: '123', + typeId: ApplicationTypes.EXAMPLE, + state: 'draft', + applicant: 'user123', + answers: {}, + externalData: {}, + attachments: [], + } + + const mockTemplate = { + stateMachineConfig: { + states: { + draft: { + meta: { + lifecycle: { + shouldBePruned: true, + pruneMessage: () => { + throw new Error('Unexpected error') + }, + }, + }, + }, + }, + }, + } + + ;(getApplicationTemplateByTypeId as jest.Mock).mockResolvedValue( + mockTemplate, + ) + + const result = await service['preparePrunedNotification'](mockApplication) + expect(result).toBeNull() + expect(mockLogger.error).toHaveBeenCalled() + }) + }) +}) diff --git a/apps/application-system/api/src/app/modules/application/lifecycle/test/application-lifecycle-unit.spec.ts b/apps/application-system/api/src/app/modules/application/lifecycle/test/application-lifecycle-unit.spec.ts index 865bb5b922d3..2cb39db66abb 100644 --- a/apps/application-system/api/src/app/modules/application/lifecycle/test/application-lifecycle-unit.spec.ts +++ b/apps/application-system/api/src/app/modules/application/lifecycle/test/application-lifecycle-unit.spec.ts @@ -15,6 +15,7 @@ let s3Service: S3Service export const createApplications = () => { return [ createApplication({ + state: 'draft', answers: { question: 'yes', isAnotherQuestion: 'yes', diff --git a/libs/application/api/core/src/lib/application/application.service.ts b/libs/application/api/core/src/lib/application/application.service.ts index 194a1bf50dfe..c7f8d8f404f7 100644 --- a/libs/application/api/core/src/lib/application/application.service.ts +++ b/libs/application/api/core/src/lib/application/application.service.ts @@ -226,11 +226,17 @@ export class ApplicationService { }) } - async findAllDueToBePruned(): Promise< - Pick[] - > { + async findAllDueToBePruned(): Promise { return this.applicationModel.findAll({ - attributes: ['id', 'attachments', 'typeId', 'state'], + attributes: [ + 'id', + 'attachments', + 'typeId', + 'state', + 'applicant', + 'answers', + 'externalData', + ], where: { [Op.and]: { pruneAt: { diff --git a/libs/application/core/src/lib/constants.ts b/libs/application/core/src/lib/constants.ts index 5770c475a8dd..37b98d47f859 100644 --- a/libs/application/core/src/lib/constants.ts +++ b/libs/application/core/src/lib/constants.ts @@ -1,4 +1,8 @@ -import { StateLifeCycle } from '@island.is/application/types' +import { + PruningApplication, + PruningNotification, + StateLifeCycle, +} from '@island.is/application/types' export const EphemeralStateLifeCycle: StateLifeCycle = { shouldBeListed: false, @@ -16,6 +20,15 @@ export const pruneAfterDays = (Days: number): StateLifeCycle => { export const DefaultStateLifeCycle: StateLifeCycle = pruneAfterDays(30) +export const defaultLifecycleWithPruneMessage = ( + message: + | PruningNotification + | ((application: PruningApplication) => PruningNotification), +) => ({ + ...DefaultStateLifeCycle, + pruneMessage: message, +}) + export const NO_ANSWER = null export const YES = 'yes' diff --git a/libs/application/template-api-modules/src/lib/index.ts b/libs/application/template-api-modules/src/lib/index.ts index c8c2758da96b..8a86fd0708aa 100644 --- a/libs/application/template-api-modules/src/lib/index.ts +++ b/libs/application/template-api-modules/src/lib/index.ts @@ -7,3 +7,6 @@ export { TemplateApiModuleActionProps, } from './types' export { AttachmentS3Service } from './modules/shared/services' +export { NotificationsService } from './notification/notifications.service' +export { ApplicationsNotificationsModule } from './notification/notifications.module' +export { NotificationType } from './notification/notificationsTemplates' diff --git a/libs/application/templates/README.md b/libs/application/templates/README.md index 55b51ce87c0f..63b063c9d8ac 100644 --- a/libs/application/templates/README.md +++ b/libs/application/templates/README.md @@ -1,5 +1,32 @@ # Templates +## Pruning notifications + +If desired it is possible to have a custom message sent to an application's applicant when it is pruned. +This is done by adding a `pruneMessage` object of type `PruningNotification` or a function returning a `PruningNotification` to the application's lifecycle configuration. +When executed, the function will be passed an argument that is a `PruningApplication` object which contains the application's data. The `PruningNotification` object has `externalBody`, `internalBody` and `notificationTemplateId`. The former two can be used to fill in the values for the `externalBody` and `internalBody` template variables in the notification that will be sent to the user. The `notificationTemplateId` is the id of the notification template that will be used to send the notification. It is up to the user whether to use the body variables or not. + +For example an application might want to send a notification when an application that was in the draft stage was pruned. Here is an example of how that might be accomplished in the application template: + +```typescript +stateMachineConfig: { + initial: States.DRAFT, + states: { + [States.DRAFT]: { + meta: { + name: application.general.name.defaultMessage, + lifecycle: defaultLifecycleWithPruneMessage((application: PruningApplication) => ({ + externalBody: `Application has been in draft for more than 30 days and has been pruned.`, + internalBody: `Application for ${application.externalData.nationalRegistry.data.fullName} has been in draft for more than 30 days. Please note that if desired the application may be re-submitted.`, + notificationTemplateId: 'HNIPP.AS.*', + })), + status: 'draft', + }, + }, + }, + }, +``` + ## Mocking XROAD endpoints with Mockoon for templates ### Prerequisites diff --git a/libs/application/types/src/lib/ApplicationLifecycle.ts b/libs/application/types/src/lib/ApplicationLifecycle.ts index f4dbe2a270b4..913f921789b3 100644 --- a/libs/application/types/src/lib/ApplicationLifecycle.ts +++ b/libs/application/types/src/lib/ApplicationLifecycle.ts @@ -1,4 +1,23 @@ +import { ApplicationWithAttachments } from './Application' export interface ApplicationLifecycle { isListed: boolean pruneAt: Date | null + pruneMessage?: string +} + +export type PruningApplication = Pick< + ApplicationWithAttachments, + | 'id' + | 'attachments' + | 'answers' + | 'externalData' + | 'typeId' + | 'state' + | 'applicant' +> + +export type PruningNotification = { + externalBody?: string + internalBody?: string + notificationTemplateId: string } diff --git a/libs/application/types/src/lib/StateMachine.ts b/libs/application/types/src/lib/StateMachine.ts index 8dd8c370cc7d..7fd80e643f12 100644 --- a/libs/application/types/src/lib/StateMachine.ts +++ b/libs/application/types/src/lib/StateMachine.ts @@ -8,10 +8,15 @@ import { import { AnyEventObject, MachineOptions, StateMachine } from 'xstate/lib/types' import { FormLoader, FormText, StaticText } from './Form' -import { Application, ActionCardTag } from './Application' +import { + Application, + ActionCardTag, + ApplicationWithAttachments, +} from './Application' import { Condition } from './Condition' import { TestSupport } from '@island.is/island-ui/utils' import { TemplateApi } from './template-api/TemplateApi' +import { PruningApplication, PruningNotification } from './ApplicationLifecycle' export type ApplicationRole = 'applicant' | 'assignee' | string @@ -69,6 +74,9 @@ export type StateLifeCycle = // If set to a number prune date will equal current timestamp + whenToPrune (ms) whenToPrune: number | ((application: Application) => Date) shouldDeleteChargeIfPaymentFulfilled?: boolean | null + pruneMessage?: + | PruningNotification + | ((application: PruningApplication) => PruningNotification) } export type PendingActionDisplayType = 'warning' | 'success' | 'info' | 'error' diff --git a/libs/clients/user-notification/src/index.ts b/libs/clients/user-notification/src/index.ts index 7118fca0037d..2f956d9da824 100644 --- a/libs/clients/user-notification/src/index.ts +++ b/libs/clients/user-notification/src/index.ts @@ -1,4 +1,5 @@ export { UserNotificationClientConfig } from './lib/userNotificationClient.config' export { UserNotificationClientModule } from './lib/userNotificationClient.module' +export { UserNotificationEagerClientModule } from './lib/userNotificationEagerClient.module' export * from '../gen/fetch' diff --git a/libs/clients/user-notification/src/lib/eagerApiConfiguration.ts b/libs/clients/user-notification/src/lib/eagerApiConfiguration.ts new file mode 100644 index 000000000000..64d5f7bd0186 --- /dev/null +++ b/libs/clients/user-notification/src/lib/eagerApiConfiguration.ts @@ -0,0 +1,26 @@ +import { createEnhancedFetch } from '@island.is/clients/middlewares' +import { ConfigType } from '@island.is/nest/config' + +import { + Configuration, + UserNotificationApi, + NotificationsApi, +} from '../../gen/fetch' +import { UserNotificationClientConfig } from './userNotificationClient.config' + +export const eagerExportedApis = [UserNotificationApi, NotificationsApi].map( + (Api) => ({ + provide: Api, + useFactory: (config: ConfigType) => + new Api( + new Configuration({ + fetchApi: createEnhancedFetch({ + name: 'clients-user-notification', + organizationSlug: 'stafraent-island', + }), + basePath: `${config.basePath}`, + }), + ), + inject: [UserNotificationClientConfig.KEY], + }), +) diff --git a/libs/clients/user-notification/src/lib/userNotificationEagerClient.module.ts b/libs/clients/user-notification/src/lib/userNotificationEagerClient.module.ts new file mode 100644 index 000000000000..2ebfdef2334f --- /dev/null +++ b/libs/clients/user-notification/src/lib/userNotificationEagerClient.module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common' + +import { eagerExportedApis } from './eagerApiConfiguration' + +@Module({ + providers: eagerExportedApis, + exports: eagerExportedApis, +}) +export class UserNotificationEagerClientModule {} From 38cb9501ff56806e39ae76fc7c199ec2f03a56df Mon Sep 17 00:00:00 2001 From: norda-gunni <161026627+norda-gunni@users.noreply.github.com> Date: Tue, 3 Dec 2024 12:47:34 +0000 Subject: [PATCH 05/14] fix(application-system): Fix s3 uri errors in heath insureance application (#17108) * Add extra log lines and start fetching files via URI * Clean up logs --------- Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../templates/health-insurance/health-insurance.utils.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libs/application/template-api-modules/src/lib/modules/templates/health-insurance/health-insurance.utils.ts b/libs/application/template-api-modules/src/lib/modules/templates/health-insurance/health-insurance.utils.ts index e0165c68cd87..fcc28104f09d 100644 --- a/libs/application/template-api-modules/src/lib/modules/templates/health-insurance/health-insurance.utils.ts +++ b/libs/application/template-api-modules/src/lib/modules/templates/health-insurance/health-insurance.utils.ts @@ -93,7 +93,8 @@ export const insuranceToXML = async ( } for (let i = 0; i < arrAttachments.length; i++) { const filename = arrAttachments[i] - const fileContent = await s3Service.getFileContent(filename, 'base64') + const fileUri = attachmentNames[i] // attachmentNames actually contains the URIs + const fileContent = await s3Service.getFileContent(fileUri, 'base64') if (!fileContent) throw new Error(`Unable to fetch file content for: ${filename}`) From 9c6a570fe6426fa616862f2d51e76c9e67058cf3 Mon Sep 17 00:00:00 2001 From: HjorturJ <34068269+HjorturJ@users.noreply.github.com> Date: Tue, 3 Dec 2024 13:00:50 +0000 Subject: [PATCH 06/14] fix(application-system-form): Checkbox ordering and look consolidation (#17065) Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../src/lib/CheckboxFormField/CheckboxFormField.tsx | 1 + .../src/lib/RadioFormField/RadioFormField.tsx | 13 +------------ .../ui-shell/src/components/FormMultiField.tsx | 2 +- .../island-ui/core/src/lib/Checkbox/Checkbox.css.ts | 2 +- 4 files changed, 4 insertions(+), 14 deletions(-) diff --git a/libs/application/ui-fields/src/lib/CheckboxFormField/CheckboxFormField.tsx b/libs/application/ui-fields/src/lib/CheckboxFormField/CheckboxFormField.tsx index 3c9ee2fc2b73..57dfb95436ab 100644 --- a/libs/application/ui-fields/src/lib/CheckboxFormField/CheckboxFormField.tsx +++ b/libs/application/ui-fields/src/lib/CheckboxFormField/CheckboxFormField.tsx @@ -79,6 +79,7 @@ export const CheckboxFormField = ({ disabled={disabled} large={large} name={`${id}`} + split={width === 'half' ? '1/2' : '1/1'} onSelect={onSelect} backgroundColor={backgroundColor} defaultValue={ diff --git a/libs/application/ui-fields/src/lib/RadioFormField/RadioFormField.tsx b/libs/application/ui-fields/src/lib/RadioFormField/RadioFormField.tsx index c769b5699326..9f760fdc6927 100644 --- a/libs/application/ui-fields/src/lib/RadioFormField/RadioFormField.tsx +++ b/libs/application/ui-fields/src/lib/RadioFormField/RadioFormField.tsx @@ -48,17 +48,6 @@ export const RadioFormField: FC> = ({ [options, application, locale], ) - console.debug( - `Radio title ${JSON.stringify( - title, - )}, and formatted: ${formatTextWithLocale( - title, - application, - locale as Locale, - formatMessage, - )}`, - ) - return ( {showFieldName && ( @@ -83,7 +72,7 @@ export const RadioFormField: FC> = ({ /> )} - + Date: Tue, 3 Dec 2024 13:33:13 +0000 Subject: [PATCH 07/14] revert(financial-aid): wide revert of financial aid main (#17091) * Revert "fix: revert back to previous version of filelist (#16825)" This reverts commit b90bbfa6e5342e9d3b2dca7e0e2e11e60805d7c2. * Revert "fix(financial-adi): use account number and ledger when sending bank info (#16599)" This reverts commit 45ec2fd51e01e92ab9dcc17cd9308f2da1386808. * Revert "fix(financial-aid): dynamic logo (#16518)" This reverts commit 4678927a15bfdaa03dfc4e5bd93582b2894a717a. * Revert "chore(financial-aid): refactor custom components out (#15549)" This reverts commit 18b87eb67f3aed85e54ca64c20d1f2c87804daa5. * chore: undo changes in veita * chore: nx format:write update dirty files * fix: remove duplicate imports after merge conflict --------- Co-authored-by: andes-it Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../application/core/src/lib/fieldBuilders.ts | 45 - libs/application/core/src/lib/messages.ts | 15 - .../financial-aid/financial-aid.service.ts | 202 +- .../src/assets/ConfirmationImage.tsx | 3736 ----------------- .../src/components/Logo/Logo.tsx | 32 - .../Status/Estimation/Estimation.tsx | 69 - .../Status/Estimation/VeitaEstimation.tsx | 70 - .../src/components/Status/Estimation/utils.ts | 42 - .../src/components/Summary/Files.css.ts | 9 - .../components/Summary/SummaryBlock.css.ts | 11 - .../src/fields/AboutForm/AboutForm.tsx | 37 + .../AboutSpouseForm/AboutSpouseForm.tsx | 38 + .../src/fields/BankInfoForm/BankInfoForm.tsx | 82 + .../Breakdown/Breakdown.tsx | 0 .../ChildrenFilesForm.tsx | 14 +- .../src/fields/ChildrenForm/ChildInput.tsx | 82 + .../src/fields/ChildrenForm/ChildrenForm.tsx | 129 +- .../Confirmation/ApplicantConfirmation.tsx | 39 + .../src/fields/Confirmation/Confirmation.tsx | 116 + .../Confirmation/SpouseConfirmation.tsx | 28 + .../ConfirmationSectionImage.tsx | 18 + .../src/fields/ContactInfo/ContactInfo.tsx | 68 + .../src/fields/CopyUrl/CopyUrl.tsx | 4 +- .../DescriptionText/DescriptionText.css.ts | 0 .../DescriptionText/DescriptionText.tsx | 0 .../DirectTaxPaymentModal.css.ts | 0 .../DirectTaxPaymentModal.tsx | 4 +- .../fields/EmploymentForm/EmploymentForm.tsx | 97 + .../FileUploadContainer.tsx} | 2 +- .../src/fields/{files => Files}/Files.tsx | 2 +- .../HomeCircumstancesForm.tsx | 116 + .../InRelationshipForm/InRelationshipForm.tsx | 67 + .../IncomeFilesForm.tsx | 22 +- .../src/fields/IncomeForm/IncomeForm.tsx | 61 + .../financial-aid/src/fields/Logo/Logo.css.ts | 14 + .../financial-aid/src/fields/Logo/Logo.tsx | 46 + .../FileList}/FileList.css.ts | 2 +- .../FileList}/FileList.tsx | 7 +- .../MissingFiles.tsx | 37 +- .../MissingFilesConfirmation.tsx | 40 +- .../PersonalTaxCreditForm.tsx | 56 + .../PrivacyPolicyAccordion.tsx | 41 + .../fields/ServiceCenter/ServiceCenter.tsx | 55 + .../financial-aid/src/fields/Shared.css.ts | 58 + .../Status/AidAmount/AidAmount.tsx | 20 +- .../src/fields/Status/ApplicantStatus.tsx | 59 +- .../Status/ApprovedAlert/ApprovedAlert.tsx | 2 + .../fields/Status/Estimation/Estimation.tsx | 121 + .../Status/Header/Header.tsx | 2 + .../MissingFilesCard/MissingFilesCard.tsx | 1 + .../Status/MoreActions/MoreActions.tsx | 3 +- .../RejectionMessage/RejectionMessage.tsx | 3 +- .../Status/SpouseAlert/SpouseAlert.tsx | 3 +- .../Status/SpouseApproved/SpouseApproved.tsx | 2 + .../src/fields/Status/SpouseStatus.tsx | 44 +- .../src/fields/Status/Status.css.ts | 6 + .../Status/Timeline/Timeline.css.ts | 0 .../Status/Timeline/Timeline.tsx | 4 +- .../financial-aid/src/fields/Status/index.ts | 10 + .../financial-aid/src/fields/Status/util.ts | 33 - .../src/fields/StudentForm/StudentForm.tsx | 73 + .../Summary/ChildrenInfo.tsx | 8 +- .../Summary/ContactInfo.tsx | 1 + .../Summary/DirectTaxPaymentCell.tsx | 0 .../{components => fields}/Summary/Files.tsx | 2 +- .../Summary/FormInfo.tsx | 0 .../src/fields/Summary/SpouseSummaryForm.tsx | 101 +- .../Summary/SummaryBlock.tsx | 2 +- .../Summary/SummaryComment.tsx | 0 .../src/fields/Summary/SummaryForm.tsx | 123 +- .../Summary/UserInfo.tsx | 1 + .../financial-aid/src/fields/Summary/index.ts | 6 + .../financial-aid/src/fields/Summary/utils.ts | 179 - .../TaxBreakdown/TaxBreakdown.css.ts | 0 .../TaxBreakdown/TaxBreakdown.tsx | 1 + .../TaxBreakdown/TaxBreakdownHeadline.tsx | 0 .../TaxBreakdown/TaxBreakdownItem.tsx | 0 .../TaxReturnFilesForm.tsx | 42 +- .../taxFormContent.tsx} | 2 +- .../UnknownRelationshipForm.tsx | 158 + .../FileUploadController.css.ts | 11 - .../financial-aid/src/fields/index.ts | 44 +- .../src/forms/ApplicantSubmitted.ts | 64 + .../ApplicantSubmittedForm/ApplicantStatus.ts | 15 - .../ApplicantSubmittedForm/MissingFiles.ts | 42 - .../MissingFilesConfirmation.ts | 35 - .../src/forms/ApplicantSubmittedForm/index.ts | 18 - .../financial-aid/src/forms/Application.ts | 251 ++ .../confirmationMultiField.ts | 112 - .../confirmationSection/index.ts | 10 - .../contactInfoMultiField.ts | 22 - .../contactInfoSection/index.ts | 10 - .../financesSection/bankInfoSubSection.ts | 33 - .../financesSection/incomeFileSubSection.ts | 34 - .../financesSection/incomeSubSection.ts | 35 - .../ApplicationForm/financesSection/index.ts | 20 - .../personalTaxCreditSubSection.ts | 37 - .../taxReturnFilesSubSection.ts | 30 - .../src/forms/ApplicationForm/index.ts | 27 - .../childrenFilesSubSection.ts | 34 - .../childrenSubSection.ts | 57 - .../employmentSubSection.ts | 39 - .../homeCircumstancesSubSection.ts | 40 - .../inARelationshipSubSection.ts | 49 - .../personalInterestSection/index.ts | 24 - .../studentSubSection.ts | 38 - .../unknownRelationshipSubSection.ts | 74 - .../ApplicationForm/summarySection/index.ts | 10 - .../summarySection/summaryMultiField.ts | 31 - .../src/forms/MuncipalityNotRegistered.ts | 17 + .../MunicipalityNotRegistered.ts | 61 - .../financial-aid/src/forms/Prerequisites.ts | 115 + .../PrerequisitesForm/externalDataSection.ts | 66 - .../src/forms/PrerequisitesForm/index.ts | 19 - .../PrerequisitesForm/informationSection.ts | 72 - .../src/forms/PrerequisitesSpouse.ts | 88 + .../forms/PrerequisitesSpouseForm/index.ts | 19 - .../informationSection.ts | 84 - .../prerequisitesSection.ts | 39 - .../financial-aid/src/forms/Spouse.ts | 109 + .../src/forms/SpouseForm/index.ts | 29 - .../SpouseForm/spouseConfirmationSection.ts | 88 - .../SpouseForm/spouseContactInfoSection.ts | 32 - .../SpouseForm/spouseIncomeFilesSection.ts | 24 - .../forms/SpouseForm/spouseIncomeSection.ts | 35 - .../forms/SpouseForm/spouseSumarySection.ts | 38 - .../SpouseForm/spouseTaxReturnFilesSection.ts | 34 - .../src/forms/SpouseSubmitted.ts | 64 + .../forms/SpouseSubmittedForm/MissingFiles.ts | 45 - .../MissingFilesConfirmation.ts | 38 - .../forms/SpouseSubmittedForm/SpouseStatus.ts | 15 - .../src/forms/SpouseSubmittedForm/index.ts | 19 - .../src/lib/FinancialAidTemplate.ts | 55 +- .../financial-aid/src/lib/constants.ts | 17 +- .../financial-aid/src/lib/dataSchema.ts | 275 +- .../financial-aid/src/lib/formatters.ts | 135 +- .../src/lib/hooks/useApplication.ts | 6 +- .../src/lib/messages/aboutForm.ts | 2 +- .../src/lib/messages/aboutSpouseForm.ts | 2 +- .../financial-aid/src/lib/messages/error.ts | 5 - .../src/lib/messages/incomeForm.ts | 16 +- .../src/lib/messages/missingFiles.ts | 20 +- .../lib/messages/privacyPolicyAccordion.ts | 14 +- .../src/lib/messages/unknownRelationship.ts | 2 +- .../templates/financial-aid/src/lib/types.ts | 47 +- .../templates/financial-aid/src/lib/utils.ts | 159 +- .../lib/utils/getApplicantsServiceCenter.ts | 16 - .../src/lib/utils/getEmploymentOptions.ts | 25 - .../lib/utils/getHomeCircumstancesOptions.ts | 34 - .../src/lib/utils/getIncomeOptions.ts | 16 - .../lib/utils/getPersonalTaxCreditOptions.ts | 17 - .../src/lib/utils/getStudentOptions.ts | 17 - .../utils/getUnknownRelationshipOptions.tsx | 17 - .../src/{assets => }/svg/akrahreppur.svg | 0 .../src/{assets => }/svg/akranes.svg | 0 .../src/{assets => }/svg/akureyri.svg | 0 .../src/{assets => }/svg/arborg.svg | 0 .../src/{assets => }/svg/arneshreppur.svg | 0 .../src/{assets => }/svg/asahreppur.svg | 0 .../src/{assets => }/svg/blaskogabyggd.svg | 0 .../src/{assets => }/svg/blonduosbaer.svg | 0 .../src/{assets => }/svg/bolungarvik.svg | 0 .../src/{assets => }/svg/borgarbyggd.svg | 0 .../src/{assets => }/svg/dalabyggd.svg | 0 .../src/{assets => }/svg/dalvikurbyggd.svg | 0 .../svg/eyja-og-miklaholtshreppur.svg | 0 .../src/{assets => }/svg/eyjafjardarsveit.svg | 0 .../src/{assets => }/svg/fjallabyggd.svg | 0 .../src/{assets => }/svg/fjardabyggd.svg | 0 .../{assets => }/svg/fljotsdalshreppur.svg | 0 .../src/{assets => }/svg/floahreppur.svg | 0 .../src/{assets => }/svg/gardabaer.svg | 0 .../svg/grimsnes-og-grafningshreppur.svg | 0 .../src/{assets => }/svg/grindavikurbaer.svg | 0 .../{assets => }/svg/grundafjardarbaer.svg | 0 .../{assets => }/svg/grytubakkahreppur.svg | 0 .../src/{assets => }/svg/hafnarfjordur.svg | 0 .../src/{assets => }/svg/helgafellssveit.svg | 0 .../src/{assets => }/svg/horgarsveit.svg | 0 .../src/{assets => }/svg/hornafjordur.svg | 0 .../{assets => }/svg/hrunamannahreppur.svg | 0 .../src/{assets => }/svg/hunathing-vestra.svg | 0 .../src/{assets => }/svg/hunavatnshreppur.svg | 0 .../src/{assets => }/svg/hvalfjardarsveit.svg | 0 .../src/{assets => }/svg/hveragerdisbaer.svg | 0 .../src/{assets => }/svg/isafjardarbaer.svg | 0 .../{assets => }/svg/kaldrananeshreppur.svg | 0 .../src/{assets => }/svg/kjosarhreppur.svg | 0 .../src/{assets => }/svg/kopavogur.svg | 0 .../src/{assets => }/svg/langanesbyggd.svg | 0 .../src/{assets => }/svg/mosfellsbaer.svg | 0 .../src/{assets => }/svg/mulathing.svg | 0 .../src/{assets => }/svg/myrdalshreppur.svg | 0 .../src/{assets => }/svg/nordurthing.svg | 0 .../src/{assets => }/svg/olfus.svg | 0 .../src/{assets => }/svg/rangarthing-ytra.svg | 0 .../{assets => }/svg/rangarthing_eystra.svg | 0 .../src/{assets => }/svg/reykholahreppur.svg | 0 .../src/{assets => }/svg/reykjanesbaer.svg | 0 .../src/{assets => }/svg/sambandid.svg | 0 .../src/{assets => }/svg/seltjarnarnes.svg | 0 .../src/{assets => }/svg/skaftarhreppur.svg | 0 .../src/{assets => }/svg/skagabyggd.svg | 0 .../src/{assets => }/svg/skagafjordur.svg | 0 .../src/{assets => }/svg/skagastrond.svg | 0 .../svg/skeida-og-gnupverjahreppur.svg | 0 .../src/{assets => }/svg/skorradalur.svg | 0 .../{assets => }/svg/skutustadahreppur.svg | 0 .../src/{assets => }/svg/snaefellsbaer.svg | 0 .../src/{assets => }/svg/strandabyggd.svg | 0 .../src/{assets => }/svg/stykkisholmsbaer.svg | 0 .../src/{assets => }/svg/sudavikurhreppur.svg | 0 .../src/{assets => }/svg/sudurnesjabaer.svg | 0 .../src/{assets => }/svg/svalbardshreppur.svg | 0 .../svg/svalbardsstrandarhreppur.svg | 0 .../{assets => }/svg/talknafjardarhreppur.svg | 0 .../src/{assets => }/svg/thingeyjarsveit.svg | 0 .../src/{assets => }/svg/tjorneshreppur.svg | 0 .../{assets => }/svg/vestmannaeyjabaer.svg | 0 .../src/{assets => }/svg/vesturbyggd.svg | 0 .../src/{assets => }/svg/vogar.svg | 0 .../{assets => }/svg/vopnafjardarhreppur.svg | 0 libs/application/types/src/lib/Fields.ts | 30 - libs/application/types/src/lib/Form.ts | 2 +- .../AccordionFormField/AccordionFormField.tsx | 65 - .../BankAccountFormField.tsx | 90 - libs/application/ui-fields/src/lib/index.ts | 2 - .../ui-shell/src/lib/FormShell.tsx | 5 +- libs/application/ui-shell/src/utils.ts | 37 - .../shared/src/lib/formatters.ts | 8 +- .../src/Markdown/markdownOptions.tsx | 1 + 231 files changed, 3023 insertions(+), 7135 deletions(-) delete mode 100644 libs/application/templates/financial-aid/src/assets/ConfirmationImage.tsx delete mode 100644 libs/application/templates/financial-aid/src/components/Logo/Logo.tsx delete mode 100644 libs/application/templates/financial-aid/src/components/Status/Estimation/Estimation.tsx delete mode 100644 libs/application/templates/financial-aid/src/components/Status/Estimation/VeitaEstimation.tsx delete mode 100644 libs/application/templates/financial-aid/src/components/Status/Estimation/utils.ts delete mode 100644 libs/application/templates/financial-aid/src/components/Summary/Files.css.ts delete mode 100644 libs/application/templates/financial-aid/src/components/Summary/SummaryBlock.css.ts create mode 100644 libs/application/templates/financial-aid/src/fields/AboutForm/AboutForm.tsx create mode 100644 libs/application/templates/financial-aid/src/fields/AboutSpouseForm/AboutSpouseForm.tsx create mode 100644 libs/application/templates/financial-aid/src/fields/BankInfoForm/BankInfoForm.tsx rename libs/application/templates/financial-aid/src/{components => fields}/Breakdown/Breakdown.tsx (100%) rename libs/application/templates/financial-aid/src/fields/{childrenFilesForm => ChildrenFilesForm}/ChildrenFilesForm.tsx (58%) create mode 100644 libs/application/templates/financial-aid/src/fields/ChildrenForm/ChildInput.tsx create mode 100644 libs/application/templates/financial-aid/src/fields/Confirmation/ApplicantConfirmation.tsx create mode 100644 libs/application/templates/financial-aid/src/fields/Confirmation/Confirmation.tsx create mode 100644 libs/application/templates/financial-aid/src/fields/Confirmation/SpouseConfirmation.tsx create mode 100644 libs/application/templates/financial-aid/src/fields/ConfirmationSectionImage/ConfirmationSectionImage.tsx create mode 100644 libs/application/templates/financial-aid/src/fields/ContactInfo/ContactInfo.tsx rename libs/application/templates/financial-aid/src/{components => fields}/DescriptionText/DescriptionText.css.ts (100%) rename libs/application/templates/financial-aid/src/{components => fields}/DescriptionText/DescriptionText.tsx (100%) rename libs/application/templates/financial-aid/src/{components => fields}/DirectTaxPaymentsModal/DirectTaxPaymentModal.css.ts (100%) rename libs/application/templates/financial-aid/src/{components => fields}/DirectTaxPaymentsModal/DirectTaxPaymentModal.tsx (96%) create mode 100644 libs/application/templates/financial-aid/src/fields/EmploymentForm/EmploymentForm.tsx rename libs/application/templates/financial-aid/src/fields/{fileUploadController/FileUploadControler.tsx => FileUploadContainer/FileUploadContainer.tsx} (94%) rename libs/application/templates/financial-aid/src/fields/{files => Files}/Files.tsx (95%) create mode 100644 libs/application/templates/financial-aid/src/fields/HomeCircumstancesForm/HomeCircumstancesForm.tsx create mode 100644 libs/application/templates/financial-aid/src/fields/InRelationshipForm/InRelationshipForm.tsx rename libs/application/templates/financial-aid/src/fields/{incomeFilesForm => IncomeFilesForm}/IncomeFilesForm.tsx (55%) create mode 100644 libs/application/templates/financial-aid/src/fields/IncomeForm/IncomeForm.tsx create mode 100644 libs/application/templates/financial-aid/src/fields/Logo/Logo.css.ts create mode 100644 libs/application/templates/financial-aid/src/fields/Logo/Logo.tsx rename libs/application/templates/financial-aid/src/fields/{Summary => MissingFiles/FileList}/FileList.css.ts (97%) rename libs/application/templates/financial-aid/src/fields/{Summary => MissingFiles/FileList}/FileList.tsx (93%) rename libs/application/templates/financial-aid/src/fields/{missingFiles => MissingFiles}/MissingFiles.tsx (85%) rename libs/application/templates/financial-aid/src/fields/{Summary => MissingFiles/MissingFilesConfirmation}/MissingFilesConfirmation.tsx (50%) create mode 100644 libs/application/templates/financial-aid/src/fields/PersonalTaxCreditForm/PersonalTaxCreditForm.tsx create mode 100644 libs/application/templates/financial-aid/src/fields/PrivacyPolicyAccordion/PrivacyPolicyAccordion.tsx create mode 100644 libs/application/templates/financial-aid/src/fields/ServiceCenter/ServiceCenter.tsx create mode 100644 libs/application/templates/financial-aid/src/fields/Shared.css.ts rename libs/application/templates/financial-aid/src/{components => fields}/Status/AidAmount/AidAmount.tsx (72%) rename libs/application/templates/financial-aid/src/{components => fields}/Status/ApprovedAlert/ApprovedAlert.tsx (99%) create mode 100644 libs/application/templates/financial-aid/src/fields/Status/Estimation/Estimation.tsx rename libs/application/templates/financial-aid/src/{components => fields}/Status/Header/Header.tsx (99%) rename libs/application/templates/financial-aid/src/{components => fields}/Status/MissingFilesCard/MissingFilesCard.tsx (99%) rename libs/application/templates/financial-aid/src/{components => fields}/Status/MoreActions/MoreActions.tsx (93%) rename libs/application/templates/financial-aid/src/{components => fields}/Status/RejectionMessage/RejectionMessage.tsx (94%) rename libs/application/templates/financial-aid/src/{components => fields}/Status/SpouseAlert/SpouseAlert.tsx (95%) rename libs/application/templates/financial-aid/src/{components => fields}/Status/SpouseApproved/SpouseApproved.tsx (99%) create mode 100644 libs/application/templates/financial-aid/src/fields/Status/Status.css.ts rename libs/application/templates/financial-aid/src/{components => fields}/Status/Timeline/Timeline.css.ts (100%) rename libs/application/templates/financial-aid/src/{components => fields}/Status/Timeline/Timeline.tsx (96%) create mode 100644 libs/application/templates/financial-aid/src/fields/Status/index.ts delete mode 100644 libs/application/templates/financial-aid/src/fields/Status/util.ts create mode 100644 libs/application/templates/financial-aid/src/fields/StudentForm/StudentForm.tsx rename libs/application/templates/financial-aid/src/{components => fields}/Summary/ChildrenInfo.tsx (96%) rename libs/application/templates/financial-aid/src/{components => fields}/Summary/ContactInfo.tsx (99%) rename libs/application/templates/financial-aid/src/{components => fields}/Summary/DirectTaxPaymentCell.tsx (100%) rename libs/application/templates/financial-aid/src/{components => fields}/Summary/Files.tsx (98%) rename libs/application/templates/financial-aid/src/{components => fields}/Summary/FormInfo.tsx (100%) rename libs/application/templates/financial-aid/src/{components => fields}/Summary/SummaryBlock.tsx (95%) rename libs/application/templates/financial-aid/src/{components => fields}/Summary/SummaryComment.tsx (100%) rename libs/application/templates/financial-aid/src/{components => fields}/Summary/UserInfo.tsx (99%) create mode 100644 libs/application/templates/financial-aid/src/fields/Summary/index.ts delete mode 100644 libs/application/templates/financial-aid/src/fields/Summary/utils.ts rename libs/application/templates/financial-aid/src/{components => fields}/TaxBreakdown/TaxBreakdown.css.ts (100%) rename libs/application/templates/financial-aid/src/{components => fields}/TaxBreakdown/TaxBreakdown.tsx (99%) rename libs/application/templates/financial-aid/src/{components => fields}/TaxBreakdown/TaxBreakdownHeadline.tsx (100%) rename libs/application/templates/financial-aid/src/{components => fields}/TaxBreakdown/TaxBreakdownItem.tsx (100%) rename libs/application/templates/financial-aid/src/fields/{taxReturnFilesForm => TaxReturnFilesForm}/TaxReturnFilesForm.tsx (59%) rename libs/application/templates/financial-aid/src/fields/{taxReturnFilesForm/TaxFormContent.tsx => TaxReturnFilesForm/taxFormContent.tsx} (96%) create mode 100644 libs/application/templates/financial-aid/src/fields/UnknownRelationshipForm/UnknownRelationshipForm.tsx delete mode 100644 libs/application/templates/financial-aid/src/fields/fileUploadController/FileUploadController.css.ts create mode 100644 libs/application/templates/financial-aid/src/forms/ApplicantSubmitted.ts delete mode 100644 libs/application/templates/financial-aid/src/forms/ApplicantSubmittedForm/ApplicantStatus.ts delete mode 100644 libs/application/templates/financial-aid/src/forms/ApplicantSubmittedForm/MissingFiles.ts delete mode 100644 libs/application/templates/financial-aid/src/forms/ApplicantSubmittedForm/MissingFilesConfirmation.ts delete mode 100644 libs/application/templates/financial-aid/src/forms/ApplicantSubmittedForm/index.ts create mode 100644 libs/application/templates/financial-aid/src/forms/Application.ts delete mode 100644 libs/application/templates/financial-aid/src/forms/ApplicationForm/confirmationSection/confirmationMultiField.ts delete mode 100644 libs/application/templates/financial-aid/src/forms/ApplicationForm/confirmationSection/index.ts delete mode 100644 libs/application/templates/financial-aid/src/forms/ApplicationForm/contactInfoSection/contactInfoMultiField.ts delete mode 100644 libs/application/templates/financial-aid/src/forms/ApplicationForm/contactInfoSection/index.ts delete mode 100644 libs/application/templates/financial-aid/src/forms/ApplicationForm/financesSection/bankInfoSubSection.ts delete mode 100644 libs/application/templates/financial-aid/src/forms/ApplicationForm/financesSection/incomeFileSubSection.ts delete mode 100644 libs/application/templates/financial-aid/src/forms/ApplicationForm/financesSection/incomeSubSection.ts delete mode 100644 libs/application/templates/financial-aid/src/forms/ApplicationForm/financesSection/index.ts delete mode 100644 libs/application/templates/financial-aid/src/forms/ApplicationForm/financesSection/personalTaxCreditSubSection.ts delete mode 100644 libs/application/templates/financial-aid/src/forms/ApplicationForm/financesSection/taxReturnFilesSubSection.ts delete mode 100644 libs/application/templates/financial-aid/src/forms/ApplicationForm/index.ts delete mode 100644 libs/application/templates/financial-aid/src/forms/ApplicationForm/personalInterestSection/childrenFilesSubSection.ts delete mode 100644 libs/application/templates/financial-aid/src/forms/ApplicationForm/personalInterestSection/childrenSubSection.ts delete mode 100644 libs/application/templates/financial-aid/src/forms/ApplicationForm/personalInterestSection/employmentSubSection.ts delete mode 100644 libs/application/templates/financial-aid/src/forms/ApplicationForm/personalInterestSection/homeCircumstancesSubSection.ts delete mode 100644 libs/application/templates/financial-aid/src/forms/ApplicationForm/personalInterestSection/inARelationshipSubSection.ts delete mode 100644 libs/application/templates/financial-aid/src/forms/ApplicationForm/personalInterestSection/index.ts delete mode 100644 libs/application/templates/financial-aid/src/forms/ApplicationForm/personalInterestSection/studentSubSection.ts delete mode 100644 libs/application/templates/financial-aid/src/forms/ApplicationForm/personalInterestSection/unknownRelationshipSubSection.ts delete mode 100644 libs/application/templates/financial-aid/src/forms/ApplicationForm/summarySection/index.ts delete mode 100644 libs/application/templates/financial-aid/src/forms/ApplicationForm/summarySection/summaryMultiField.ts create mode 100644 libs/application/templates/financial-aid/src/forms/MuncipalityNotRegistered.ts delete mode 100644 libs/application/templates/financial-aid/src/forms/MunicipalityNotRegisteredForm/MunicipalityNotRegistered.ts create mode 100644 libs/application/templates/financial-aid/src/forms/Prerequisites.ts delete mode 100644 libs/application/templates/financial-aid/src/forms/PrerequisitesForm/externalDataSection.ts delete mode 100644 libs/application/templates/financial-aid/src/forms/PrerequisitesForm/index.ts delete mode 100644 libs/application/templates/financial-aid/src/forms/PrerequisitesForm/informationSection.ts create mode 100644 libs/application/templates/financial-aid/src/forms/PrerequisitesSpouse.ts delete mode 100644 libs/application/templates/financial-aid/src/forms/PrerequisitesSpouseForm/index.ts delete mode 100644 libs/application/templates/financial-aid/src/forms/PrerequisitesSpouseForm/informationSection.ts delete mode 100644 libs/application/templates/financial-aid/src/forms/PrerequisitesSpouseForm/prerequisitesSection.ts create mode 100644 libs/application/templates/financial-aid/src/forms/Spouse.ts delete mode 100644 libs/application/templates/financial-aid/src/forms/SpouseForm/index.ts delete mode 100644 libs/application/templates/financial-aid/src/forms/SpouseForm/spouseConfirmationSection.ts delete mode 100644 libs/application/templates/financial-aid/src/forms/SpouseForm/spouseContactInfoSection.ts delete mode 100644 libs/application/templates/financial-aid/src/forms/SpouseForm/spouseIncomeFilesSection.ts delete mode 100644 libs/application/templates/financial-aid/src/forms/SpouseForm/spouseIncomeSection.ts delete mode 100644 libs/application/templates/financial-aid/src/forms/SpouseForm/spouseSumarySection.ts delete mode 100644 libs/application/templates/financial-aid/src/forms/SpouseForm/spouseTaxReturnFilesSection.ts create mode 100644 libs/application/templates/financial-aid/src/forms/SpouseSubmitted.ts delete mode 100644 libs/application/templates/financial-aid/src/forms/SpouseSubmittedForm/MissingFiles.ts delete mode 100644 libs/application/templates/financial-aid/src/forms/SpouseSubmittedForm/MissingFilesConfirmation.ts delete mode 100644 libs/application/templates/financial-aid/src/forms/SpouseSubmittedForm/SpouseStatus.ts delete mode 100644 libs/application/templates/financial-aid/src/forms/SpouseSubmittedForm/index.ts delete mode 100644 libs/application/templates/financial-aid/src/lib/utils/getApplicantsServiceCenter.ts delete mode 100644 libs/application/templates/financial-aid/src/lib/utils/getEmploymentOptions.ts delete mode 100644 libs/application/templates/financial-aid/src/lib/utils/getHomeCircumstancesOptions.ts delete mode 100644 libs/application/templates/financial-aid/src/lib/utils/getIncomeOptions.ts delete mode 100644 libs/application/templates/financial-aid/src/lib/utils/getPersonalTaxCreditOptions.ts delete mode 100644 libs/application/templates/financial-aid/src/lib/utils/getStudentOptions.ts delete mode 100644 libs/application/templates/financial-aid/src/lib/utils/getUnknownRelationshipOptions.tsx rename libs/application/templates/financial-aid/src/{assets => }/svg/akrahreppur.svg (100%) rename libs/application/templates/financial-aid/src/{assets => }/svg/akranes.svg (100%) rename libs/application/templates/financial-aid/src/{assets => }/svg/akureyri.svg (100%) rename libs/application/templates/financial-aid/src/{assets => }/svg/arborg.svg (100%) rename libs/application/templates/financial-aid/src/{assets => }/svg/arneshreppur.svg (100%) rename libs/application/templates/financial-aid/src/{assets => }/svg/asahreppur.svg (100%) rename libs/application/templates/financial-aid/src/{assets => }/svg/blaskogabyggd.svg (100%) rename libs/application/templates/financial-aid/src/{assets => }/svg/blonduosbaer.svg (100%) rename libs/application/templates/financial-aid/src/{assets => }/svg/bolungarvik.svg (100%) rename libs/application/templates/financial-aid/src/{assets => }/svg/borgarbyggd.svg (100%) rename libs/application/templates/financial-aid/src/{assets => }/svg/dalabyggd.svg (100%) rename libs/application/templates/financial-aid/src/{assets => }/svg/dalvikurbyggd.svg (100%) rename libs/application/templates/financial-aid/src/{assets => }/svg/eyja-og-miklaholtshreppur.svg (100%) rename libs/application/templates/financial-aid/src/{assets => }/svg/eyjafjardarsveit.svg (100%) rename libs/application/templates/financial-aid/src/{assets => }/svg/fjallabyggd.svg (100%) rename libs/application/templates/financial-aid/src/{assets => }/svg/fjardabyggd.svg (100%) rename libs/application/templates/financial-aid/src/{assets => }/svg/fljotsdalshreppur.svg (100%) rename libs/application/templates/financial-aid/src/{assets => }/svg/floahreppur.svg (100%) rename libs/application/templates/financial-aid/src/{assets => }/svg/gardabaer.svg (100%) rename libs/application/templates/financial-aid/src/{assets => }/svg/grimsnes-og-grafningshreppur.svg (100%) rename libs/application/templates/financial-aid/src/{assets => }/svg/grindavikurbaer.svg (100%) rename libs/application/templates/financial-aid/src/{assets => }/svg/grundafjardarbaer.svg (100%) rename libs/application/templates/financial-aid/src/{assets => }/svg/grytubakkahreppur.svg (100%) rename libs/application/templates/financial-aid/src/{assets => }/svg/hafnarfjordur.svg (100%) rename libs/application/templates/financial-aid/src/{assets => }/svg/helgafellssveit.svg (100%) rename libs/application/templates/financial-aid/src/{assets => }/svg/horgarsveit.svg (100%) rename libs/application/templates/financial-aid/src/{assets => }/svg/hornafjordur.svg (100%) rename libs/application/templates/financial-aid/src/{assets => }/svg/hrunamannahreppur.svg (100%) rename libs/application/templates/financial-aid/src/{assets => }/svg/hunathing-vestra.svg (100%) rename libs/application/templates/financial-aid/src/{assets => }/svg/hunavatnshreppur.svg (100%) rename libs/application/templates/financial-aid/src/{assets => }/svg/hvalfjardarsveit.svg (100%) rename libs/application/templates/financial-aid/src/{assets => }/svg/hveragerdisbaer.svg (100%) rename libs/application/templates/financial-aid/src/{assets => }/svg/isafjardarbaer.svg (100%) rename libs/application/templates/financial-aid/src/{assets => }/svg/kaldrananeshreppur.svg (100%) rename libs/application/templates/financial-aid/src/{assets => }/svg/kjosarhreppur.svg (100%) rename libs/application/templates/financial-aid/src/{assets => }/svg/kopavogur.svg (100%) rename libs/application/templates/financial-aid/src/{assets => }/svg/langanesbyggd.svg (100%) rename libs/application/templates/financial-aid/src/{assets => }/svg/mosfellsbaer.svg (100%) rename libs/application/templates/financial-aid/src/{assets => }/svg/mulathing.svg (100%) rename libs/application/templates/financial-aid/src/{assets => }/svg/myrdalshreppur.svg (100%) rename libs/application/templates/financial-aid/src/{assets => }/svg/nordurthing.svg (100%) rename libs/application/templates/financial-aid/src/{assets => }/svg/olfus.svg (100%) rename libs/application/templates/financial-aid/src/{assets => }/svg/rangarthing-ytra.svg (100%) rename libs/application/templates/financial-aid/src/{assets => }/svg/rangarthing_eystra.svg (100%) rename libs/application/templates/financial-aid/src/{assets => }/svg/reykholahreppur.svg (100%) rename libs/application/templates/financial-aid/src/{assets => }/svg/reykjanesbaer.svg (100%) rename libs/application/templates/financial-aid/src/{assets => }/svg/sambandid.svg (100%) rename libs/application/templates/financial-aid/src/{assets => }/svg/seltjarnarnes.svg (100%) rename libs/application/templates/financial-aid/src/{assets => }/svg/skaftarhreppur.svg (100%) rename libs/application/templates/financial-aid/src/{assets => }/svg/skagabyggd.svg (100%) rename libs/application/templates/financial-aid/src/{assets => }/svg/skagafjordur.svg (100%) rename libs/application/templates/financial-aid/src/{assets => }/svg/skagastrond.svg (100%) rename libs/application/templates/financial-aid/src/{assets => }/svg/skeida-og-gnupverjahreppur.svg (100%) rename libs/application/templates/financial-aid/src/{assets => }/svg/skorradalur.svg (100%) rename libs/application/templates/financial-aid/src/{assets => }/svg/skutustadahreppur.svg (100%) rename libs/application/templates/financial-aid/src/{assets => }/svg/snaefellsbaer.svg (100%) rename libs/application/templates/financial-aid/src/{assets => }/svg/strandabyggd.svg (100%) rename libs/application/templates/financial-aid/src/{assets => }/svg/stykkisholmsbaer.svg (100%) rename libs/application/templates/financial-aid/src/{assets => }/svg/sudavikurhreppur.svg (100%) rename libs/application/templates/financial-aid/src/{assets => }/svg/sudurnesjabaer.svg (100%) rename libs/application/templates/financial-aid/src/{assets => }/svg/svalbardshreppur.svg (100%) rename libs/application/templates/financial-aid/src/{assets => }/svg/svalbardsstrandarhreppur.svg (100%) rename libs/application/templates/financial-aid/src/{assets => }/svg/talknafjardarhreppur.svg (100%) rename libs/application/templates/financial-aid/src/{assets => }/svg/thingeyjarsveit.svg (100%) rename libs/application/templates/financial-aid/src/{assets => }/svg/tjorneshreppur.svg (100%) rename libs/application/templates/financial-aid/src/{assets => }/svg/vestmannaeyjabaer.svg (100%) rename libs/application/templates/financial-aid/src/{assets => }/svg/vesturbyggd.svg (100%) rename libs/application/templates/financial-aid/src/{assets => }/svg/vogar.svg (100%) rename libs/application/templates/financial-aid/src/{assets => }/svg/vopnafjardarhreppur.svg (100%) delete mode 100644 libs/application/ui-fields/src/lib/AccordionFormField/AccordionFormField.tsx delete mode 100644 libs/application/ui-fields/src/lib/BankAccountFormField/BankAccountFormField.tsx diff --git a/libs/application/core/src/lib/fieldBuilders.ts b/libs/application/core/src/lib/fieldBuilders.ts index 2c77343d5aca..1ef54e3fa911 100644 --- a/libs/application/core/src/lib/fieldBuilders.ts +++ b/libs/application/core/src/lib/fieldBuilders.ts @@ -41,8 +41,6 @@ import { StaticTableField, HiddenInputWithWatchedValueField, HiddenInputField, - AccordionField, - BankAccountField, SliderField, MaybeWithApplication, MaybeWithApplicationAndFieldAndLocale, @@ -459,49 +457,6 @@ export const buildKeyValueField = (data: { } } -export const buildAccordionField = ( - data: Omit, -): AccordionField => { - const { - accordionItems, - title, - titleVariant, - id, - marginTop, - marginBottom, - condition, - } = data - return { - children: undefined, - id, - title, - titleVariant, - marginTop, - marginBottom, - accordionItems, - condition, - type: FieldTypes.ACCORDION, - component: FieldComponents.ACCORDION, - } -} - -export const buildBankAccountField = ( - data: Omit, -): BankAccountField => { - const { title, id, marginBottom, marginTop, titleVariant } = data - - return { - children: undefined, - id, - title, - marginBottom, - marginTop, - titleVariant, - type: FieldTypes.BANK_ACCOUNT, - component: FieldComponents.BANK_ACCOUNT, - } -} - export const buildSubmitField = (data: { id: string title: FormText diff --git a/libs/application/core/src/lib/messages.ts b/libs/application/core/src/lib/messages.ts index 65cb2f5cdd67..aaaafd03dee2 100644 --- a/libs/application/core/src/lib/messages.ts +++ b/libs/application/core/src/lib/messages.ts @@ -318,21 +318,6 @@ export const coreDefaultFieldMessages = defineMessages({ defaultMessage: 'Veljið skjöl til að hlaða upp', description: 'Default file upload button label', }, - defaultBankAccountBankNumber: { - id: 'application.system:core.default.bankAccount.bankNumber', - defaultMessage: 'Bankanúmer', - description: 'Bank account bank number', - }, - defaultBankAccountLedger: { - id: 'application.system:core.default.bankAccount.ledger', - defaultMessage: 'Höfuðbók', - description: 'Bank account ledger', - }, - defaultBankAccountAccountNumber: { - id: 'application.system:core.default.bankAccount.accountNumber', - defaultMessage: 'Reikningsnúmer', - description: 'Bank account account number', - }, defaultDownloadButtonTitle: { id: 'application.system:core.default.pdfLinkButtonField.downloadButtonTitle', defaultMessage: 'Hlaða niður skjali', diff --git a/libs/application/template-api-modules/src/lib/modules/templates/financial-aid/financial-aid.service.ts b/libs/application/template-api-modules/src/lib/modules/templates/financial-aid/financial-aid.service.ts index a6bb3c78fbf7..d3d79b8777f2 100644 --- a/libs/application/template-api-modules/src/lib/modules/templates/financial-aid/financial-aid.service.ts +++ b/libs/application/template-api-modules/src/lib/modules/templates/financial-aid/financial-aid.service.ts @@ -1,11 +1,9 @@ import { Injectable } from '@nestjs/common' import { - FinancialAidAnswers, ApproveOptions, - ChildrenSchoolInfo, CurrentApplication, - FinancialAidExternalData, + FAApplication, findFamilyStatus, TaxData, } from '@island.is/application/templates/financial-aid' @@ -14,7 +12,6 @@ import { AuthMiddleware } from '@island.is/auth-nest-tools' import { ApplicationState, FileType, - PersonalTaxReturn, UserType, } from '@island.is/financial-aid/shared/lib' import { @@ -27,17 +24,15 @@ import { import { TemplateApiModuleActionProps } from '../../../types' import { BaseTemplateApiService } from '../../base-template-api.service' import { - Application, ApplicationAnswerFile, ApplicationTypes, } from '@island.is/application/types' import { FetchError } from '@island.is/clients/middlewares' import { messages } from '@island.is/application/templates/financial-aid' import { TemplateApiError } from '@island.is/nest/problem' -import { getValueViaPath } from '@island.is/application/core' type Props = Omit & { - application: Application + application: FAApplication } @Injectable() @@ -80,26 +75,15 @@ export class FinancialAidService extends BaseTemplateApiService { auth, }: Props): Promise { const { id, answers, externalData } = application - const answersSchema = answers as FinancialAidAnswers - const externalDataSchema = - externalData as unknown as FinancialAidExternalData - const currentApplicationId = getValueViaPath( - externalData, - 'currentApplication.data.currentApplicationId', - ) as string | undefined - const childrenSchoolInfo = getValueViaPath( - answers, - 'childrenSchoolInfo', - ) as Array - - if (currentApplicationId) { + if (externalData.currentApplication.data?.currentApplicationId) { return { - currentApplicationId, + currentApplicationId: + externalData.currentApplication.data.currentApplicationId, } } const children = answers.childrenSchoolInfo - ? childrenSchoolInfo.map((child) => { + ? answers.childrenSchoolInfo.map((child) => { return { name: child.fullName, nationalId: child.nationalId, @@ -124,117 +108,102 @@ export class FinancialAidService extends BaseTemplateApiService { }) } - const personalTaxReturn = getValueViaPath( - externalData, - 'taxData.data.municipalitiesPersonalTaxReturn.personalTaxReturn', - ) as PersonalTaxReturn | undefined - - const spouseTaxReturn = getValueViaPath( - externalData, - 'taxDataSpouse.data.municipalitiesPersonalTaxReturn.personalTaxReturn', - ) as PersonalTaxReturn | undefined - const spouseTaxFiles = () => { - if (spouseTaxReturn == null) { + if ( + externalData?.taxDataSpouse?.data?.municipalitiesPersonalTaxReturn + ?.personalTaxReturn == null + ) { return [] } - return [spouseTaxReturn] + return [ + externalData?.taxDataSpouse?.data?.municipalitiesPersonalTaxReturn + ?.personalTaxReturn, + ] } const applicantTaxFiles = () => { - if (personalTaxReturn == null) { + if ( + externalData?.taxData?.data?.municipalitiesPersonalTaxReturn + ?.personalTaxReturn == null + ) { return [] } - return [personalTaxReturn] + return [ + externalData?.taxData?.data?.municipalitiesPersonalTaxReturn + ?.personalTaxReturn, + ] } const directTaxPayments = () => { - const combinedTaxPayments = [ - ...(externalDataSchema?.taxData?.data?.municipalitiesDirectTaxPayments - ?.directTaxPayments || []), - ...(externalDataSchema?.taxDataSpouse?.data - ?.municipalitiesDirectTaxPayments?.directTaxPayments || []), - ] - - return combinedTaxPayments.map((d) => { - d.userType = application.assignees.includes(auth.nationalId) - ? UserType.SPOUSE - : UserType.APPLICANT - return d - }) + if (externalData?.taxDataSpouse?.data) { + externalData?.taxData?.data?.municipalitiesDirectTaxPayments?.directTaxPayments.concat( + externalData?.taxDataSpouse?.data.municipalitiesDirectTaxPayments + ?.directTaxPayments, + ) + } + return externalData?.taxData?.data?.municipalitiesDirectTaxPayments?.directTaxPayments.map( + (d) => { + d.userType = application.assignees.includes(auth.nationalId) + ? UserType.SPOUSE + : UserType.APPLICANT + return d + }, + ) } - const files = formatFiles( - answersSchema?.taxReturnFiles ?? [], - FileType.TAXRETURN, - ) - .concat(formatFiles(answersSchema.incomeFiles ?? [], FileType.INCOME)) - .concat( - formatFiles( - answersSchema.spouseIncomeFiles ?? [], - FileType.SPOUSEFILES, - ), - ) - .concat( - formatFiles( - answersSchema.spouseTaxReturnFiles ?? [], - FileType.SPOUSEFILES, - ), - ) + const files = formatFiles(answers.taxReturnFiles, FileType.TAXRETURN) + .concat(formatFiles(answers.incomeFiles, FileType.INCOME)) + .concat(formatFiles(answers.spouseIncomeFiles, FileType.SPOUSEFILES)) + .concat(formatFiles(answers.spouseTaxReturnFiles, FileType.SPOUSEFILES)) .concat(formatFiles(spouseTaxFiles(), FileType.SPOUSEFILES)) .concat(formatFiles(applicantTaxFiles(), FileType.TAXRETURN)) - .concat( - formatFiles(answersSchema.childrenFiles ?? [], FileType.CHILDRENFILES), - ) + .concat(formatFiles(answers.childrenFiles, FileType.CHILDRENFILES)) const newApplication = { - name: externalDataSchema.nationalRegistry.data.fullName, - nationalId: externalDataSchema.nationalRegistry.data.nationalId, - phoneNumber: answersSchema.contactInfo.phone, - email: answersSchema.contactInfo.email, - homeCircumstances: answersSchema.homeCircumstances.type, - homeCircumstancesCustom: answersSchema.homeCircumstances.custom, - student: Boolean(answersSchema.student.isStudent === ApproveOptions.Yes), - studentCustom: answersSchema.student.custom, - hasIncome: Boolean(answersSchema.income.type === ApproveOptions.Yes), + name: externalData.nationalRegistry.data.fullName, + nationalId: externalData.nationalRegistry.data.nationalId, + phoneNumber: answers.contactInfo.phone, + email: answers.contactInfo.email, + homeCircumstances: answers.homeCircumstances.type, + homeCircumstancesCustom: answers.homeCircumstances.custom, + student: Boolean(answers.student.isStudent === ApproveOptions.Yes), + studentCustom: answers.student.custom, + hasIncome: Boolean(answers.income === ApproveOptions.Yes), usePersonalTaxCredit: Boolean( - answersSchema.personalTaxCredit.type === ApproveOptions.Yes, + answers.personalTaxCredit === ApproveOptions.Yes, ), - bankNumber: answersSchema.bankInfo.bankNumber, - ledger: answersSchema.bankInfo.ledger, - accountNumber: answersSchema.bankInfo.accountNumber, - employment: answersSchema.employment.type, - employmentCustom: answersSchema.employment.custom, - formComment: answersSchema.formComment, + bankNumber: answers.bankInfo.bankNumber, + ledger: answers.bankInfo.ledger, + accountNumber: answers.bankInfo.accountNumber, + employment: answers.employment.type, + employmentCustom: answers.employment.custom, + formComment: answers.formComment, state: ApplicationState.NEW, files: files, - children, - childrenComment: answersSchema.childrenComment, + children: children, + childrenComment: answers.childrenComment, spouseNationalId: - externalDataSchema.nationalRegistrySpouse.data?.nationalId || - answersSchema.relationshipStatus?.spouseNationalId, + externalData.nationalRegistrySpouse.data?.nationalId || + answers.relationshipStatus?.spouseNationalId, spouseEmail: - answersSchema.spouseContactInfo?.email || - answersSchema.spouse?.email || - answersSchema.relationshipStatus?.spouseEmail, - spousePhoneNumber: answersSchema.spouseContactInfo?.phone, + answers.spouseContactInfo?.email || + answers.spouse?.email || + answers.relationshipStatus?.spouseEmail, + spousePhoneNumber: answers.spouseContactInfo?.phone, spouseName: - externalDataSchema.nationalRegistrySpouse.data?.name || - answersSchema.spouseName, - spouseFormComment: answersSchema.spouseFormComment, - familyStatus: findFamilyStatus(answersSchema, externalData), - streetName: - externalDataSchema.nationalRegistry.data.address?.streetAddress, - postalCode: externalDataSchema.nationalRegistry.data.address?.postalCode, - city: externalDataSchema.nationalRegistry.data.address?.locality, + externalData.nationalRegistrySpouse.data?.name || answers.spouseName, + spouseFormComment: answers.spouseFormComment, + familyStatus: findFamilyStatus(answers, externalData), + streetName: externalData.nationalRegistry.data.address?.streetAddress, + postalCode: externalData.nationalRegistry.data.address?.postalCode, + city: externalData.nationalRegistry.data.address?.locality, municipalityCode: - externalDataSchema.nationalRegistry.data.address?.municipalityCode, + externalData.nationalRegistry.data.address?.municipalityCode, directTaxPayments: directTaxPayments(), hasFetchedDirectTaxPayment: - externalDataSchema?.taxData?.data?.municipalitiesDirectTaxPayments - ?.success, + externalData?.taxData?.data?.municipalitiesDirectTaxPayments?.success, spouseHasFetchedDirectTaxPayment: - externalDataSchema?.taxDataSpouse?.data?.municipalitiesDirectTaxPayments + externalData?.taxDataSpouse?.data?.municipalitiesDirectTaxPayments ?.success, applicationSystemId: id, } @@ -274,10 +243,8 @@ export class FinancialAidService extends BaseTemplateApiService { auth, application, }: Props): Promise { - const municiplaityCode = getValueViaPath( - application.externalData, - 'nationalRegistry.data.address.municipalityCode', - ) as string + const municiplaityCode = + application.externalData.nationalRegistry.data.address?.municipalityCode if (municiplaityCode == null) { return null } @@ -320,26 +287,23 @@ export class FinancialAidService extends BaseTemplateApiService { application, }: Props): Promise<{ success: boolean }> { const { answers, externalData } = application - const answersSchema = answers as unknown as FinancialAidAnswers - const externalDataSchema = - externalData as unknown as FinancialAidExternalData try { return await this.applicationApiWithAuth( auth, ).applicationControllerSendSpouseEmail({ spouseEmailDto: { - name: externalDataSchema.nationalRegistry.data.fullName, - email: answersSchema.contactInfo.email, + name: externalData.nationalRegistry.data.fullName, + email: answers.contactInfo.email, spouseName: - externalDataSchema.nationalRegistrySpouse.data?.name || - answersSchema.spouseName || + externalData.nationalRegistrySpouse.data?.name || + answers.spouseName || '', spouseEmail: - answersSchema.spouse?.email || - answersSchema.relationshipStatus.spouseEmail || + answers.spouse?.email || + answers.relationshipStatus.spouseEmail || '', municipalityCode: - externalDataSchema.municipality.data?.municipalityId || '', + externalData.municipality.data?.municipalityId || '', created: application.created, applicationSystemId: application.id, }, diff --git a/libs/application/templates/financial-aid/src/assets/ConfirmationImage.tsx b/libs/application/templates/financial-aid/src/assets/ConfirmationImage.tsx deleted file mode 100644 index 35c114439c4d..000000000000 --- a/libs/application/templates/financial-aid/src/assets/ConfirmationImage.tsx +++ /dev/null @@ -1,3736 +0,0 @@ -import React from 'react' - -export const ConfirmationImage = () => ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -) diff --git a/libs/application/templates/financial-aid/src/components/Logo/Logo.tsx b/libs/application/templates/financial-aid/src/components/Logo/Logo.tsx deleted file mode 100644 index fddd5ef14eb8..000000000000 --- a/libs/application/templates/financial-aid/src/components/Logo/Logo.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { Application } from '@island.is/application/types' -import { - Municipality, - logoKeyFromMunicipalityCode, -} from '@island.is/financial-aid/shared/lib' -import React, { useEffect, useState } from 'react' - -type Props = { - application: Application -} - -export const Logo = ({ application }: Props) => { - const [logo, setLogo] = useState() - const municipality = application.externalData.municipality - ?.data as Municipality - - useEffect(() => { - const getLogo = async () => { - const municipalityId = - municipality && municipality?.municipalityId - ? municipality.municipalityId - : '' - const svgLogo = await import( - `../../assets/svg/${logoKeyFromMunicipalityCode[municipalityId]}` - ) - setLogo(svgLogo.default) - } - getLogo() - }, []) - - return Municipality logo -} diff --git a/libs/application/templates/financial-aid/src/components/Status/Estimation/Estimation.tsx b/libs/application/templates/financial-aid/src/components/Status/Estimation/Estimation.tsx deleted file mode 100644 index 176a8a610ba4..000000000000 --- a/libs/application/templates/financial-aid/src/components/Status/Estimation/Estimation.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import React, { useMemo } from 'react' -import { useIntl } from 'react-intl' -import { Text, Box } from '@island.is/island-ui/core' -import { - aidCalculator, - estimatedBreakDown, - showSpouseData, -} from '@island.is/financial-aid/shared/lib' -import { ApproveOptions } from '../../../lib/types' -import { aidAmount as aidAmountMessages } from '../../../lib/messages' -import { findFamilyStatus } from '../../..' -import Breakdown from '../../../components/Breakdown/Breakdown' -import DescriptionText from '../../../components/DescriptionText/DescriptionText' -import { Application, DataProviderResult } from '@island.is/application/types' -import { getEstimationConstants } from './utils' - -type EstimationProps = { - application: Application - municipality: DataProviderResult -} - -export const Estimation = ({ application, municipality }: EstimationProps) => { - const { formatMessage } = useIntl() - const { answers, externalData } = application - - const getAidType = () => { - return !showSpouseData[findFamilyStatus(answers, externalData)] - } - - const { - individualAid, - cohabitationAid, - homeCircumstances, - personalTaxCredit, - } = getEstimationConstants(municipality, answers) - - const aidAmount = useMemo(() => { - if (homeCircumstances && municipality.data) { - return aidCalculator( - homeCircumstances, - getAidType() ? individualAid : cohabitationAid, - ) - } - }, [municipality.data]) - - return ( - <> - - <> - - {formatMessage(aidAmountMessages.title)} - - - - - - - - - ) -} diff --git a/libs/application/templates/financial-aid/src/components/Status/Estimation/VeitaEstimation.tsx b/libs/application/templates/financial-aid/src/components/Status/Estimation/VeitaEstimation.tsx deleted file mode 100644 index f44986ec70e8..000000000000 --- a/libs/application/templates/financial-aid/src/components/Status/Estimation/VeitaEstimation.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import React, { useMemo } from 'react' -import { useIntl } from 'react-intl' -import { Text, Box } from '@island.is/island-ui/core' -import { aidAmount as aidAmountMessages } from '../../../lib/messages' -import Breakdown from '../../../components/Breakdown/Breakdown' -import DescriptionText from '../../../components/DescriptionText/DescriptionText' -import { DataProviderResult } from '@island.is/application/types' -import { getEstimationConstants } from './utils' -import { - estimatedBreakDown, - showSpouseData, - Application as FinancialAidAnswers, - aidCalculator, -} from '@island.is/financial-aid/shared/lib' - -type VeitaEstiamtionProps = { - application: FinancialAidAnswers - municipality: DataProviderResult -} - -export const VeitaEstimation = ({ - application, - municipality, -}: VeitaEstiamtionProps) => { - const { formatMessage } = useIntl() - - const getAidType = () => { - if (application.familyStatus != undefined) { - return !showSpouseData[application.familyStatus] - } else { - return application.spouseNationalId == null - } - } - - const { individualAid, cohabitationAid } = - getEstimationConstants(municipality) - - const aidAmount = useMemo(() => { - if (municipality.data && application.homeCircumstances) { - return aidCalculator( - application.homeCircumstances, - getAidType() ? individualAid : cohabitationAid, - ) - } - }, [municipality.data]) - - return ( - <> - - <> - - {formatMessage(aidAmountMessages.title)} - - - - - - - - - ) -} diff --git a/libs/application/templates/financial-aid/src/components/Status/Estimation/utils.ts b/libs/application/templates/financial-aid/src/components/Status/Estimation/utils.ts deleted file mode 100644 index f9a99f642cf6..000000000000 --- a/libs/application/templates/financial-aid/src/components/Status/Estimation/utils.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { getValueViaPath } from '@island.is/application/core' -import { DataProviderResult, FormValue } from '@island.is/application/types' -import { Aid, HomeCircumstances } from '@island.is/financial-aid/shared/lib' -import { ApproveOptions } from '../../../lib/types' - -export const getEstimationConstants = ( - municipality: DataProviderResult, - answers?: FormValue, -) => { - const municipalityData = municipality.data as unknown as Record< - string, - unknown - > - const individualAid = getValueViaPath( - municipalityData, - 'data.individualAid', - ) - const cohabitationAid = getValueViaPath( - municipalityData, - 'cohabitationAid', - ) - - if (answers) { - const homeCircumstances = getValueViaPath( - answers, - 'homeCircumstances.type', - ) - const personalTaxCredit = getValueViaPath( - answers, - 'personalTaxCredit.type', - ) - - return { - individualAid, - cohabitationAid, - homeCircumstances, - personalTaxCredit, - } - } - - return { individualAid, cohabitationAid } -} diff --git a/libs/application/templates/financial-aid/src/components/Summary/Files.css.ts b/libs/application/templates/financial-aid/src/components/Summary/Files.css.ts deleted file mode 100644 index aec52ce1eee5..000000000000 --- a/libs/application/templates/financial-aid/src/components/Summary/Files.css.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { style } from '@vanilla-extract/css' - -export const filesButtons = style({ - selectors: { - '&:hover': { - cursor: 'pointer', - }, - }, -}) diff --git a/libs/application/templates/financial-aid/src/components/Summary/SummaryBlock.css.ts b/libs/application/templates/financial-aid/src/components/Summary/SummaryBlock.css.ts deleted file mode 100644 index e119e774e5ae..000000000000 --- a/libs/application/templates/financial-aid/src/components/Summary/SummaryBlock.css.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { style } from '@vanilla-extract/css' -import { theme } from '@island.is/island-ui/theme' - -export const summaryBlockChild = style({ - minWidth: '50%', - '@media': { - [`screen and (min-width: ${theme.breakpoints.lg}px)`]: { - minWidth: '83%', - }, - }, -}) diff --git a/libs/application/templates/financial-aid/src/fields/AboutForm/AboutForm.tsx b/libs/application/templates/financial-aid/src/fields/AboutForm/AboutForm.tsx new file mode 100644 index 000000000000..976f27f7ee55 --- /dev/null +++ b/libs/application/templates/financial-aid/src/fields/AboutForm/AboutForm.tsx @@ -0,0 +1,37 @@ +import React from 'react' +import { useIntl } from 'react-intl' + +import { Text, Box } from '@island.is/island-ui/core' +import { useLocale } from '@island.is/localization' +import { currentMonth } from '@island.is/financial-aid/shared/lib' + +import { DescriptionText, PrivacyPolicyAccordion } from '..' +import { FAFieldBaseProps } from '../../lib/types' +import withLogo from '../Logo/Logo' +import { aboutForm } from '../../lib/messages' + +const AboutForm = ({ application }: FAFieldBaseProps) => { + const { formatMessage } = useIntl() + const { lang } = useLocale() + + return ( + <> + + {formatMessage(aboutForm.general.description, { + currentMonth: currentMonth(lang), + })} + + + + + + + + ) +} + +export default withLogo(AboutForm) diff --git a/libs/application/templates/financial-aid/src/fields/AboutSpouseForm/AboutSpouseForm.tsx b/libs/application/templates/financial-aid/src/fields/AboutSpouseForm/AboutSpouseForm.tsx new file mode 100644 index 000000000000..9cc82c71dd69 --- /dev/null +++ b/libs/application/templates/financial-aid/src/fields/AboutSpouseForm/AboutSpouseForm.tsx @@ -0,0 +1,38 @@ +import React from 'react' +import { + currentMonth, + getNextPeriod, +} from '@island.is/financial-aid/shared/lib' +import { Box } from '@island.is/island-ui/core' +import { useLocale } from '@island.is/localization' + +import { aboutSpouseForm } from '../../lib/messages' +import { DescriptionText, PrivacyPolicyAccordion } from '..' +import { FAFieldBaseProps } from '../../lib/types' +import withLogo from '../Logo/Logo' + +const AboutSpouseForm = ({ application }: FAFieldBaseProps) => { + const { lang } = useLocale() + const { nationalRegistry, municipality } = application.externalData + + return ( + <> + + + + + + ) +} + +export default withLogo(AboutSpouseForm) diff --git a/libs/application/templates/financial-aid/src/fields/BankInfoForm/BankInfoForm.tsx b/libs/application/templates/financial-aid/src/fields/BankInfoForm/BankInfoForm.tsx new file mode 100644 index 000000000000..463cece0ae97 --- /dev/null +++ b/libs/application/templates/financial-aid/src/fields/BankInfoForm/BankInfoForm.tsx @@ -0,0 +1,82 @@ +import React from 'react' +import { Box, Text, GridRow, GridColumn } from '@island.is/island-ui/core' +import { FAFieldBaseProps } from '../../lib/types' +import { useIntl } from 'react-intl' +import { bankInfoForm } from '../../lib/messages' +import { InputController } from '@island.is/shared/form-fields' +import withLogo from '../Logo/Logo' + +const BankInfoForm = ({ field, application }: FAFieldBaseProps) => { + const { id } = field + const { formatMessage } = useIntl() + const { answers } = application + + const bankNumberId = `${id}.bankNumber` + const ledgerId = `${id}.ledger` + const accountNumberId = `${id}.accountNumber` + + return ( + <> + + {formatMessage(bankInfoForm.general.info)} + + + + + + + + + + + + + + + + + + + + {formatMessage(bankInfoForm.general.descriptionTitle)} + + + + {formatMessage(bankInfoForm.general.description)} + + + ) +} + +export default withLogo(BankInfoForm) diff --git a/libs/application/templates/financial-aid/src/components/Breakdown/Breakdown.tsx b/libs/application/templates/financial-aid/src/fields/Breakdown/Breakdown.tsx similarity index 100% rename from libs/application/templates/financial-aid/src/components/Breakdown/Breakdown.tsx rename to libs/application/templates/financial-aid/src/fields/Breakdown/Breakdown.tsx diff --git a/libs/application/templates/financial-aid/src/fields/childrenFilesForm/ChildrenFilesForm.tsx b/libs/application/templates/financial-aid/src/fields/ChildrenFilesForm/ChildrenFilesForm.tsx similarity index 58% rename from libs/application/templates/financial-aid/src/fields/childrenFilesForm/ChildrenFilesForm.tsx rename to libs/application/templates/financial-aid/src/fields/ChildrenFilesForm/ChildrenFilesForm.tsx index 4e8c901d9749..9a640093a7bf 100644 --- a/libs/application/templates/financial-aid/src/fields/childrenFilesForm/ChildrenFilesForm.tsx +++ b/libs/application/templates/financial-aid/src/fields/ChildrenFilesForm/ChildrenFilesForm.tsx @@ -2,11 +2,11 @@ import React from 'react' import { useIntl } from 'react-intl' import { Text, UploadFile } from '@island.is/island-ui/core' import { childrenFilesForm } from '../../lib/messages' -import { UploadFileType } from '../..' -import Files from '../files/Files' -import { FieldBaseProps } from '@island.is/application/types' +import { FAFieldBaseProps, OverrideAnswerSchema, UploadFileType } from '../..' +import { Files } from '..' +import withLogo from '../Logo/Logo' -export const ChildrenFilesForm = ({ field, application }: FieldBaseProps) => { +const ChildrenFilesForm = ({ field, application }: FAFieldBaseProps) => { const { formatMessage } = useIntl() const { id, answers } = application @@ -17,9 +17,13 @@ export const ChildrenFilesForm = ({ field, application }: FieldBaseProps) => { ) } + +export default withLogo(ChildrenFilesForm) diff --git a/libs/application/templates/financial-aid/src/fields/ChildrenForm/ChildInput.tsx b/libs/application/templates/financial-aid/src/fields/ChildrenForm/ChildInput.tsx new file mode 100644 index 000000000000..587be9cdd6fb --- /dev/null +++ b/libs/application/templates/financial-aid/src/fields/ChildrenForm/ChildInput.tsx @@ -0,0 +1,82 @@ +import React, { FC } from 'react' + +import { Text, Box } from '@island.is/island-ui/core' + +import { childrenForm } from '../../lib/messages' +import format from 'date-fns/format' + +import { InputController } from '@island.is/shared/form-fields' + +import kennitala from 'kennitala' +import { useLocale } from '@island.is/localization' +import { RecordObject } from '@island.is/application/types' +import { getErrorViaPath } from '@island.is/application/core' +import { useFormContext } from 'react-hook-form' + +interface Props { + fieldIndex: string + errors: RecordObject | undefined + childFullName: string + childNationalId: string +} + +export const ChildInput: FC> = ({ + fieldIndex, + errors, + childFullName, + childNationalId, +}) => { + const { setValue, clearErrors } = useFormContext() + + const schoolField = `${fieldIndex}.school` + const nameField = `${fieldIndex}.fullName` + const nationalIdField = `${fieldIndex}.nationalId` + + setValue(nameField, childFullName) + setValue(nationalIdField, childNationalId) + + const nationalId = childNationalId + const kennitalaInfo = kennitala.info(nationalId) + const birthday = kennitalaInfo?.birthday + const age = kennitalaInfo?.age + const dateOfBirth = new Date(birthday) + + const { formatMessage } = useLocale() + + if (age >= 18) { + return null + } + return ( + + + {childFullName} + + + {formatMessage(childrenForm.page.birthday, { + birthday: format(dateOfBirth, 'dd.MM.yyyy'), + })} + + + + { + clearErrors(schoolField) + }} + /> + + + ) +} diff --git a/libs/application/templates/financial-aid/src/fields/ChildrenForm/ChildrenForm.tsx b/libs/application/templates/financial-aid/src/fields/ChildrenForm/ChildrenForm.tsx index 3eca247056e1..37fc08ac83a0 100644 --- a/libs/application/templates/financial-aid/src/fields/ChildrenForm/ChildrenForm.tsx +++ b/libs/application/templates/financial-aid/src/fields/ChildrenForm/ChildrenForm.tsx @@ -1,33 +1,38 @@ import React from 'react' -import { Text, Box } from '@island.is/island-ui/core' +import { useIntl } from 'react-intl' + +import { Text, Box, Input } from '@island.is/island-ui/core' + +import { DescriptionText } from '..' +import { + FAFieldBaseProps, + SummaryComment as SummaryCommentType, +} from '../../lib/types' +import withLogo from '../Logo/Logo' import { childrenForm } from '../../lib/messages' + +import { ChildInput } from './ChildInput' import { sortChildrenUnderAgeByAge } from '../../lib/utils' -import { useFormContext } from 'react-hook-form' -import { useLocale } from '@island.is/localization' -import format from 'date-fns/format' -import { InputController } from '@island.is/shared/form-fields' -import { getErrorViaPath } from '@island.is/application/core' -import kennitala from 'kennitala' -import { - ApplicantChildCustodyInformation, - FieldBaseProps, -} from '@island.is/application/types' +import { Controller, useFormContext } from 'react-hook-form' -export const ChildrenForm = ({ - application, - field, - errors, -}: FieldBaseProps) => { - const { setValue, clearErrors } = useFormContext() - const { formatMessage } = useLocale() +const ChildrenForm = ({ application, field, errors }: FAFieldBaseProps) => { + const { formatMessage } = useIntl() + const { setValue } = useFormContext() - const { externalData } = application - const childrenExternalData = externalData.childrenCustodyInformation - .data as ApplicantChildCustodyInformation[] + const { externalData, answers } = application + const childrenExternalData = externalData.childrenCustodyInformation.data const childrenInfo = sortChildrenUnderAgeByAge(childrenExternalData) + const summaryCommentType = SummaryCommentType.CHILDRENCOMMENT return ( <> + + {formatMessage(childrenForm.general.description)} + + + + + {childrenInfo?.map((child, index) => { const fieldIndex = `${field.id}[${index}]` @@ -37,46 +42,56 @@ export const ChildrenForm = ({ child.livesWithBothParents, ) - const schoolField = `${fieldIndex}.school` - const nameField = `${fieldIndex}.fullName` - const nationalIdField = `${fieldIndex}.nationalId` - - setValue(nameField, child.fullName) - setValue(nationalIdField, child.nationalId) - - const kennitalaInfo = kennitala.info(child.nationalId) - const birthday = new Date(kennitalaInfo?.birthday) - return ( - - - {child.fullName} - - - {formatMessage(childrenForm.page.birthday, { - birthday: format(birthday, 'dd.MM.yyyy'), - })} - + + ) + })} + + + + + {formatMessage(childrenForm.page.commentTitle)} + + + {formatMessage(childrenForm.page.commentText)} + + - - { - clearErrors(schoolField) + { + return ( + { + onChange(e.target.value) + setValue(summaryCommentType, e.target.value) }} /> - - - ) - })} + ) + }} + /> + ) } + +export default withLogo(ChildrenForm) diff --git a/libs/application/templates/financial-aid/src/fields/Confirmation/ApplicantConfirmation.tsx b/libs/application/templates/financial-aid/src/fields/Confirmation/ApplicantConfirmation.tsx new file mode 100644 index 000000000000..79fd6945a41c --- /dev/null +++ b/libs/application/templates/financial-aid/src/fields/Confirmation/ApplicantConfirmation.tsx @@ -0,0 +1,39 @@ +import React from 'react' + +import { ApproveOptions, FAFieldBaseProps } from '../../lib/types' +import { confirmation } from '../../lib/messages' +import { hasFiles, hasSpouse } from '../../lib/utils' +import Confirmation from './Confirmation' + +const ApplicantConfirmation = ({ application }: FAFieldBaseProps) => { + const { answers, externalData } = application + + const applicantHasSpouse = hasSpouse(answers, externalData) + const missingIncomeFiles = + answers.income === ApproveOptions.Yes && !hasFiles('incomeFiles', answers) + + const firstStepText = () => { + switch (true) { + case applicantHasSpouse && missingIncomeFiles: + return confirmation.nextSteps.contentBothMissingFiles + case applicantHasSpouse: + return confirmation.nextSteps.contentSpouseMissingFiles + case missingIncomeFiles: + return confirmation.nextSteps.contentMissingFiles + } + } + + return ( + + ) +} + +export default ApplicantConfirmation diff --git a/libs/application/templates/financial-aid/src/fields/Confirmation/Confirmation.tsx b/libs/application/templates/financial-aid/src/fields/Confirmation/Confirmation.tsx new file mode 100644 index 000000000000..956ef692f842 --- /dev/null +++ b/libs/application/templates/financial-aid/src/fields/Confirmation/Confirmation.tsx @@ -0,0 +1,116 @@ +import React from 'react' +import { MessageDescriptor, useIntl } from 'react-intl' + +import { getNextPeriod } from '@island.is/financial-aid/shared/lib' +import { AlertMessage, Box, Text } from '@island.is/island-ui/core' +import { useLocale } from '@island.is/localization' + +import { confirmation, copyUrl } from '../../lib/messages' +import { DescriptionText, ConfirmationSectionImage, CopyUrl } from '..' + +interface Props { + firstStepText?: MessageDescriptor + missingIncomeFiles: boolean + hasSpouse?: boolean + spouseEmailSuccess?: boolean + municipalityHomepage?: string +} + +const Confirmation = ({ + firstStepText, + missingIncomeFiles, + hasSpouse, + spouseEmailSuccess, + municipalityHomepage, +}: Props) => { + const { formatMessage } = useIntl() + const { lang } = useLocale() + + return ( + <> + + {missingIncomeFiles ? ( + + ) : ( + + )} + {hasSpouse && ( + + + + )} + + + + {formatMessage(confirmation.nextSteps.title)} + + + {firstStepText && } + + + + + + {hasSpouse && ( + <> + + {formatMessage(confirmation.sharedLink.title)} + + + + + + )} + + + {formatMessage(confirmation.links.title)} + + + + + + + + + ) +} + +export default Confirmation diff --git a/libs/application/templates/financial-aid/src/fields/Confirmation/SpouseConfirmation.tsx b/libs/application/templates/financial-aid/src/fields/Confirmation/SpouseConfirmation.tsx new file mode 100644 index 000000000000..2fec48e4e8bf --- /dev/null +++ b/libs/application/templates/financial-aid/src/fields/Confirmation/SpouseConfirmation.tsx @@ -0,0 +1,28 @@ +import React from 'react' + +import { ApproveOptions, FAFieldBaseProps } from '../../lib/types' +import { confirmation } from '../../lib/messages' +import { hasFiles } from '../../lib/utils' +import Confirmation from './Confirmation' + +const SpouseConfirmation = ({ application }: FAFieldBaseProps) => { + const { answers, externalData } = application + + const missingIncomeFiles = + answers.spouseIncome === ApproveOptions.Yes && + !hasFiles('spouseIncomeFiles', answers) + + return ( + + ) +} + +export default SpouseConfirmation diff --git a/libs/application/templates/financial-aid/src/fields/ConfirmationSectionImage/ConfirmationSectionImage.tsx b/libs/application/templates/financial-aid/src/fields/ConfirmationSectionImage/ConfirmationSectionImage.tsx new file mode 100644 index 000000000000..822bf4294a83 --- /dev/null +++ b/libs/application/templates/financial-aid/src/fields/ConfirmationSectionImage/ConfirmationSectionImage.tsx @@ -0,0 +1,18 @@ +import React from 'react' +import { Box } from '@island.is/island-ui/core' + +import { confirmationIllustration } from '../Shared.css' + +const ConfirmationSectionImage = () => { + return ( + + + + ) +} +export default ConfirmationSectionImage diff --git a/libs/application/templates/financial-aid/src/fields/ContactInfo/ContactInfo.tsx b/libs/application/templates/financial-aid/src/fields/ContactInfo/ContactInfo.tsx new file mode 100644 index 000000000000..c3ed1d0da863 --- /dev/null +++ b/libs/application/templates/financial-aid/src/fields/ContactInfo/ContactInfo.tsx @@ -0,0 +1,68 @@ +import React from 'react' +import { Text, Box } from '@island.is/island-ui/core' +import { FAFieldBaseProps } from '../../lib/types' +import { useIntl } from 'react-intl' +import { contactInfo } from '../../lib/messages' +import { InputController } from '@island.is/shared/form-fields' +import { useFormContext } from 'react-hook-form' +import { getValueViaPath } from '@island.is/application/core' +import { answersSchema } from '../../lib/dataSchema' +import { Routes } from '../../lib/constants' +import withLogo from '../Logo/Logo' + +const ContactInfo = ({ field, errors, application }: FAFieldBaseProps) => { + const { formatMessage } = useIntl() + const { answers } = application + const { id } = field + const { clearErrors } = useFormContext() + + const emailPath = `${id}.email` + const phonePath = `${id}.phone` + + return ( + <> + + {formatMessage(contactInfo.general.description)} + + + { + clearErrors(emailPath) + }} + /> + + + { + clearErrors(phonePath) + }} + /> + + + ) +} + +export default withLogo(ContactInfo) diff --git a/libs/application/templates/financial-aid/src/fields/CopyUrl/CopyUrl.tsx b/libs/application/templates/financial-aid/src/fields/CopyUrl/CopyUrl.tsx index 4f3aef864e4a..38b57388ec2a 100644 --- a/libs/application/templates/financial-aid/src/fields/CopyUrl/CopyUrl.tsx +++ b/libs/application/templates/financial-aid/src/fields/CopyUrl/CopyUrl.tsx @@ -8,7 +8,7 @@ interface Props { successMessage: string } -export const CopyUrl = ({ inputLabel, buttonLabel, successMessage }: Props) => { +const CopyUrl = ({ inputLabel, buttonLabel, successMessage }: Props) => { const [currentUrl, setCurrentUrl] = useState(undefined) const copyToClipboard = (val: string) => { @@ -70,3 +70,5 @@ export const CopyUrl = ({ inputLabel, buttonLabel, successMessage }: Props) => { ) } + +export default CopyUrl diff --git a/libs/application/templates/financial-aid/src/components/DescriptionText/DescriptionText.css.ts b/libs/application/templates/financial-aid/src/fields/DescriptionText/DescriptionText.css.ts similarity index 100% rename from libs/application/templates/financial-aid/src/components/DescriptionText/DescriptionText.css.ts rename to libs/application/templates/financial-aid/src/fields/DescriptionText/DescriptionText.css.ts diff --git a/libs/application/templates/financial-aid/src/components/DescriptionText/DescriptionText.tsx b/libs/application/templates/financial-aid/src/fields/DescriptionText/DescriptionText.tsx similarity index 100% rename from libs/application/templates/financial-aid/src/components/DescriptionText/DescriptionText.tsx rename to libs/application/templates/financial-aid/src/fields/DescriptionText/DescriptionText.tsx diff --git a/libs/application/templates/financial-aid/src/components/DirectTaxPaymentsModal/DirectTaxPaymentModal.css.ts b/libs/application/templates/financial-aid/src/fields/DirectTaxPaymentsModal/DirectTaxPaymentModal.css.ts similarity index 100% rename from libs/application/templates/financial-aid/src/components/DirectTaxPaymentsModal/DirectTaxPaymentModal.css.ts rename to libs/application/templates/financial-aid/src/fields/DirectTaxPaymentsModal/DirectTaxPaymentModal.css.ts diff --git a/libs/application/templates/financial-aid/src/components/DirectTaxPaymentsModal/DirectTaxPaymentModal.tsx b/libs/application/templates/financial-aid/src/fields/DirectTaxPaymentsModal/DirectTaxPaymentModal.tsx similarity index 96% rename from libs/application/templates/financial-aid/src/components/DirectTaxPaymentsModal/DirectTaxPaymentModal.tsx rename to libs/application/templates/financial-aid/src/fields/DirectTaxPaymentsModal/DirectTaxPaymentModal.tsx index 76d9d3b26dc2..b2fb8481b914 100644 --- a/libs/application/templates/financial-aid/src/components/DirectTaxPaymentsModal/DirectTaxPaymentModal.tsx +++ b/libs/application/templates/financial-aid/src/fields/DirectTaxPaymentsModal/DirectTaxPaymentModal.tsx @@ -1,10 +1,12 @@ import React from 'react' import { useIntl } from 'react-intl' import { ModalBase, Text, Box, Button } from '@island.is/island-ui/core' + import { DirectTaxPayment } from '@island.is/financial-aid/shared/lib' +import { TaxBreakdown } from '..' import { directTaxPaymentModal } from '../../lib/messages' + import * as styles from './DirectTaxPaymentModal.css' -import TaxBreakdown from '../TaxBreakdown/TaxBreakdown' interface Props { isVisible: boolean diff --git a/libs/application/templates/financial-aid/src/fields/EmploymentForm/EmploymentForm.tsx b/libs/application/templates/financial-aid/src/fields/EmploymentForm/EmploymentForm.tsx new file mode 100644 index 000000000000..1cdf2e305191 --- /dev/null +++ b/libs/application/templates/financial-aid/src/fields/EmploymentForm/EmploymentForm.tsx @@ -0,0 +1,97 @@ +import React from 'react' +import { Box, Input } from '@island.is/island-ui/core' +import { employmentForm, input } from '../../lib/messages' +import { useIntl } from 'react-intl' + +import { RadioController } from '@island.is/shared/form-fields' +import { Controller, useFormContext } from 'react-hook-form' +import { Employment } from '@island.is/financial-aid/shared/lib' +import * as styles from '../Shared.css' +import cn from 'classnames' +import { FAFieldBaseProps, InputTypes } from '../../lib/types' +import withLogo from '../Logo/Logo' + +const EmploymentForm = ({ application, errors }: FAFieldBaseProps) => { + const typeInput = { + id: 'employment.type', + error: errors?.employment?.type, + } as InputTypes + + const customInput = { + id: 'employment.custom', + error: errors?.employment?.custom, + } as InputTypes + + const { formatMessage } = useIntl() + + const { answers } = application + + const { setValue, getValues, clearErrors } = useFormContext() + + return ( + <> + + + + + + { + return ( + { + clearErrors(customInput.id) + onChange(e.target.value) + setValue(customInput.id, e.target.value) + }} + /> + ) + }} + /> + + + ) +} + +export default withLogo(EmploymentForm) diff --git a/libs/application/templates/financial-aid/src/fields/fileUploadController/FileUploadControler.tsx b/libs/application/templates/financial-aid/src/fields/FileUploadContainer/FileUploadContainer.tsx similarity index 94% rename from libs/application/templates/financial-aid/src/fields/fileUploadController/FileUploadControler.tsx rename to libs/application/templates/financial-aid/src/fields/FileUploadContainer/FileUploadContainer.tsx index 297bbefe6d7a..8206fddde401 100644 --- a/libs/application/templates/financial-aid/src/fields/fileUploadController/FileUploadControler.tsx +++ b/libs/application/templates/financial-aid/src/fields/FileUploadContainer/FileUploadContainer.tsx @@ -3,7 +3,7 @@ import { useIntl } from 'react-intl' import { Text, Box, GridRow, GridColumn } from '@island.is/island-ui/core' import { filesText } from '../../lib/messages' import cn from 'classnames' -import * as styles from './FileUploadController.css' +import * as styles from './../Shared.css' interface Props { children: ReactNode hasError?: boolean diff --git a/libs/application/templates/financial-aid/src/fields/files/Files.tsx b/libs/application/templates/financial-aid/src/fields/Files/Files.tsx similarity index 95% rename from libs/application/templates/financial-aid/src/fields/files/Files.tsx rename to libs/application/templates/financial-aid/src/fields/Files/Files.tsx index e1077c1412d5..4fb979e8c9d6 100644 --- a/libs/application/templates/financial-aid/src/fields/files/Files.tsx +++ b/libs/application/templates/financial-aid/src/fields/Files/Files.tsx @@ -3,11 +3,11 @@ import { InputFileUpload, UploadFile } from '@island.is/island-ui/core' import { useIntl } from 'react-intl' import { filesText } from '../../lib/messages' +import { FileUploadContainer } from '..' import { UploadFileType } from '../../lib/types' import { useFormContext } from 'react-hook-form' import { useFileUpload } from '../../lib/hooks/useFileUpload' import { FILE_SIZE_LIMIT, UPLOAD_ACCEPT } from '../../lib/constants' -import FileUploadContainer from '../fileUploadController/FileUploadControler' interface Props { uploadFiles: UploadFile[] diff --git a/libs/application/templates/financial-aid/src/fields/HomeCircumstancesForm/HomeCircumstancesForm.tsx b/libs/application/templates/financial-aid/src/fields/HomeCircumstancesForm/HomeCircumstancesForm.tsx new file mode 100644 index 000000000000..c3bb45953858 --- /dev/null +++ b/libs/application/templates/financial-aid/src/fields/HomeCircumstancesForm/HomeCircumstancesForm.tsx @@ -0,0 +1,116 @@ +import React from 'react' +import { Box, Input } from '@island.is/island-ui/core' +import { homeCircumstancesForm, input } from '../../lib/messages' +import { useIntl } from 'react-intl' + +import { RadioController } from '@island.is/shared/form-fields' +import { Controller, useFormContext } from 'react-hook-form' +import { HomeCircumstances } from '@island.is/financial-aid/shared/lib' +import * as styles from '../Shared.css' +import cn from 'classnames' +import { FAFieldBaseProps, InputTypes } from '../../lib/types' +import withLogo from '../Logo/Logo' + +const HomeCircumstancesForm = ({ application, errors }: FAFieldBaseProps) => { + const typeInput = { + id: 'homeCircumstances.type', + error: errors?.homeCircumstances?.type, + } as InputTypes + + const customInput = { + id: 'homeCircumstances.custom', + error: errors?.homeCircumstances?.custom, + } as InputTypes + + const { formatMessage } = useIntl() + + const { answers } = application + + const { setValue, getValues, clearErrors } = useFormContext() + + return ( + <> + + + + + + { + return ( + { + clearErrors(customInput.id) + onChange(e.target.value) + setValue(customInput.id, e.target.value) + }} + /> + ) + }} + /> + + + ) +} + +export default withLogo(HomeCircumstancesForm) diff --git a/libs/application/templates/financial-aid/src/fields/InRelationshipForm/InRelationshipForm.tsx b/libs/application/templates/financial-aid/src/fields/InRelationshipForm/InRelationshipForm.tsx new file mode 100644 index 000000000000..acab22dbf322 --- /dev/null +++ b/libs/application/templates/financial-aid/src/fields/InRelationshipForm/InRelationshipForm.tsx @@ -0,0 +1,67 @@ +import React from 'react' +import { Box, Text } from '@island.is/island-ui/core' +import { FAFieldBaseProps } from '../../lib/types' +import { + CheckboxController, + InputController, +} from '@island.is/shared/form-fields' +import { useIntl } from 'react-intl' +import { inRelationship } from '../../lib/messages' +import DescriptionText from '../DescriptionText/DescriptionText' +import { useFormContext } from 'react-hook-form' +import withLogo from '../Logo/Logo' + +const spouseEmail = 'spouse.email' +const spouseApproveTerms = 'spouse.approveTerms' + +const InRelationshipForm = ({ errors, application }: FAFieldBaseProps) => { + const { formatMessage } = useIntl() + const { answers } = application + const { clearErrors } = useFormContext() + const spouseEmailError = errors?.spouse?.email + const spouseApproveTermsError = errors?.spouse?.approveTerms + + return ( +
+ + {formatMessage(inRelationship.general.intro)} + + + + + + + { + clearErrors(spouseEmail) + }} + /> + + + +
+ ) +} + +export default withLogo(InRelationshipForm) diff --git a/libs/application/templates/financial-aid/src/fields/incomeFilesForm/IncomeFilesForm.tsx b/libs/application/templates/financial-aid/src/fields/IncomeFilesForm/IncomeFilesForm.tsx similarity index 55% rename from libs/application/templates/financial-aid/src/fields/incomeFilesForm/IncomeFilesForm.tsx rename to libs/application/templates/financial-aid/src/fields/IncomeFilesForm/IncomeFilesForm.tsx index c69fabaa0f5b..baf3a42ed199 100644 --- a/libs/application/templates/financial-aid/src/fields/incomeFilesForm/IncomeFilesForm.tsx +++ b/libs/application/templates/financial-aid/src/fields/IncomeFilesForm/IncomeFilesForm.tsx @@ -2,32 +2,32 @@ import React from 'react' import { useIntl } from 'react-intl' import { Text, UploadFile } from '@island.is/island-ui/core' import { incomeFilesForm } from '../../lib/messages' -import { UploadFileType } from '../..' -import { FieldBaseProps } from '@island.is/application/types' -import { getValueViaPath } from '@island.is/application/core' -import Files from '../files/Files' +import { FAFieldBaseProps, OverrideAnswerSchema, UploadFileType } from '../..' +import { Files } from '..' +import withLogo from '../Logo/Logo' -export const IncomeFilesForm = ({ field, application }: FieldBaseProps) => { +const IncomeFilesForm = ({ field, application }: FAFieldBaseProps) => { const { formatMessage } = useIntl() const { id, answers, externalData } = application - const success = getValueViaPath( - externalData, - 'taxData.data.municipalitiesDirectTaxPayments.success', - ) + return ( <> {formatMessage( - success + externalData?.taxData?.data?.municipalitiesDirectTaxPayments?.success ? incomeFilesForm.general.descriptionTaxSuccess : incomeFilesForm.general.description, )} ) } + +export default withLogo(IncomeFilesForm) diff --git a/libs/application/templates/financial-aid/src/fields/IncomeForm/IncomeForm.tsx b/libs/application/templates/financial-aid/src/fields/IncomeForm/IncomeForm.tsx new file mode 100644 index 000000000000..d26d57b8c8fb --- /dev/null +++ b/libs/application/templates/financial-aid/src/fields/IncomeForm/IncomeForm.tsx @@ -0,0 +1,61 @@ +import React from 'react' +import { Box, Text, GridRow, GridColumn } from '@island.is/island-ui/core' +import { + FAFieldBaseProps, + ApproveOptions, + OverrideAnswerSchema, + ErrorSchema, +} from '../../lib/types' +import { useIntl } from 'react-intl' +import { incomeForm } from '../../lib/messages' +import { RadioController } from '@island.is/shared/form-fields' +import DescriptionText from '../DescriptionText/DescriptionText' +import withLogo from '../Logo/Logo' + +const IncomeForm = ({ field, errors, application }: FAFieldBaseProps) => { + const { formatMessage } = useIntl() + const { answers } = application + + return ( + <> + + + + + {formatMessage(incomeForm.bulletList.headline)} + + + + + + + + + + + + + ) +} + +export default withLogo(IncomeForm) diff --git a/libs/application/templates/financial-aid/src/fields/Logo/Logo.css.ts b/libs/application/templates/financial-aid/src/fields/Logo/Logo.css.ts new file mode 100644 index 000000000000..51dfd173160b --- /dev/null +++ b/libs/application/templates/financial-aid/src/fields/Logo/Logo.css.ts @@ -0,0 +1,14 @@ +import { style } from '@vanilla-extract/css' +import { theme } from '@island.is/island-ui/theme' + +export const logo = style({ + position: 'relative', + left: '16px', + width: '180px', + '@media': { + [`screen and (min-width: ${theme.breakpoints.md}px)`]: { + width: '250px', + left: '46px', + }, + }, +}) diff --git a/libs/application/templates/financial-aid/src/fields/Logo/Logo.tsx b/libs/application/templates/financial-aid/src/fields/Logo/Logo.tsx new file mode 100644 index 000000000000..40b3eff8ca12 --- /dev/null +++ b/libs/application/templates/financial-aid/src/fields/Logo/Logo.tsx @@ -0,0 +1,46 @@ +import React, { useEffect, useState } from 'react' + +import { logoKeyFromMunicipalityCode } from '@island.is/financial-aid/shared/lib' +import { Box } from '@island.is/island-ui/core' + +import { FAFieldBaseProps } from '../../lib/types' +import * as styles from './Logo.css' + +const withLogo = + (Component: React.ComponentType>) => + (props: FAFieldBaseProps) => { + const [logo, setLogo] = useState() + const municipality = props.application.externalData.municipality.data + + useEffect(() => { + const getLogo = async () => { + const svgLogo = await import( + `../../svg/${ + logoKeyFromMunicipalityCode[ + municipality ? municipality.municipalityId : '' + ] + }` + ) + setLogo(svgLogo.default) + } + getLogo() + }, []) + + return ( + <> + + + + + + + + ) + } + +export default withLogo diff --git a/libs/application/templates/financial-aid/src/fields/Summary/FileList.css.ts b/libs/application/templates/financial-aid/src/fields/MissingFiles/FileList/FileList.css.ts similarity index 97% rename from libs/application/templates/financial-aid/src/fields/Summary/FileList.css.ts rename to libs/application/templates/financial-aid/src/fields/MissingFiles/FileList/FileList.css.ts index 56c30e8791d5..2af298541a87 100644 --- a/libs/application/templates/financial-aid/src/fields/Summary/FileList.css.ts +++ b/libs/application/templates/financial-aid/src/fields/MissingFiles/FileList/FileList.css.ts @@ -40,5 +40,5 @@ export const type = style({ export const name = style({ gridColumn: 'span 2', - display: 'flex', + overflow: 'hidden', }) diff --git a/libs/application/templates/financial-aid/src/fields/Summary/FileList.tsx b/libs/application/templates/financial-aid/src/fields/MissingFiles/FileList/FileList.tsx similarity index 93% rename from libs/application/templates/financial-aid/src/fields/Summary/FileList.tsx rename to libs/application/templates/financial-aid/src/fields/MissingFiles/FileList/FileList.tsx index 5a637f8a87d6..1d1542ad16cc 100644 --- a/libs/application/templates/financial-aid/src/fields/Summary/FileList.tsx +++ b/libs/application/templates/financial-aid/src/fields/MissingFiles/FileList/FileList.tsx @@ -1,14 +1,16 @@ import React from 'react' import { useIntl } from 'react-intl' import cn from 'classnames' + import { Text, Box, UploadFile } from '@island.is/island-ui/core' import { getFileSizeInKilo, getFileType, } from '@island.is/financial-aid/shared/lib' -import { useFileUpload } from '../../lib/hooks/useFileUpload' -import { missingFiles } from '../../lib/messages' + import * as styles from './FileList.css' +import { missingFiles } from '../../../lib/messages' +import { useFileUpload } from '../../../lib/hooks/useFileUpload' interface Props { applicationSystemId: string @@ -57,4 +59,5 @@ const FileList = ({ files, applicationSystemId }: Props) => {
) } + export default FileList diff --git a/libs/application/templates/financial-aid/src/fields/missingFiles/MissingFiles.tsx b/libs/application/templates/financial-aid/src/fields/MissingFiles/MissingFiles.tsx similarity index 85% rename from libs/application/templates/financial-aid/src/fields/missingFiles/MissingFiles.tsx rename to libs/application/templates/financial-aid/src/fields/MissingFiles/MissingFiles.tsx index 4b0edab8d80d..ec79d36afd5f 100644 --- a/libs/application/templates/financial-aid/src/fields/missingFiles/MissingFiles.tsx +++ b/libs/application/templates/financial-aid/src/fields/MissingFiles/MissingFiles.tsx @@ -1,5 +1,6 @@ import React, { useEffect, useMemo, useState } from 'react' import { useIntl } from 'react-intl' + import { Text, Box, @@ -14,34 +15,29 @@ import { getCommentFromLatestEvent, } from '@island.is/financial-aid/shared/lib' import { getValueViaPath } from '@island.is/application/core' -import { FieldBaseProps, RecordObject } from '@island.is/application/types' +import { RecordObject } from '@island.is/application/types' + import { filesText, missingFiles } from '../../lib/messages' -import { UploadFileType } from '../../lib/types' +import { Files } from '..' +import { FAFieldBaseProps, UploadFileType } from '../../lib/types' import useApplication from '../../lib/hooks/useApplication' import { Controller, useFormContext } from 'react-hook-form' import { useFileUpload } from '../../lib/hooks/useFileUpload' -import Files from '../files/Files' -import DescriptionText from '../../components/DescriptionText/DescriptionText' +import DescriptionText from '../DescriptionText/DescriptionText' -export const MissingFiles = ({ +const MissingFiles = ({ application, setBeforeSubmitCallback, field, -}: FieldBaseProps) => { - const currentApplicationId = getValueViaPath( - application.externalData, - 'currentApplication.data.currentApplicationId', - ) - const email = getValueViaPath( - application.externalData, - 'municipality.data.email', +}: FAFieldBaseProps) => { + const { currentApplication, updateApplication, loading } = useApplication( + application.externalData.currentApplication.data?.currentApplicationId, ) - const { currentApplication, updateApplication, loading } = - useApplication(currentApplicationId) const isSpouse = getValueViaPath(field as RecordObject, 'props.isSpouse') const { formatMessage } = useIntl() const { setValue, getValues } = useFormContext() + const fileType: UploadFileType = 'otherFiles' const commentType = 'fileUploadComment' const files = getValues(fileType) @@ -75,12 +71,15 @@ export const MissingFiles = ({ } try { - if (!currentApplicationId) { + if ( + !application.externalData.currentApplication.data + ?.currentApplicationId + ) { throw new Error() } const uploadedFiles = await uploadFiles( - currentApplicationId, + application.externalData.currentApplication.data.currentApplicationId, FileType.OTHER, files, ) @@ -171,7 +170,7 @@ export const MissingFiles = ({ @@ -179,3 +178,5 @@ export const MissingFiles = ({ ) } + +export default MissingFiles diff --git a/libs/application/templates/financial-aid/src/fields/Summary/MissingFilesConfirmation.tsx b/libs/application/templates/financial-aid/src/fields/MissingFiles/MissingFilesConfirmation/MissingFilesConfirmation.tsx similarity index 50% rename from libs/application/templates/financial-aid/src/fields/Summary/MissingFilesConfirmation.tsx rename to libs/application/templates/financial-aid/src/fields/MissingFiles/MissingFilesConfirmation/MissingFilesConfirmation.tsx index ac86730f9817..254994aa77bf 100644 --- a/libs/application/templates/financial-aid/src/fields/Summary/MissingFilesConfirmation.tsx +++ b/libs/application/templates/financial-aid/src/fields/MissingFiles/MissingFilesConfirmation/MissingFilesConfirmation.tsx @@ -1,32 +1,26 @@ import React from 'react' import { useIntl } from 'react-intl' import { useFormContext } from 'react-hook-form' -import { Text, Box } from '@island.is/island-ui/core' -import { UploadFileType } from '../../lib/types' -import { missingFiles } from '../../lib/messages' -import FileList from './FileList' -import { RecordObject } from '@island.is/shared/types' -import { getValueViaPath } from '@island.is/application/core' -import { FieldBaseProps } from '@island.is/application/types' - -export const MissingFilesConfirmation = ({ - application, - field, -}: FieldBaseProps) => { + +import { Text, Box, Button } from '@island.is/island-ui/core' + +import { FAFieldBaseProps, UploadFileType } from '../../../lib/types' +import { missingFiles } from '../../../lib/messages' +import FileList from '../FileList/FileList' + +const MissingFilesConfirmation = ({ application }: FAFieldBaseProps) => { const { formatMessage } = useIntl() const { getValues } = useFormContext() - const isSpouse = getValueViaPath( - field as RecordObject, - 'props.isSpouse', - ) - const fileType: UploadFileType = isSpouse - ? 'missingFilesSpouse' - : 'missingFiles' + const fileType: UploadFileType = 'otherFiles' const commentType = 'fileUploadComment' return ( <> + + {formatMessage(missingFiles.confirmation.subtitle)} + + {getValues(commentType)} )} + + + + ) } + +export default MissingFilesConfirmation diff --git a/libs/application/templates/financial-aid/src/fields/PersonalTaxCreditForm/PersonalTaxCreditForm.tsx b/libs/application/templates/financial-aid/src/fields/PersonalTaxCreditForm/PersonalTaxCreditForm.tsx new file mode 100644 index 000000000000..9db94fdb817f --- /dev/null +++ b/libs/application/templates/financial-aid/src/fields/PersonalTaxCreditForm/PersonalTaxCreditForm.tsx @@ -0,0 +1,56 @@ +import React from 'react' +import { Box, Text } from '@island.is/island-ui/core' +import { FAFieldBaseProps, ApproveOptions } from '../../lib/types' +import { useIntl } from 'react-intl' +import { personalTaxCreditForm } from '../../lib/messages' +import { RadioController } from '@island.is/shared/form-fields' +import withLogo from '../Logo/Logo' + +const PersonalTaxCreditForm = ({ + field, + errors, + application, +}: FAFieldBaseProps) => { + const { formatMessage } = useIntl() + const { answers } = application + + return ( + <> + + {formatMessage(personalTaxCreditForm.general.recommendedChoice)} + + + + + + {formatMessage(personalTaxCreditForm.general.descriptionTitle)} + + + {formatMessage(personalTaxCreditForm.general.description)} + + + ) +} + +export default withLogo(PersonalTaxCreditForm) diff --git a/libs/application/templates/financial-aid/src/fields/PrivacyPolicyAccordion/PrivacyPolicyAccordion.tsx b/libs/application/templates/financial-aid/src/fields/PrivacyPolicyAccordion/PrivacyPolicyAccordion.tsx new file mode 100644 index 000000000000..99248972ced8 --- /dev/null +++ b/libs/application/templates/financial-aid/src/fields/PrivacyPolicyAccordion/PrivacyPolicyAccordion.tsx @@ -0,0 +1,41 @@ +import React from 'react' +import { useIntl } from 'react-intl' +import { Accordion, AccordionItem, Text } from '@island.is/island-ui/core' +import { privacyPolicyAccordion } from '../../lib/messages' +import { DescriptionText } from '..' + +interface Props { + municipalityPageUrl?: string +} + +const PrivacyPolicyAccordion = ({ municipalityPageUrl }: Props) => { + const { formatMessage } = useIntl() + + return ( + <> + + {formatMessage(privacyPolicyAccordion.general.sectionTitle)} + + + + + {municipalityPageUrl ? ( + + ) : ( + + )} + + + + ) +} + +export default PrivacyPolicyAccordion diff --git a/libs/application/templates/financial-aid/src/fields/ServiceCenter/ServiceCenter.tsx b/libs/application/templates/financial-aid/src/fields/ServiceCenter/ServiceCenter.tsx new file mode 100644 index 000000000000..cee7dc56fe8c --- /dev/null +++ b/libs/application/templates/financial-aid/src/fields/ServiceCenter/ServiceCenter.tsx @@ -0,0 +1,55 @@ +import React from 'react' +import { useIntl } from 'react-intl' +import { Button, Box } from '@island.is/island-ui/core' + +import { serviceCenters } from '@island.is/financial-aid/shared/data' +import { FAFieldBaseProps } from '../../lib/types' +import { serviceCenter } from '../../lib/messages' +import { DescriptionText } from '..' +import withLogo from '../Logo/Logo' + +const ServiceCenter = ({ application }: FAFieldBaseProps) => { + const { formatMessage } = useIntl() + + const { externalData } = application + + const applicantsCenter = serviceCenters.find( + (serviceCenter) => + serviceCenter.number === + Number(externalData.nationalRegistry.data.address?.municipalityCode), + ) + + return ( + <> + + + + + + + + + {applicantsCenter?.link && ( + + )} + + ) +} + +export default withLogo(ServiceCenter) diff --git a/libs/application/templates/financial-aid/src/fields/Shared.css.ts b/libs/application/templates/financial-aid/src/fields/Shared.css.ts new file mode 100644 index 000000000000..fc9fd02698df --- /dev/null +++ b/libs/application/templates/financial-aid/src/fields/Shared.css.ts @@ -0,0 +1,58 @@ +import { style } from '@vanilla-extract/css' +import { theme } from '@island.is/island-ui/theme' + +export const inputContainer = style({ + maxHeight: '0', + overflow: 'hidden', + transition: 'max-height 300ms ease', +}) + +export const inputAppear = style({ + maxHeight: '300px', +}) + +export const formAppear = style({ + maxHeight: '400px', +}) + +export const filesButtons = style({ + selectors: { + '&:hover': { + cursor: 'pointer', + }, + }, +}) + +export const summaryBlockChild = style({ + minWidth: '50%', + '@media': { + [`screen and (min-width: ${theme.breakpoints.lg}px)`]: { + minWidth: '83%', + }, + }, +}) + +export const confirmationIllustration = style({ + marginTop: theme.spacing[5], + display: 'none', + '@media': { + [`screen and (min-width: ${theme.breakpoints.sm}px)`]: { + display: 'block', + }, + [`screen and (min-width: ${theme.breakpoints.md}px)`]: { + display: 'none', + }, + [`screen and (min-width: ${theme.breakpoints.lg}px)`]: { + display: 'block', + }, + }, +}) + +export const errorMessage = style({ + overflow: 'hidden', + maxHeight: '0', + transition: 'max-height 250ms ease', +}) +export const showErrorMessage = style({ + maxHeight: theme.spacing[5], +}) diff --git a/libs/application/templates/financial-aid/src/components/Status/AidAmount/AidAmount.tsx b/libs/application/templates/financial-aid/src/fields/Status/AidAmount/AidAmount.tsx similarity index 72% rename from libs/application/templates/financial-aid/src/components/Status/AidAmount/AidAmount.tsx rename to libs/application/templates/financial-aid/src/fields/Status/AidAmount/AidAmount.tsx index 59c01e01509f..0d66654d2f0a 100644 --- a/libs/application/templates/financial-aid/src/components/Status/AidAmount/AidAmount.tsx +++ b/libs/application/templates/financial-aid/src/fields/Status/AidAmount/AidAmount.tsx @@ -1,23 +1,23 @@ import React from 'react' import { useIntl } from 'react-intl' + import { Box, Text } from '@island.is/island-ui/core' -import { aidAmount } from '../../../lib/messages' -import { waitingForSpouse } from '../../..' -import Breakdown from '../../../components/Breakdown/Breakdown' -import { Estimation } from '../Estimation/Estimation' -import { Application, DataProviderResult } from '@island.is/application/types' -import { VeitaEstimation } from '../Estimation/VeitaEstimation' import { acceptedAmountBreakDown, Amount, + Application, ApplicationState, - Application as FinancialAidAnswers, } from '@island.is/financial-aid/shared/lib' +import { aidAmount } from '../../../lib/messages' +import { Breakdown } from '../../index' +import { Estimation, VeitaEstimation } from '../index' +import { ExternalData, FAApplication, waitingForSpouse } from '../../..' + interface Props { - application: Application - veitaApplication?: FinancialAidAnswers - municipality: DataProviderResult + application: FAApplication + veitaApplication?: Application + municipality: ExternalData['municipality'] state?: ApplicationState amount?: Amount } diff --git a/libs/application/templates/financial-aid/src/fields/Status/ApplicantStatus.tsx b/libs/application/templates/financial-aid/src/fields/Status/ApplicantStatus.tsx index 269bfbef4971..bfadba2f1fbc 100644 --- a/libs/application/templates/financial-aid/src/fields/Status/ApplicantStatus.tsx +++ b/libs/application/templates/financial-aid/src/fields/Status/ApplicantStatus.tsx @@ -1,28 +1,27 @@ import React from 'react' + import { ApplicationState } from '@island.is/financial-aid/shared/lib' import { Box, LoadingDots } from '@island.is/island-ui/core' + +import { FAFieldBaseProps } from '../../lib/types' import { hasSpouse, waitingForSpouse } from '../../lib/utils' +import { + AidAmount, + ApprovedAlert, + Header, + MissingFilesCard, + MoreActions, + RejectionMessage, + SpouseAlert, + Timeline, +} from './index' import useApplication from '../../lib/hooks/useApplication' -import Header from '../../components/Status/Header/Header' -import SpouseAlert from '../../components/Status/SpouseAlert/SpouseAlert' -import ApprovedAlert from '../../components/Status/ApprovedAlert/ApprovedAlert' -import RejectionMessage from '../../components/Status/RejectionMessage/RejectionMessage' -import MissingFilesCard from '../../components/Status/MissingFilesCard/MissingFilesCard' -import AidAmount from '../../components/Status/AidAmount/AidAmount' -import Timeline from '../../components/Status/Timeline/Timeline' -import MoreActions from '../../components/Status/MoreActions/MoreActions' -import { FieldBaseProps } from '@island.is/application/types' -import { getApplicantStatusConstants } from './util' +import * as styles from './Status.css' -export const ApplicantStatus = ({ - application, - goToScreen, -}: FieldBaseProps) => { - const { answers, externalData } = application - const { currentApplicationId, showCopyUrl, homepage, email, rulesHomepage } = - getApplicantStatusConstants(answers, externalData) - - const { currentApplication, loading } = useApplication(currentApplicationId) +const ApplicantStatus = ({ application, goToScreen }: FAFieldBaseProps) => { + const { currentApplication, loading } = useApplication( + application.externalData.currentApplication.data?.currentApplicationId, + ) const { municipality } = application.externalData const isWaitingForSpouse = waitingForSpouse(application.state) @@ -36,18 +35,22 @@ export const ApplicantStatus = ({ } return ( - +
- {isWaitingForSpouse && } + {isWaitingForSpouse && ( + + )} {state === ApplicationState.APPROVED && ( )} {state === ApplicationState.REJECTED && ( )} {state === ApplicationState.DATANEEDED && ( @@ -68,14 +71,16 @@ export const ApplicantStatus = ({ modified={currentApplication?.modified ?? application.modified} showSpouseStep={ isWaitingForSpouse - ? hasSpouse(answers, externalData) + ? hasSpouse(application.answers, application.externalData) : currentApplication?.spouseNationalId != null } /> ) } + +export default ApplicantStatus diff --git a/libs/application/templates/financial-aid/src/components/Status/ApprovedAlert/ApprovedAlert.tsx b/libs/application/templates/financial-aid/src/fields/Status/ApprovedAlert/ApprovedAlert.tsx similarity index 99% rename from libs/application/templates/financial-aid/src/components/Status/ApprovedAlert/ApprovedAlert.tsx rename to libs/application/templates/financial-aid/src/fields/Status/ApprovedAlert/ApprovedAlert.tsx index 5d84be082daa..09e5ba3dbd76 100644 --- a/libs/application/templates/financial-aid/src/components/Status/ApprovedAlert/ApprovedAlert.tsx +++ b/libs/application/templates/financial-aid/src/fields/Status/ApprovedAlert/ApprovedAlert.tsx @@ -1,10 +1,12 @@ import React, { useMemo } from 'react' import { useIntl } from 'react-intl' + import { ApplicationEvent, ApplicationEventType, } from '@island.is/financial-aid/shared/lib' import { AlertMessage, Box, Text } from '@island.is/island-ui/core' + import { approvedAlert } from '../../../lib/messages' interface Props { diff --git a/libs/application/templates/financial-aid/src/fields/Status/Estimation/Estimation.tsx b/libs/application/templates/financial-aid/src/fields/Status/Estimation/Estimation.tsx new file mode 100644 index 000000000000..01130cfac0d5 --- /dev/null +++ b/libs/application/templates/financial-aid/src/fields/Status/Estimation/Estimation.tsx @@ -0,0 +1,121 @@ +import React, { useMemo } from 'react' +import { useIntl } from 'react-intl' + +import { Text, Box } from '@island.is/island-ui/core' +import { + aidCalculator, + estimatedBreakDown, + showSpouseData, + Application, +} from '@island.is/financial-aid/shared/lib' + +import { Breakdown, DescriptionText } from '../..' +import { ApproveOptions, ExternalData, FAApplication } from '../../../lib/types' +import { aidAmount as aidAmountMessages } from '../../../lib/messages' +import { findFamilyStatus } from '../../..' + +interface EstimationProps { + application: FAApplication + municipality: ExternalData['municipality'] +} + +interface VeitaEstiamtionProps { + application: Application + municipality: ExternalData['municipality'] +} + +export const Estimation = ({ application, municipality }: EstimationProps) => { + const { formatMessage } = useIntl() + + const getAidType = () => { + return !showSpouseData[ + findFamilyStatus(application.answers, application.externalData) + ] + } + + const aidAmount = useMemo(() => { + if (application.answers.homeCircumstances.type && municipality.data) { + return aidCalculator( + application.answers.homeCircumstances.type, + getAidType() + ? municipality.data.individualAid + : municipality.data.cohabitationAid, + ) + } + }, [municipality.data]) + + return ( + <> + + <> + + {formatMessage(aidAmountMessages.title)} + + + + + + + + + ) +} + +export const VeitaEstimation = ({ + application, + municipality, +}: VeitaEstiamtionProps) => { + const { formatMessage } = useIntl() + + const getAidType = () => { + switch (true) { + case application.familyStatus != undefined: + return !showSpouseData[application.familyStatus] + default: + return application.spouseNationalId == null + } + } + + const aidAmount = useMemo(() => { + if (municipality.data && application.homeCircumstances) { + return aidCalculator( + application.homeCircumstances, + getAidType() + ? municipality.data.individualAid + : municipality.data.cohabitationAid, + ) + } + }, [municipality.data]) + + return ( + <> + + <> + + {formatMessage(aidAmountMessages.title)} + + + + + + + + + ) +} diff --git a/libs/application/templates/financial-aid/src/components/Status/Header/Header.tsx b/libs/application/templates/financial-aid/src/fields/Status/Header/Header.tsx similarity index 99% rename from libs/application/templates/financial-aid/src/components/Status/Header/Header.tsx rename to libs/application/templates/financial-aid/src/fields/Status/Header/Header.tsx index 5608da416dc6..861cf274481b 100644 --- a/libs/application/templates/financial-aid/src/components/Status/Header/Header.tsx +++ b/libs/application/templates/financial-aid/src/fields/Status/Header/Header.tsx @@ -1,11 +1,13 @@ import React from 'react' import { useIntl } from 'react-intl' + import { Text } from '@island.is/island-ui/core' import { ApplicationState, getNextPeriod, } from '@island.is/financial-aid/shared/lib' import { useLocale } from '@island.is/localization' + import { getStateMessageAndColor } from '../../../lib/formatters' interface Props { diff --git a/libs/application/templates/financial-aid/src/components/Status/MissingFilesCard/MissingFilesCard.tsx b/libs/application/templates/financial-aid/src/fields/Status/MissingFilesCard/MissingFilesCard.tsx similarity index 99% rename from libs/application/templates/financial-aid/src/components/Status/MissingFilesCard/MissingFilesCard.tsx rename to libs/application/templates/financial-aid/src/fields/Status/MissingFilesCard/MissingFilesCard.tsx index 360608036974..ca643166f01f 100644 --- a/libs/application/templates/financial-aid/src/components/Status/MissingFilesCard/MissingFilesCard.tsx +++ b/libs/application/templates/financial-aid/src/fields/Status/MissingFilesCard/MissingFilesCard.tsx @@ -1,5 +1,6 @@ import React from 'react' import { useIntl } from 'react-intl' + import { Box, ActionCard } from '@island.is/island-ui/core' import { missingFilesCard } from '../../../lib/messages' import { Routes } from '../../../lib/constants' diff --git a/libs/application/templates/financial-aid/src/components/Status/MoreActions/MoreActions.tsx b/libs/application/templates/financial-aid/src/fields/Status/MoreActions/MoreActions.tsx similarity index 93% rename from libs/application/templates/financial-aid/src/components/Status/MoreActions/MoreActions.tsx rename to libs/application/templates/financial-aid/src/fields/Status/MoreActions/MoreActions.tsx index bf0df7cec76d..b19b4be59967 100644 --- a/libs/application/templates/financial-aid/src/components/Status/MoreActions/MoreActions.tsx +++ b/libs/application/templates/financial-aid/src/fields/Status/MoreActions/MoreActions.tsx @@ -1,8 +1,9 @@ import React from 'react' import { useIntl } from 'react-intl' + import { Box, Text } from '@island.is/island-ui/core' +import { DescriptionText } from '../..' import { moreActions } from '../../../lib/messages' -import DescriptionText from '../../../components/DescriptionText/DescriptionText' interface Props { municipalityRulesPage?: string diff --git a/libs/application/templates/financial-aid/src/components/Status/RejectionMessage/RejectionMessage.tsx b/libs/application/templates/financial-aid/src/fields/Status/RejectionMessage/RejectionMessage.tsx similarity index 94% rename from libs/application/templates/financial-aid/src/components/Status/RejectionMessage/RejectionMessage.tsx rename to libs/application/templates/financial-aid/src/fields/Status/RejectionMessage/RejectionMessage.tsx index ef3f301a51df..40d75059c1e3 100644 --- a/libs/application/templates/financial-aid/src/components/Status/RejectionMessage/RejectionMessage.tsx +++ b/libs/application/templates/financial-aid/src/fields/Status/RejectionMessage/RejectionMessage.tsx @@ -1,8 +1,9 @@ import React from 'react' import { useIntl } from 'react-intl' + import { Box, Link, Text } from '@island.is/island-ui/core' import { rejectionMessage } from '../../../lib/messages' -import DescriptionText from '../../../components/DescriptionText/DescriptionText' +import DescriptionText from '../../DescriptionText/DescriptionText' interface Props { rejectionComment?: string diff --git a/libs/application/templates/financial-aid/src/components/Status/SpouseAlert/SpouseAlert.tsx b/libs/application/templates/financial-aid/src/fields/Status/SpouseAlert/SpouseAlert.tsx similarity index 95% rename from libs/application/templates/financial-aid/src/components/Status/SpouseAlert/SpouseAlert.tsx rename to libs/application/templates/financial-aid/src/fields/Status/SpouseAlert/SpouseAlert.tsx index 09dd8c20fb60..d55a3089f9e4 100644 --- a/libs/application/templates/financial-aid/src/components/Status/SpouseAlert/SpouseAlert.tsx +++ b/libs/application/templates/financial-aid/src/fields/Status/SpouseAlert/SpouseAlert.tsx @@ -1,8 +1,9 @@ import React from 'react' import { useIntl } from 'react-intl' + import { AlertMessage, Box } from '@island.is/island-ui/core' import { copyUrl, spouseAlert } from '../../../lib/messages' -import { CopyUrl } from '../../../fields' +import { CopyUrl } from '../..' interface Props { showCopyUrl: boolean diff --git a/libs/application/templates/financial-aid/src/components/Status/SpouseApproved/SpouseApproved.tsx b/libs/application/templates/financial-aid/src/fields/Status/SpouseApproved/SpouseApproved.tsx similarity index 99% rename from libs/application/templates/financial-aid/src/components/Status/SpouseApproved/SpouseApproved.tsx rename to libs/application/templates/financial-aid/src/fields/Status/SpouseApproved/SpouseApproved.tsx index 578ee7aa44e9..22224959ccb9 100644 --- a/libs/application/templates/financial-aid/src/components/Status/SpouseApproved/SpouseApproved.tsx +++ b/libs/application/templates/financial-aid/src/fields/Status/SpouseApproved/SpouseApproved.tsx @@ -1,8 +1,10 @@ import React from 'react' import { useIntl } from 'react-intl' + import { Text } from '@island.is/island-ui/core' import { getNextPeriod } from '@island.is/financial-aid/shared/lib' import { useLocale } from '@island.is/localization' + import { spouseApproved } from '../../../lib/messages' const SpouseApproved = () => { diff --git a/libs/application/templates/financial-aid/src/fields/Status/SpouseStatus.tsx b/libs/application/templates/financial-aid/src/fields/Status/SpouseStatus.tsx index 2ee67b3bd0c7..ce4824047fb1 100644 --- a/libs/application/templates/financial-aid/src/fields/Status/SpouseStatus.tsx +++ b/libs/application/templates/financial-aid/src/fields/Status/SpouseStatus.tsx @@ -1,28 +1,24 @@ import React from 'react' + import { ApplicationState } from '@island.is/financial-aid/shared/lib' import { Box, LoadingDots } from '@island.is/island-ui/core' + +import { FAFieldBaseProps } from '../../lib/types' +import { + Header, + MissingFilesCard, + MoreActions, + SpouseApproved, + Timeline, +} from './index' import useApplication from '../../lib/hooks/useApplication' -import Header from '../../components/Status/Header/Header' -import SpouseApproved from '../../components/Status/SpouseApproved/SpouseApproved' -import MissingFilesCard from '../../components/Status/MissingFilesCard/MissingFilesCard' -import Timeline from '../../components/Status/Timeline/Timeline' -import MoreActions from '../../components/Status/MoreActions/MoreActions' -import { FieldBaseProps } from '@island.is/application/types' -import { getValueViaPath } from '@island.is/application/core' - -export const SpouseStatus = ({ application, goToScreen }: FieldBaseProps) => { - const { externalData } = application - const currentApplicationId = getValueViaPath( - externalData, - 'currentApplication.data.currentApplicationId', - ) - const rulesHomepage = getValueViaPath( - externalData, - 'municipality.data.rulesHomepage', - ) - const email = getValueViaPath(externalData, 'municipality.data.email') +import * as styles from './Status.css' - const { currentApplication, loading } = useApplication(currentApplicationId) +const SpouseStatus = ({ application, goToScreen }: FAFieldBaseProps) => { + const { currentApplication, loading } = useApplication( + application.externalData.currentApplication.data?.currentApplicationId, + ) + const { municipality } = application.externalData const state = currentApplication?.state if (loading) { @@ -30,7 +26,7 @@ export const SpouseStatus = ({ application, goToScreen }: FieldBaseProps) => { } return ( - +
{state === ApplicationState.APPROVED && } @@ -46,9 +42,11 @@ export const SpouseStatus = ({ application, goToScreen }: FieldBaseProps) => { /> ) } + +export default SpouseStatus diff --git a/libs/application/templates/financial-aid/src/fields/Status/Status.css.ts b/libs/application/templates/financial-aid/src/fields/Status/Status.css.ts new file mode 100644 index 000000000000..a839766a3e8b --- /dev/null +++ b/libs/application/templates/financial-aid/src/fields/Status/Status.css.ts @@ -0,0 +1,6 @@ +import { style } from '@vanilla-extract/css' +import { theme } from '@island.is/island-ui/theme' + +export const container = style({ + marginTop: -theme.spacing[4], +}) diff --git a/libs/application/templates/financial-aid/src/components/Status/Timeline/Timeline.css.ts b/libs/application/templates/financial-aid/src/fields/Status/Timeline/Timeline.css.ts similarity index 100% rename from libs/application/templates/financial-aid/src/components/Status/Timeline/Timeline.css.ts rename to libs/application/templates/financial-aid/src/fields/Status/Timeline/Timeline.css.ts diff --git a/libs/application/templates/financial-aid/src/components/Status/Timeline/Timeline.tsx b/libs/application/templates/financial-aid/src/fields/Status/Timeline/Timeline.tsx similarity index 96% rename from libs/application/templates/financial-aid/src/components/Status/Timeline/Timeline.tsx rename to libs/application/templates/financial-aid/src/fields/Status/Timeline/Timeline.tsx index b22b1a4f2011..8613e42576f5 100644 --- a/libs/application/templates/financial-aid/src/components/Status/Timeline/Timeline.tsx +++ b/libs/application/templates/financial-aid/src/fields/Status/Timeline/Timeline.tsx @@ -2,8 +2,10 @@ import React, { useEffect, useState } from 'react' import cn from 'classnames' import { useIntl } from 'react-intl' import findLastIndex from 'lodash/findLastIndex' + import { Text, Box } from '@island.is/island-ui/core' import { ApplicationState } from '@island.is/financial-aid/shared/lib' + import * as styles from './Timeline.css' import { timeline } from '../../../lib/messages' import { timelineSections } from '../../../lib/formatters' @@ -40,7 +42,7 @@ const Timeline = ({ state, modified, created, showSpouseStep }: Props) => { return ( <> - + {formatMessage(timeline.title)} diff --git a/libs/application/templates/financial-aid/src/fields/Status/index.ts b/libs/application/templates/financial-aid/src/fields/Status/index.ts new file mode 100644 index 000000000000..a59a64402502 --- /dev/null +++ b/libs/application/templates/financial-aid/src/fields/Status/index.ts @@ -0,0 +1,10 @@ +export { default as AidAmount } from './AidAmount/AidAmount' +export { Estimation, VeitaEstimation } from './Estimation/Estimation' +export { default as Header } from './Header/Header' +export { default as MissingFilesCard } from './MissingFilesCard/MissingFilesCard' +export { default as MoreActions } from './MoreActions/MoreActions' +export { default as RejectionMessage } from './RejectionMessage/RejectionMessage' +export { default as SpouseAlert } from './SpouseAlert/SpouseAlert' +export { default as SpouseApproved } from './SpouseApproved/SpouseApproved' +export { default as Timeline } from './Timeline/Timeline' +export { default as ApprovedAlert } from './ApprovedAlert/ApprovedAlert' diff --git a/libs/application/templates/financial-aid/src/fields/Status/util.ts b/libs/application/templates/financial-aid/src/fields/Status/util.ts deleted file mode 100644 index 4bc9c7db5367..000000000000 --- a/libs/application/templates/financial-aid/src/fields/Status/util.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { getValueViaPath } from '@island.is/application/core' -import { ExternalData, FormValue } from '@island.is/application/types' - -export const getApplicantStatusConstants = ( - answers: FormValue, - externalData: ExternalData, -) => { - const currentApplicationId = getValueViaPath( - answers, - 'externalData.currentApplication.data.currentApplicationId', - ) - const showCopyUrl = getValueViaPath( - externalData, - 'sendSpouseEmail.data.success', - ) - const homepage = getValueViaPath( - externalData, - 'municipality.data.homepage', - ) - const email = getValueViaPath(externalData, 'municipality.data.email') - const rulesHomepage = getValueViaPath( - externalData, - 'municipality.data.rulesHomepage', - ) - - return { - currentApplicationId, - showCopyUrl, - homepage, - email, - rulesHomepage, - } -} diff --git a/libs/application/templates/financial-aid/src/fields/StudentForm/StudentForm.tsx b/libs/application/templates/financial-aid/src/fields/StudentForm/StudentForm.tsx new file mode 100644 index 000000000000..6921708c46b6 --- /dev/null +++ b/libs/application/templates/financial-aid/src/fields/StudentForm/StudentForm.tsx @@ -0,0 +1,73 @@ +import React from 'react' +import { Box, Text } from '@island.is/island-ui/core' +import { FAFieldBaseProps, ApproveOptions } from '../../lib/types' +import { useIntl } from 'react-intl' +import { studentForm, approveOptions } from '../../lib/messages' +import * as styles from '../Shared.css' +import cn from 'classnames' +import { InputController, RadioController } from '@island.is/shared/form-fields' +import { useFormContext } from 'react-hook-form' +import withLogo from '../Logo/Logo' + +const StudentForm = ({ errors, application }: FAFieldBaseProps) => { + const { formatMessage } = useIntl() + const { answers } = application + + const typeInput = { + id: 'student.isStudent', + error: errors?.student?.isStudent, + } + const customInput = { + id: 'student.custom', + error: errors?.student?.custom, + } + const { clearErrors, getValues } = useFormContext() + + return ( + <> + + + + + { + clearErrors(customInput.id) + }} + /> + + {formatMessage(studentForm.input.example)} + + + + ) +} + +export default withLogo(StudentForm) diff --git a/libs/application/templates/financial-aid/src/components/Summary/ChildrenInfo.tsx b/libs/application/templates/financial-aid/src/fields/Summary/ChildrenInfo.tsx similarity index 96% rename from libs/application/templates/financial-aid/src/components/Summary/ChildrenInfo.tsx rename to libs/application/templates/financial-aid/src/fields/Summary/ChildrenInfo.tsx index 3077fe7f9113..3af0704ebcff 100644 --- a/libs/application/templates/financial-aid/src/components/Summary/ChildrenInfo.tsx +++ b/libs/application/templates/financial-aid/src/fields/Summary/ChildrenInfo.tsx @@ -1,14 +1,18 @@ import React from 'react' import { useIntl } from 'react-intl' + import { Box, GridColumn, GridRow, Text } from '@island.is/island-ui/core' import { summaryForm } from '../../lib/messages' import { formatNationalId } from '@island.is/financial-aid/shared/lib' import SummaryBlock from './SummaryBlock' import { Routes } from '../../lib/constants' -import { ChildrenSchoolInfo } from '../../lib/types' interface Props { - childrenSchoolInfo: Array + childrenSchoolInfo: { + nationalId: string + school: string + fullName: string + }[] goToScreen: ((id: string) => void) | undefined childrenComment?: string } diff --git a/libs/application/templates/financial-aid/src/components/Summary/ContactInfo.tsx b/libs/application/templates/financial-aid/src/fields/Summary/ContactInfo.tsx similarity index 99% rename from libs/application/templates/financial-aid/src/components/Summary/ContactInfo.tsx rename to libs/application/templates/financial-aid/src/fields/Summary/ContactInfo.tsx index 224174bfa1ad..32cb65f44338 100644 --- a/libs/application/templates/financial-aid/src/components/Summary/ContactInfo.tsx +++ b/libs/application/templates/financial-aid/src/fields/Summary/ContactInfo.tsx @@ -1,5 +1,6 @@ import React from 'react' import { useIntl } from 'react-intl' + import { GridColumn, GridRow, Text } from '@island.is/island-ui/core' import { Routes } from '../../lib/constants' import { contactInfo } from '../../lib/messages' diff --git a/libs/application/templates/financial-aid/src/components/Summary/DirectTaxPaymentCell.tsx b/libs/application/templates/financial-aid/src/fields/Summary/DirectTaxPaymentCell.tsx similarity index 100% rename from libs/application/templates/financial-aid/src/components/Summary/DirectTaxPaymentCell.tsx rename to libs/application/templates/financial-aid/src/fields/Summary/DirectTaxPaymentCell.tsx diff --git a/libs/application/templates/financial-aid/src/components/Summary/Files.tsx b/libs/application/templates/financial-aid/src/fields/Summary/Files.tsx similarity index 98% rename from libs/application/templates/financial-aid/src/components/Summary/Files.tsx rename to libs/application/templates/financial-aid/src/fields/Summary/Files.tsx index b63e280fe3e1..9a1eb2b3f457 100644 --- a/libs/application/templates/financial-aid/src/components/Summary/Files.tsx +++ b/libs/application/templates/financial-aid/src/fields/Summary/Files.tsx @@ -5,7 +5,7 @@ import { useMutation } from '@apollo/client' import { UploadFile, Box, Icon, Text } from '@island.is/island-ui/core' import { encodeFilenames } from '../../lib/utils' import { CreateSignedUrlMutation } from '../../lib/hooks/useFileUpload' -import * as styles from './Files.css' +import * as styles from '../Shared.css' import SummaryBlock from './SummaryBlock' import { Routes } from '../../lib/constants' import { summaryForm } from '../../lib/messages' diff --git a/libs/application/templates/financial-aid/src/components/Summary/FormInfo.tsx b/libs/application/templates/financial-aid/src/fields/Summary/FormInfo.tsx similarity index 100% rename from libs/application/templates/financial-aid/src/components/Summary/FormInfo.tsx rename to libs/application/templates/financial-aid/src/fields/Summary/FormInfo.tsx diff --git a/libs/application/templates/financial-aid/src/fields/Summary/SpouseSummaryForm.tsx b/libs/application/templates/financial-aid/src/fields/Summary/SpouseSummaryForm.tsx index 6ea1fefede88..3ece2fc20497 100644 --- a/libs/application/templates/financial-aid/src/fields/Summary/SpouseSummaryForm.tsx +++ b/libs/application/templates/financial-aid/src/fields/Summary/SpouseSummaryForm.tsx @@ -1,52 +1,42 @@ -import { FieldBaseProps } from '@island.is/application/types' -import { Box } from '@island.is/island-ui/core' -import { useUserInfo } from '@island.is/react-spa/bff' import React, { useEffect, useState } from 'react' -import { useFormContext } from 'react-hook-form' -import DescriptionText from '../../components/DescriptionText/DescriptionText' -import DirectTaxPaymentModal from '../../components/DirectTaxPaymentsModal/DirectTaxPaymentModal' -import ContactInfo from '../../components/Summary/ContactInfo' -import DirectTaxPaymentCell from '../../components/Summary/DirectTaxPaymentCell' -import Files from '../../components/Summary/Files' -import FormInfo from '../../components/Summary/FormInfo' -import SummaryComment from '../../components/Summary/SummaryComment' -import UserInfo from '../../components/Summary/UserInfo' +import { Box } from '@island.is/island-ui/core' +import * as m from '../../lib/messages' +import { + ApproveOptions, + FAFieldBaseProps, + SummaryComment as SummaryCommentType, +} from '../../lib/types' import { Routes } from '../../lib/constants' +import { DescriptionText, DirectTaxPaymentsModal } from '../index' import { formatAddress, spouseFormItems } from '../../lib/formatters' -import * as m from '../../lib/messages' -import { SummaryComment as SummaryCommentType } from '../../lib/types' -import { getSpouseSummaryConstants } from './utils' +import { + FormInfo, + SummaryComment, + UserInfo, + ContactInfo, + Files, + DirectTaxPaymentCell, +} from './index' +import withLogo from '../Logo/Logo' +import { useFormContext } from 'react-hook-form' +import { useUserInfo } from '@island.is/react-spa/bff' -export const SpouseSummaryForm = ({ - application, - goToScreen, -}: FieldBaseProps) => { +const SpouseSummaryForm = ({ application, goToScreen }: FAFieldBaseProps) => { const { id, answers, externalData } = application const summaryCommentType = SummaryCommentType.SPOUSEFORMCOMMENT const [isModalOpen, setIsModalOpen] = useState(false) const { setValue } = useFormContext() + + const nationalId = + externalData.nationalRegistrySpouse.data?.nationalId || + answers?.relationshipStatus?.spouseNationalId const userInfo = useUserInfo() useEffect(() => { setValue('spouseName', userInfo?.profile.name) }, []) - const { - nationalId, - data, - taxData, - spouseEmail, - spousePhone, - route, - personalTaxReturn, - directTaxPayments, - fetchDate, - spouseTaxReturnFiles, - spouseFormComment, - spouseIncomeFiles, - } = getSpouseSummaryConstants(answers, externalData) - return ( <> @@ -56,7 +46,7 @@ export const SpouseSummaryForm = ({ @@ -65,40 +55,53 @@ export const SpouseSummaryForm = ({ )} - {directTaxPayments && ( - { setIsModalOpen(isOpen) @@ -108,3 +111,5 @@ export const SpouseSummaryForm = ({ ) } + +export default withLogo(SpouseSummaryForm) diff --git a/libs/application/templates/financial-aid/src/components/Summary/SummaryBlock.tsx b/libs/application/templates/financial-aid/src/fields/Summary/SummaryBlock.tsx similarity index 95% rename from libs/application/templates/financial-aid/src/components/Summary/SummaryBlock.tsx rename to libs/application/templates/financial-aid/src/fields/Summary/SummaryBlock.tsx index dab6c13ce932..4607d415db7e 100644 --- a/libs/application/templates/financial-aid/src/components/Summary/SummaryBlock.tsx +++ b/libs/application/templates/financial-aid/src/fields/Summary/SummaryBlock.tsx @@ -3,7 +3,7 @@ import { useIntl } from 'react-intl' import { Box, Button } from '@island.is/island-ui/core' import { summaryForm } from '../../lib/messages' -import * as styles from './SummaryBlock.css' +import * as styles from '../Shared.css' interface Props { editAction?: () => void diff --git a/libs/application/templates/financial-aid/src/components/Summary/SummaryComment.tsx b/libs/application/templates/financial-aid/src/fields/Summary/SummaryComment.tsx similarity index 100% rename from libs/application/templates/financial-aid/src/components/Summary/SummaryComment.tsx rename to libs/application/templates/financial-aid/src/fields/Summary/SummaryComment.tsx diff --git a/libs/application/templates/financial-aid/src/fields/Summary/SummaryForm.tsx b/libs/application/templates/financial-aid/src/fields/Summary/SummaryForm.tsx index 42338bd7608f..79fc7ad25c72 100644 --- a/libs/application/templates/financial-aid/src/fields/Summary/SummaryForm.tsx +++ b/libs/application/templates/financial-aid/src/fields/Summary/SummaryForm.tsx @@ -1,5 +1,6 @@ import React, { useMemo, useState } from 'react' import { useIntl } from 'react-intl' + import { Text, Box, AlertMessage, UploadFile } from '@island.is/island-ui/core' import { getNextPeriod, @@ -9,68 +10,53 @@ import { ChildrenAid, } from '@island.is/financial-aid/shared/lib' import { useLocale } from '@island.is/localization' + import * as m from '../../lib/messages' import { ApproveOptions, + FAFieldBaseProps, SummaryComment as SummaryCommentType, } from '../../lib/types' import { Routes } from '../../lib/constants' +import { DescriptionText, Breakdown } from '../index' import { formatAddress, formItems } from '../../lib/formatters' +import { + FormInfo, + SummaryComment, + UserInfo, + ContactInfo, + Files, + DirectTaxPaymentCell, +} from './index' + +import { DirectTaxPaymentsModal } from '..' import { findFamilyStatus } from '../../lib/utils' -import DescriptionText from '../../components/DescriptionText/DescriptionText' -import Breakdown from '../../components/Breakdown/Breakdown' -import DirectTaxPaymentModal from '../../components/DirectTaxPaymentsModal/DirectTaxPaymentModal' -import SummaryComment from '../../components/Summary/SummaryComment' -import ChildrenInfo from '../../components/Summary/ChildrenInfo' -import ContactInfo from '../../components/Summary/ContactInfo' -import Files from '../../components/Summary/Files' -import FormInfo from '../../components/Summary/FormInfo' -import DirectTaxPaymentCell from '../../components/Summary/DirectTaxPaymentCell' -import UserInfo from '../../components/Summary/UserInfo' -import { FieldBaseProps } from '@island.is/application/types' -import { AnswersSchema } from '../../lib/dataSchema' -import { getSummaryConstants } from './utils' - -export const SummaryForm = ({ application, goToScreen }: FieldBaseProps) => { +import withLogo from '../Logo/Logo' +import ChildrenInfo from './ChildrenInfo' + +const SummaryForm = ({ application, goToScreen }: FAFieldBaseProps) => { const { formatMessage } = useIntl() const { lang } = useLocale() const { id, answers, externalData } = application - const answersSchema = answers as AnswersSchema const summaryCommentType = SummaryCommentType.FORMCOMMENT const [isModalOpen, setIsModalOpen] = useState(false) - const { - homeCircumstances, - individualAid, - cohabitationAid, - childrenSchoolInfo, - municipalitiesDirectTaxPaymentsSuccess, - municipalitiesDirectTaxPayments, - fetchDate, - personalTaxReturn, - personalTaxCreditType, - childrenCustodyData, - childrenAid, - nationalRegistryData, - } = getSummaryConstants(answers, externalData) - const aidAmount = useMemo(() => { - if (externalData.municipality.data && homeCircumstances) { + if (externalData.municipality.data && answers.homeCircumstances) { return aidCalculator( - homeCircumstances, + answers.homeCircumstances.type, findFamilyStatus(answers, externalData) === FamilyStatus.NOT_COHABITATION - ? individualAid - : cohabitationAid, + ? externalData.municipality.data.individualAid + : externalData.municipality.data.cohabitationAid, ) } }, [externalData.municipality.data]) const showAlertMessageAboutChildrenAid = - childrenCustodyData && - childrenCustodyData?.length > 0 && - childrenAid !== ChildrenAid.NOTDEFINED + externalData.childrenCustodyInformation.data.length > 0 && + externalData.municipality.data?.childrenAid !== ChildrenAid.NOTDEFINED const findFilesRouteFrom = ( childrenFiles: UploadFile[], @@ -111,7 +97,7 @@ export const SummaryForm = ({ application, goToScreen }: FieldBaseProps) => { @@ -119,7 +105,8 @@ export const SummaryForm = ({ application, goToScreen }: FieldBaseProps) => { {showAlertMessageAboutChildrenAid && ( - {childrenAid === ChildrenAid.APPLICANT ? ( + {externalData.municipality.data?.childrenAid === + ChildrenAid.APPLICANT ? ( { - {childrenSchoolInfo && childrenSchoolInfo.length > 0 && ( + {answers?.childrenSchoolInfo && answers.childrenSchoolInfo.length > 0 && ( )} @@ -168,40 +155,46 @@ export const SummaryForm = ({ application, goToScreen }: FieldBaseProps) => { - { setIsModalOpen(isOpen) @@ -210,3 +203,5 @@ export const SummaryForm = ({ application, goToScreen }: FieldBaseProps) => { ) } + +export default withLogo(SummaryForm) diff --git a/libs/application/templates/financial-aid/src/components/Summary/UserInfo.tsx b/libs/application/templates/financial-aid/src/fields/Summary/UserInfo.tsx similarity index 99% rename from libs/application/templates/financial-aid/src/components/Summary/UserInfo.tsx rename to libs/application/templates/financial-aid/src/fields/Summary/UserInfo.tsx index cbf87df7ac64..8a8ccbe28816 100644 --- a/libs/application/templates/financial-aid/src/components/Summary/UserInfo.tsx +++ b/libs/application/templates/financial-aid/src/fields/Summary/UserInfo.tsx @@ -1,5 +1,6 @@ import React from 'react' import { useIntl } from 'react-intl' + import { Box, GridColumn, GridRow, Text } from '@island.is/island-ui/core' import { summaryForm } from '../../lib/messages' import { formatNationalId } from '@island.is/financial-aid/shared/lib' diff --git a/libs/application/templates/financial-aid/src/fields/Summary/index.ts b/libs/application/templates/financial-aid/src/fields/Summary/index.ts new file mode 100644 index 000000000000..201c26ccd8e9 --- /dev/null +++ b/libs/application/templates/financial-aid/src/fields/Summary/index.ts @@ -0,0 +1,6 @@ +export { default as ContactInfo } from './ContactInfo' +export { default as Files } from './Files' +export { default as SummaryComment } from './SummaryComment' +export { default as UserInfo } from './UserInfo' +export { default as FormInfo } from './FormInfo' +export { default as DirectTaxPaymentCell } from './DirectTaxPaymentCell' diff --git a/libs/application/templates/financial-aid/src/fields/Summary/utils.ts b/libs/application/templates/financial-aid/src/fields/Summary/utils.ts deleted file mode 100644 index c26ff83f2549..000000000000 --- a/libs/application/templates/financial-aid/src/fields/Summary/utils.ts +++ /dev/null @@ -1,179 +0,0 @@ -import { getValueViaPath } from '@island.is/application/core' -import { - ApplicantChildCustodyInformation, - ExternalData, - FormValue, - NationalRegistryIndividual, -} from '@island.is/application/types' -import { ApproveOptions, ChildrenSchoolInfo, TaxData } from '../../lib/types' -import { Routes } from '../../lib/constants' -import { - Aid, - ChildrenAid, - DirectTaxPayment, - HomeCircumstances, - PersonalTaxReturn, -} from '@island.is/financial-aid/shared/lib' -import { UploadFile } from '@island.is/island-ui/core' - -export const getSpouseSummaryConstants = ( - answers: FormValue, - externalData: ExternalData, -) => { - const nationalId = - getValueViaPath( - externalData, - 'nationalRegistrySpouse.data.nationalId', - ) || getValueViaPath(answers, 'relationshipStatus.spouseNationalId') - - const data = getValueViaPath(externalData, 'nationalRegistrySpouse.data') as - | NationalRegistryIndividual - | undefined - - const taxData = getValueViaPath(externalData, 'taxDataSpouse.data') - - const spouseEmail = getValueViaPath( - answers, - 'spouseContactInfo.email', - ) - - const spousePhone = getValueViaPath( - answers, - 'spouseContactInfo.phone', - ) - - const spouseIncomeType = getValueViaPath( - answers, - 'spouseIncome.type', - ) - - const route = - spouseIncomeType === ApproveOptions.Yes - ? Routes.SPOUSEINCOMEFILES - : Routes.SPOUSETAXRETURNFILES - - const personalTaxReturn = getValueViaPath( - externalData, - 'taxDataSpouse.data.municipalitiesPersonalTaxReturn.personalTaxReturn', - ) - - const directTaxPayments = getValueViaPath>( - externalData, - 'taxDataSpouse.data.municipalitiesDirectTaxPayments.directTaxPayments', - ) - - const fetchDate = getValueViaPath( - externalData, - 'nationalRegistry?.date', - ) - - const spouseTaxReturnFiles = getValueViaPath>( - answers, - 'spouseTaxReturnFiles', - ) - - const spouseFormComment = getValueViaPath( - answers, - 'spouseFormComment', - ) - - const spouseIncomeFiles = getValueViaPath>( - answers, - 'spouseIncomeFiles', - ) - - return { - nationalId, - data, - taxData, - spouseEmail, - spousePhone, - route, - personalTaxReturn, - directTaxPayments, - fetchDate, - spouseTaxReturnFiles, - spouseFormComment, - spouseIncomeFiles, - } -} - -export const getSummaryConstants = ( - answers: FormValue, - externalData: ExternalData, -) => { - const homeCircumstances = getValueViaPath( - answers, - 'homeCircumstances.type', - ) - - const individualAid = getValueViaPath( - externalData, - 'municipality.data.individualAid', - ) - - const cohabitationAid = getValueViaPath( - externalData, - 'municipality.data.cohabitationAid', - ) - const childrenSchoolInfo = getValueViaPath>( - answers, - 'childrenSchoolInfo', - ) - - const municipalitiesDirectTaxPaymentsSuccess = getValueViaPath( - externalData, - 'taxData.data.municipalitiesDirectTaxPayments.success', - ) - - const municipalitiesDirectTaxPayments = getValueViaPath< - Array - >( - externalData, - 'taxData.data.municipalitiesDirectTaxPayments.directTaxPayments', - ) - - const fetchDate = getValueViaPath( - externalData, - 'nationalRegistry.date', - ) - - const personalTaxReturn = getValueViaPath( - externalData, - 'taxData.data.municipalitiesPersonalTaxReturn.personalTaxReturn', - ) - - const personalTaxCreditType = getValueViaPath( - answers, - 'personalTaxCredit.type', - ) - - const childrenCustodyData = getValueViaPath< - Array - >(externalData, 'childrenCustodyInformation.data') - - const childrenAid = getValueViaPath( - externalData, - 'municipality.data.childrenAid', - ) - - const nationalRegistryData = getValueViaPath( - externalData, - 'nationalRegistry.data', - ) - - return { - homeCircumstances, - individualAid, - cohabitationAid, - childrenSchoolInfo, - municipalitiesDirectTaxPaymentsSuccess, - municipalitiesDirectTaxPayments, - fetchDate, - personalTaxReturn, - personalTaxCreditType, - childrenCustodyData, - childrenAid, - nationalRegistryData, - } -} diff --git a/libs/application/templates/financial-aid/src/components/TaxBreakdown/TaxBreakdown.css.ts b/libs/application/templates/financial-aid/src/fields/TaxBreakdown/TaxBreakdown.css.ts similarity index 100% rename from libs/application/templates/financial-aid/src/components/TaxBreakdown/TaxBreakdown.css.ts rename to libs/application/templates/financial-aid/src/fields/TaxBreakdown/TaxBreakdown.css.ts diff --git a/libs/application/templates/financial-aid/src/components/TaxBreakdown/TaxBreakdown.tsx b/libs/application/templates/financial-aid/src/fields/TaxBreakdown/TaxBreakdown.tsx similarity index 99% rename from libs/application/templates/financial-aid/src/components/TaxBreakdown/TaxBreakdown.tsx rename to libs/application/templates/financial-aid/src/fields/TaxBreakdown/TaxBreakdown.tsx index 0dc17d19d3b0..6b1047914bb1 100644 --- a/libs/application/templates/financial-aid/src/components/TaxBreakdown/TaxBreakdown.tsx +++ b/libs/application/templates/financial-aid/src/fields/TaxBreakdown/TaxBreakdown.tsx @@ -9,6 +9,7 @@ import { import TaxBreakdownHeadline from './TaxBreakdownHeadline' import groupBy from 'lodash/groupBy' import { directTaxPaymentModal } from '../../lib/messages' + import * as styles from './TaxBreakdown.css' import { useIntl } from 'react-intl' import { useLocale } from '@island.is/localization' diff --git a/libs/application/templates/financial-aid/src/components/TaxBreakdown/TaxBreakdownHeadline.tsx b/libs/application/templates/financial-aid/src/fields/TaxBreakdown/TaxBreakdownHeadline.tsx similarity index 100% rename from libs/application/templates/financial-aid/src/components/TaxBreakdown/TaxBreakdownHeadline.tsx rename to libs/application/templates/financial-aid/src/fields/TaxBreakdown/TaxBreakdownHeadline.tsx diff --git a/libs/application/templates/financial-aid/src/components/TaxBreakdown/TaxBreakdownItem.tsx b/libs/application/templates/financial-aid/src/fields/TaxBreakdown/TaxBreakdownItem.tsx similarity index 100% rename from libs/application/templates/financial-aid/src/components/TaxBreakdown/TaxBreakdownItem.tsx rename to libs/application/templates/financial-aid/src/fields/TaxBreakdown/TaxBreakdownItem.tsx diff --git a/libs/application/templates/financial-aid/src/fields/taxReturnFilesForm/TaxReturnFilesForm.tsx b/libs/application/templates/financial-aid/src/fields/TaxReturnFilesForm/TaxReturnFilesForm.tsx similarity index 59% rename from libs/application/templates/financial-aid/src/fields/taxReturnFilesForm/TaxReturnFilesForm.tsx rename to libs/application/templates/financial-aid/src/fields/TaxReturnFilesForm/TaxReturnFilesForm.tsx index 71480d5c5aa0..a746a9accf6c 100644 --- a/libs/application/templates/financial-aid/src/fields/taxReturnFilesForm/TaxReturnFilesForm.tsx +++ b/libs/application/templates/financial-aid/src/fields/TaxReturnFilesForm/TaxReturnFilesForm.tsx @@ -2,36 +2,22 @@ import React from 'react' import { useIntl } from 'react-intl' import { UploadFile, Box, AlertMessage } from '@island.is/island-ui/core' import { taxReturnForm } from '../../lib/messages' -import { TaxData, UploadFileType } from '../..' -import { FieldBaseProps } from '@island.is/application/types' -import Files from '../files/Files' -import { getValueViaPath } from '@island.is/application/core' -import { getTaxFormContent } from './TaxFormContent' -export const TaxReturnFilesForm = ({ field, application }: FieldBaseProps) => { - const { formatMessage } = useIntl() - const { id, answers, externalData, assignees } = application - const nationalId = getValueViaPath( - externalData, - 'nationalRegistry.data.nationalId', - ) - const taxData = getValueViaPath(externalData, 'taxData.data') - const spouseTaxData = getValueViaPath( - externalData, - 'taxDataSpouse.data', - ) +import { FAFieldBaseProps, OverrideAnswerSchema, UploadFileType } from '../..' - const taxDataToUse = - assignees.includes(nationalId ?? '') && spouseTaxData - ? spouseTaxData - : taxData +import { Files } from '..' +import { getTaxFormContent } from './taxFormContent' +import withLogo from '../Logo/Logo' - if (!taxDataToUse) { - return null - } +const TaxReturnFilesForm = ({ field, application }: FAFieldBaseProps) => { + const { formatMessage } = useIntl() + const { id, answers, externalData, assignees } = application const { municipalitiesDirectTaxPayments, municipalitiesPersonalTaxReturn } = - taxDataToUse + assignees.includes(externalData.nationalRegistry.data.nationalId) && + externalData?.taxDataSpouse?.data + ? externalData.taxDataSpouse.data + : externalData.taxData.data const taxReturnFetchFailed = municipalitiesPersonalTaxReturn?.personalTaxReturn === null @@ -62,7 +48,9 @@ export const TaxReturnFilesForm = ({ field, application }: FieldBaseProps) => { @@ -70,3 +58,5 @@ export const TaxReturnFilesForm = ({ field, application }: FieldBaseProps) => { ) } + +export default withLogo(TaxReturnFilesForm) diff --git a/libs/application/templates/financial-aid/src/fields/taxReturnFilesForm/TaxFormContent.tsx b/libs/application/templates/financial-aid/src/fields/TaxReturnFilesForm/taxFormContent.tsx similarity index 96% rename from libs/application/templates/financial-aid/src/fields/taxReturnFilesForm/TaxFormContent.tsx rename to libs/application/templates/financial-aid/src/fields/TaxReturnFilesForm/taxFormContent.tsx index b24399cae3e2..895d732d6c4c 100644 --- a/libs/application/templates/financial-aid/src/fields/taxReturnFilesForm/TaxFormContent.tsx +++ b/libs/application/templates/financial-aid/src/fields/TaxReturnFilesForm/taxFormContent.tsx @@ -2,7 +2,7 @@ import React from 'react' import { useIntl } from 'react-intl' import { Text, Box } from '@island.is/island-ui/core' import { taxReturnForm } from '../../lib/messages' -import DescriptionText from '../../components/DescriptionText/DescriptionText' +import { DescriptionText } from '..' const DirectTaxPaymentsInfo = () => { const { formatMessage } = useIntl() diff --git a/libs/application/templates/financial-aid/src/fields/UnknownRelationshipForm/UnknownRelationshipForm.tsx b/libs/application/templates/financial-aid/src/fields/UnknownRelationshipForm/UnknownRelationshipForm.tsx new file mode 100644 index 000000000000..46f301a80637 --- /dev/null +++ b/libs/application/templates/financial-aid/src/fields/UnknownRelationshipForm/UnknownRelationshipForm.tsx @@ -0,0 +1,158 @@ +import React from 'react' +import { Box, Text } from '@island.is/island-ui/core' +import { ApproveOptions, ErrorSchema, FAFieldBaseProps } from '../../lib/types' +import { useIntl } from 'react-intl' +import { unknownRelationship, error } from '../../lib/messages' +import DescriptionText from '../DescriptionText/DescriptionText' +import { useFormContext } from 'react-hook-form' +import { + RadioController, + InputController, + CheckboxController, +} from '@island.is/shared/form-fields' + +import * as styles from '../Shared.css' +import cn from 'classnames' +import { isValidEmail, isValidNationalId } from '../../lib/utils' +import withLogo from '../Logo/Logo' + +type validationType = 'email' | 'nationalId' | 'approveItems' +const errorIdForSpouse = 'relationshipStatus' + +const ValidationCheck = (errors: ErrorSchema, type: validationType) => { + const { formatMessage } = useIntl() + const { getValues } = useFormContext() + switch (type) { + case 'email': + return errors.relationshipStatus !== undefined && + !isValidEmail(getValues(`${errorIdForSpouse}.spouseEmail`)) + ? formatMessage(error.validation.email) + : undefined + case 'nationalId': + return errors.relationshipStatus !== undefined && + !isValidNationalId(getValues(`${errorIdForSpouse}.spouseNationalId`)) + ? formatMessage(error.validation.nationalId) + : undefined + case 'approveItems': + return errors.relationshipStatus !== undefined && + getValues(`${errorIdForSpouse}.spouseApproveTerms`)?.length !== 1 + ? formatMessage(error.validation.approveSpouse) + : undefined + } +} + +const UnknownRelationshipForm = ({ errors, application }: FAFieldBaseProps) => { + const { formatMessage } = useIntl() + const { answers } = application + const { clearErrors, getValues } = useFormContext() + + const typeInput = { + id: 'relationshipStatus.unregisteredCohabitation', + error: errors?.relationshipStatus?.unregisteredCohabitation, + } + + const spouseEmail = { + id: 'relationshipStatus.spouseEmail', + error: ValidationCheck(errors, 'email'), + } + const spouseNationalId = { + id: 'relationshipStatus.spouseNationalId', + error: ValidationCheck(errors, 'nationalId'), + } + + const spouseApproveTerms = { + id: 'relationshipStatus.spouseApproveTerms', + error: ValidationCheck(errors, 'approveItems'), + } + + return ( + <> + + {formatMessage(unknownRelationship.general.intro)} + + + + + + {formatMessage(unknownRelationship.form.title)} + + + + + + + + { + clearErrors(errorIdForSpouse) + }} + /> + + + { + clearErrors(errorIdForSpouse) + }} + /> + + + + + ) +} + +export default withLogo(UnknownRelationshipForm) diff --git a/libs/application/templates/financial-aid/src/fields/fileUploadController/FileUploadController.css.ts b/libs/application/templates/financial-aid/src/fields/fileUploadController/FileUploadController.css.ts deleted file mode 100644 index f3d55639bad4..000000000000 --- a/libs/application/templates/financial-aid/src/fields/fileUploadController/FileUploadController.css.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { style } from '@vanilla-extract/css' -import { theme } from '@island.is/island-ui/theme' - -export const errorMessage = style({ - overflow: 'hidden', - maxHeight: '0', - transition: 'max-height 250ms ease', -}) -export const showErrorMessage = style({ - maxHeight: theme.spacing[5], -}) diff --git a/libs/application/templates/financial-aid/src/fields/index.ts b/libs/application/templates/financial-aid/src/fields/index.ts index de41cb87c6bf..49efb9a3335f 100644 --- a/libs/application/templates/financial-aid/src/fields/index.ts +++ b/libs/application/templates/financial-aid/src/fields/index.ts @@ -1,11 +1,33 @@ -export { ChildrenForm } from './ChildrenForm/ChildrenForm' -export { CopyUrl } from './CopyUrl/CopyUrl' -export { SummaryForm } from './Summary/SummaryForm' -export { SpouseSummaryForm } from './Summary/SpouseSummaryForm' -export { ApplicantStatus } from './Status/ApplicantStatus' -export { SpouseStatus } from './Status/SpouseStatus' -export { MissingFilesConfirmation } from './Summary/MissingFilesConfirmation' -export { ChildrenFilesForm } from './childrenFilesForm/ChildrenFilesForm' -export { IncomeFilesForm } from './incomeFilesForm/IncomeFilesForm' -export { TaxReturnFilesForm } from './taxReturnFilesForm/TaxReturnFilesForm' -export { MissingFiles } from './missingFiles/MissingFiles' +export { default as InRelationshipForm } from './InRelationshipForm/InRelationshipForm' +export { default as AboutForm } from './AboutForm/AboutForm' +export { default as ChildrenForm } from './ChildrenForm/ChildrenForm' +export { default as ChildrenFilesForm } from './ChildrenFilesForm/ChildrenFilesForm' +export { default as DescriptionText } from './DescriptionText/DescriptionText' +export { default as StudentForm } from './StudentForm/StudentForm' +export { default as HomeCircumstancesForm } from './HomeCircumstancesForm/HomeCircumstancesForm' +export { default as IncomeForm } from './IncomeForm/IncomeForm' +export { default as EmploymentForm } from './EmploymentForm/EmploymentForm' +export { default as BankInfoForm } from './BankInfoForm/BankInfoForm' +export { default as PersonalTaxCreditForm } from './PersonalTaxCreditForm/PersonalTaxCreditForm' +export { default as ApplicantConfirmation } from './Confirmation/ApplicantConfirmation' +export { default as SpouseConfirmation } from './Confirmation/SpouseConfirmation' +export { default as ConfirmationSectionImage } from './ConfirmationSectionImage/ConfirmationSectionImage' +export { default as CopyUrl } from './CopyUrl/CopyUrl' +export { default as ContactInfo } from './ContactInfo/ContactInfo' +export { default as UnknownRelationshipForm } from './UnknownRelationshipForm/UnknownRelationshipForm' +export { default as SummaryForm } from './Summary/SummaryForm' +export { default as SpouseSummaryForm } from './Summary/SpouseSummaryForm' +export { default as IncomeFilesForm } from './IncomeFilesForm/IncomeFilesForm' +export { default as Files } from './Files/Files' +export { default as FileUploadContainer } from './FileUploadContainer/FileUploadContainer' +export { default as TaxReturnFilesForm } from './TaxReturnFilesForm/TaxReturnFilesForm' +export { default as Breakdown } from './Breakdown/Breakdown' +export { default as AboutSpouseForm } from './AboutSpouseForm/AboutSpouseForm' +export { default as PrivacyPolicyAccordion } from './PrivacyPolicyAccordion/PrivacyPolicyAccordion' +export { default as ServiceCenter } from './ServiceCenter/ServiceCenter' +export { default as ApplicantStatus } from './Status/ApplicantStatus' +export { default as SpouseStatus } from './Status/SpouseStatus' +export { default as MissingFiles } from './MissingFiles/MissingFiles' +export { default as MissingFilesConfirmation } from './MissingFiles/MissingFilesConfirmation/MissingFilesConfirmation' +export { default as DirectTaxPaymentsModal } from './DirectTaxPaymentsModal/DirectTaxPaymentModal' +export { default as TaxBreakdown } from './TaxBreakdown/TaxBreakdown' diff --git a/libs/application/templates/financial-aid/src/forms/ApplicantSubmitted.ts b/libs/application/templates/financial-aid/src/forms/ApplicantSubmitted.ts new file mode 100644 index 000000000000..3fc819cb5f4b --- /dev/null +++ b/libs/application/templates/financial-aid/src/forms/ApplicantSubmitted.ts @@ -0,0 +1,64 @@ +import { + buildCustomField, + buildForm, + buildMultiField, + buildSubmitField, +} from '@island.is/application/core' +import { DefaultEvents, Form } from '@island.is/application/types' +import { Routes } from '../lib/constants' + +import * as m from '../lib/messages' + +export const ApplicantSubmitted: Form = buildForm({ + id: 'FinancialAidApplication', + title: m.status.sectionTitle, + children: [ + buildMultiField({ + id: Routes.APPLICANTSTATUS, + title: m.status.pageTitle, + children: [ + buildCustomField({ + id: Routes.APPLICANTSTATUS, + title: m.status.pageTitle, + component: 'ApplicantStatus', + }), + // Empty submit field to hide all buttons in the footer + buildSubmitField({ + id: '', + title: '', + actions: [], + }), + ], + }), + buildMultiField({ + id: Routes.MISSINGFILES, + title: m.missingFiles.general.pageTitle, + children: [ + buildCustomField( + { + id: Routes.MISSINGFILES, + title: m.missingFiles.general.pageTitle, + component: 'MissingFiles', + }, + { isSpouse: false }, + ), + buildSubmitField({ + id: 'missingFilesSubmit', + title: '', + actions: [ + { + event: DefaultEvents.EDIT, + name: m.missingFiles.general.submit, + type: 'primary', + }, + ], + }), + ], + }), + buildCustomField({ + id: Routes.MISSINGFILESCONFIRMATION, + title: m.missingFiles.confirmation.title, + component: 'MissingFilesConfirmation', + }), + ], +}) diff --git a/libs/application/templates/financial-aid/src/forms/ApplicantSubmittedForm/ApplicantStatus.ts b/libs/application/templates/financial-aid/src/forms/ApplicantSubmittedForm/ApplicantStatus.ts deleted file mode 100644 index 69da1fdff3ed..000000000000 --- a/libs/application/templates/financial-aid/src/forms/ApplicantSubmittedForm/ApplicantStatus.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { buildCustomField, buildSection } from '@island.is/application/core' -import { Routes } from '../../lib/constants' -import * as m from '../../lib/messages' - -export const ApplicantStatus = buildSection({ - id: Routes.APPLICANTSTATUS, - title: 'Staða umsóknar', - children: [ - buildCustomField({ - id: Routes.APPLICANTSTATUS, - title: m.status.pageTitle, - component: 'ApplicantStatus', - }), - ], -}) diff --git a/libs/application/templates/financial-aid/src/forms/ApplicantSubmittedForm/MissingFiles.ts b/libs/application/templates/financial-aid/src/forms/ApplicantSubmittedForm/MissingFiles.ts deleted file mode 100644 index cfe796a5429b..000000000000 --- a/libs/application/templates/financial-aid/src/forms/ApplicantSubmittedForm/MissingFiles.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { - buildCustomField, - buildMultiField, - buildSection, - buildSubmitField, -} from '@island.is/application/core' -import { Routes } from '../../lib/constants' -import { DefaultEvents } from '@island.is/application/types' -import * as m from '../../lib/messages' - -export const MissingFiles = buildSection({ - id: Routes.MISSINGFILES, - title: m.missingFiles.general.pageTitle, - children: [ - buildMultiField({ - id: Routes.MISSINGFILES, - title: m.missingFiles.general.pageTitle, - description: m.missingFiles.general.description, - children: [ - buildCustomField( - { - id: Routes.MISSINGFILES, - title: m.missingFiles.general.pageTitle, - component: 'MissingFiles', - }, - { isSpouse: false }, - ), - buildSubmitField({ - id: 'missingFilesSubmit', - title: '', - actions: [ - { - event: DefaultEvents.EDIT, - name: m.missingFiles.general.submit, - type: 'primary', - }, - ], - }), - ], - }), - ], -}) diff --git a/libs/application/templates/financial-aid/src/forms/ApplicantSubmittedForm/MissingFilesConfirmation.ts b/libs/application/templates/financial-aid/src/forms/ApplicantSubmittedForm/MissingFilesConfirmation.ts deleted file mode 100644 index 1e3c4de51fbe..000000000000 --- a/libs/application/templates/financial-aid/src/forms/ApplicantSubmittedForm/MissingFilesConfirmation.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { - buildCustomField, - buildMessageWithLinkButtonField, - buildMultiField, - buildSection, -} from '@island.is/application/core' -import { Routes } from '../../lib/constants' -import * as m from '../../lib/messages' - -export const MissingFilesConfirmation = buildSection({ - id: Routes.MISSINGFILESCONFIRMATION, - title: m.missingFiles.confirmation.sectionTitle, - children: [ - buildMultiField({ - id: Routes.MISSINGFILESCONFIRMATION, - title: m.missingFiles.confirmation.sectionTitle, - description: m.missingFiles.confirmation.subtitle, - children: [ - buildCustomField({ - id: Routes.MISSINGFILESCONFIRMATION, - title: '', - component: 'MissingFilesConfirmation', - }), - buildMessageWithLinkButtonField({ - id: 'goToServicePortal', - title: '', - url: '/minarsidur/umsoknir', - buttonTitle: m.missingFiles.confirmation.openServicePortalButtonTitle, - message: m.missingFiles.confirmation.openServicePortalMessageText, - marginTop: 6, - }), - ], - }), - ], -}) diff --git a/libs/application/templates/financial-aid/src/forms/ApplicantSubmittedForm/index.ts b/libs/application/templates/financial-aid/src/forms/ApplicantSubmittedForm/index.ts deleted file mode 100644 index b1dacf8a8ac3..000000000000 --- a/libs/application/templates/financial-aid/src/forms/ApplicantSubmittedForm/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { buildForm } from '@island.is/application/core' -import { Application, Form } from '@island.is/application/types' -import * as m from '../../lib/messages' -import { createElement } from 'react' -import { Logo } from '../../components/Logo/Logo' -import { ApplicantStatus } from './ApplicantStatus' -import { MissingFiles } from './MissingFiles' -import { MissingFilesConfirmation } from './MissingFilesConfirmation' - -export const ApplicantSubmitted: Form = buildForm({ - id: 'FinancialAidApplication', - title: m.status.sectionTitle, - logo: (application: Application) => { - const logo = createElement(Logo, { application }) - return () => logo - }, - children: [ApplicantStatus, MissingFiles, MissingFilesConfirmation], -}) diff --git a/libs/application/templates/financial-aid/src/forms/Application.ts b/libs/application/templates/financial-aid/src/forms/Application.ts new file mode 100644 index 000000000000..e99e6c4abf1a --- /dev/null +++ b/libs/application/templates/financial-aid/src/forms/Application.ts @@ -0,0 +1,251 @@ +import { + buildCustomField, + buildForm, + buildMultiField, + buildSection, + buildSubmitField, + buildSubSection, + getValueViaPath, +} from '@island.is/application/core' +import { + ApplicantChildCustodyInformation, + DefaultEvents, + Form, + FormModes, +} from '@island.is/application/types' +import { ApproveOptions, ExternalData } from '../lib/types' + +import * as m from '../lib/messages' +import { Routes } from '../lib/constants' + +export const Application: Form = buildForm({ + id: 'FinancialAidApplication', + title: m.application.name, + mode: FormModes.DRAFT, + children: [ + buildSection({ + id: 'personalInterest', + title: m.section.personalInterest, + children: [ + buildSubSection({ + condition: (_, externalData) => + (externalData as unknown as ExternalData).nationalRegistrySpouse + .data != null, + title: m.inRelationship.general.sectionTitle, + id: Routes.INRELATIONSHIP, + children: [ + buildCustomField({ + id: Routes.INRELATIONSHIP, + title: m.inRelationship.general.pageTitle, + component: 'InRelationshipForm', + }), + ], + }), + buildSubSection({ + condition: (_, externalData) => + (externalData as unknown as ExternalData).nationalRegistrySpouse + .data == null, + title: m.unknownRelationship.general.sectionTitle, + id: Routes.UNKNOWNRELATIONSHIP, + children: [ + buildCustomField({ + id: Routes.UNKNOWNRELATIONSHIP, + title: m.unknownRelationship.general.pageTitle, + component: 'UnknownRelationshipForm', + }), + ], + }), + buildSubSection({ + condition: (_, externalData) => { + const childWithInfo = getValueViaPath( + externalData, + 'childrenCustodyInformation.data', + [], + ) as ApplicantChildCustodyInformation[] + + return Boolean(childWithInfo?.length) + }, + id: Routes.CHILDRENSCHOOLINFO, + title: m.childrenForm.general.sectionTitle, + children: [ + buildCustomField({ + id: Routes.CHILDRENSCHOOLINFO, + title: m.childrenForm.general.pageTitle, + component: 'ChildrenForm', + }), + ], + }), + buildSubSection({ + condition: (_, externalData) => { + const childWithInfo = getValueViaPath( + externalData, + 'childrenCustodyInformation.data', + [], + ) as ApplicantChildCustodyInformation[] + + return Boolean(childWithInfo?.length) + }, + id: Routes.CHILDRENFILES, + title: m.childrenFilesForm.general.sectionTitle, + children: [ + buildCustomField({ + id: Routes.CHILDRENFILES, + title: m.childrenFilesForm.general.pageTitle, + component: 'ChildrenFilesForm', + }), + ], + }), + buildSubSection({ + id: Routes.HOMECIRCUMSTANCES, + title: m.homeCircumstancesForm.general.sectionTitle, + children: [ + buildCustomField({ + id: Routes.HOMECIRCUMSTANCES, + title: m.homeCircumstancesForm.general.pageTitle, + component: 'HomeCircumstancesForm', + }), + ], + }), + buildSubSection({ + id: Routes.STUDENT, + title: m.studentForm.general.sectionTitle, + children: [ + buildCustomField({ + id: Routes.STUDENT, + title: m.studentForm.general.pageTitle, + component: 'StudentForm', + }), + ], + }), + buildSubSection({ + id: Routes.EMPLOYMENT, + title: m.employmentForm.general.sectionTitle, + children: [ + buildCustomField({ + id: Routes.EMPLOYMENT, + title: m.employmentForm.general.pageTitle, + component: 'EmploymentForm', + }), + ], + }), + ], + }), + buildSection({ + id: 'finances', + title: m.section.finances, + children: [ + buildSubSection({ + id: Routes.INCOME, + title: m.incomeForm.general.sectionTitle, + children: [ + buildCustomField({ + id: Routes.INCOME, + title: m.incomeForm.general.pageTitle, + component: 'IncomeForm', + }), + ], + }), + buildSubSection({ + condition: (answers) => answers.income === ApproveOptions.Yes, + id: Routes.INCOMEFILES, + title: m.incomeFilesForm.general.sectionTitle, + children: [ + buildCustomField({ + id: Routes.INCOMEFILES, + title: m.incomeFilesForm.general.pageTitle, + component: 'IncomeFilesForm', + }), + ], + }), + buildSubSection({ + condition: (_, externalData) => + (externalData as unknown as ExternalData).taxData?.data + .municipalitiesDirectTaxPayments.success === false || + (externalData as unknown as ExternalData).taxData?.data + ?.municipalitiesPersonalTaxReturn?.personalTaxReturn == null, + id: Routes.TAXRETURNFILES, + title: m.taxReturnForm.general.sectionTitle, + children: [ + buildCustomField({ + id: Routes.TAXRETURNFILES, + title: m.taxReturnForm.general.pageTitle, + component: 'TaxReturnFilesForm', + }), + ], + }), + buildSubSection({ + id: Routes.PERSONALTAXCREDIT, + title: m.personalTaxCreditForm.general.sectionTitle, + children: [ + buildCustomField({ + id: Routes.PERSONALTAXCREDIT, + title: m.personalTaxCreditForm.general.pageTitle, + component: 'PersonalTaxCreditForm', + }), + ], + }), + buildSubSection({ + id: Routes.BANKINFO, + title: m.bankInfoForm.general.sectionTitle, + children: [ + buildCustomField({ + id: Routes.BANKINFO, + title: m.bankInfoForm.general.pageTitle, + component: 'BankInfoForm', + }), + ], + }), + ], + }), + buildSection({ + id: Routes.CONTACTINFO, + title: m.contactInfo.general.sectionTitle, + children: [ + buildCustomField({ + id: Routes.CONTACTINFO, + title: m.contactInfo.general.pageTitle, + component: 'ContactInfo', + }), + ], + }), + buildSection({ + id: Routes.SUMMARY, + title: m.summaryForm.general.sectionTitle, + children: [ + buildMultiField({ + id: Routes.SUMMARY, + title: m.summaryForm.general.pageTitle, + children: [ + buildCustomField({ + id: Routes.SUMMARY, + title: m.summaryForm.general.pageTitle, + component: 'SummaryForm', + }), + buildSubmitField({ + id: 'submitApplication', + title: '', + actions: [ + { + event: DefaultEvents.SUBMIT, + name: m.summaryForm.general.submit, + type: 'primary', + }, + ], + }), + ], + }), + ], + }), + buildSection({ + id: Routes.CONFIRMATION, + title: m.confirmation.general.sectionTitle, + children: [ + buildCustomField({ + id: Routes.CONFIRMATION, + title: m.confirmation.general.pageTitle, + component: 'ApplicantConfirmation', + }), + ], + }), + ], +}) diff --git a/libs/application/templates/financial-aid/src/forms/ApplicationForm/confirmationSection/confirmationMultiField.ts b/libs/application/templates/financial-aid/src/forms/ApplicationForm/confirmationSection/confirmationMultiField.ts deleted file mode 100644 index 452acf7b3bfa..000000000000 --- a/libs/application/templates/financial-aid/src/forms/ApplicationForm/confirmationSection/confirmationMultiField.ts +++ /dev/null @@ -1,112 +0,0 @@ -import { - buildAlertMessageField, - buildCustomField, - buildDescriptionField, - buildImageField, - buildMultiField, - getValueViaPath, -} from '@island.is/application/core' -import { Routes } from '../../../lib/constants' -import * as m from '../../../lib/messages' -import { - getNextStepsDescription, - hasIncomeFiles, - hasSpouse, -} from '../../../lib/utils' -import { Application, FormValue } from '@island.is/application/types' -import { ConfirmationImage } from '../../../assets/ConfirmationImage' -import { getNextPeriod } from '@island.is/financial-aid/shared/lib' - -export const confirmationMultiField = buildMultiField({ - id: Routes.CONFIRMATION, - title: m.confirmation.general.pageTitle, - children: [ - buildAlertMessageField({ - id: 'confirmationAlert-1', - title: m.confirmation.alertMessagesInRelationship.success, - alertType: 'success', - condition: (answers) => { - return !hasIncomeFiles(answers) - }, - }), - buildAlertMessageField({ - id: 'confirmationAlert-2', - title: m.confirmation.alertMessages.dataNeeded, - message: m.confirmation.alertMessages.dataNeededText, - alertType: 'warning', - condition: (answers) => { - return !hasIncomeFiles(answers) - }, - }), - buildAlertMessageField({ - id: 'confirmationAlert-3', - title: m.confirmation.alertMessagesInRelationship.dataNeeded, - message: (application) => { - const spouse = getValueViaPath( - application.externalData, - 'sendSpouseEmail.data.success', - ) - return spouse - ? m.confirmation.alertMessagesInRelationship.dataNeededText - : m.confirmation.alertMessagesInRelationship.dataNeededAlternativeText - }, - alertType: 'warning', - condition: (answers, externalData) => { - return hasSpouse(answers, externalData) - }, - }), - buildDescriptionField({ - id: 'confirmationDescription', - title: m.confirmation.nextSteps.title, - titleVariant: 'h3', - marginBottom: 2, - }), - buildDescriptionField({ - id: 'confirmationDescription', - title: '', - description: (application: Application) => - getNextStepsDescription(application.answers, application.externalData), - }), - buildDescriptionField({ - id: 'confirmationDescriptionBullets', - title: '', - description: () => ({ - ...m.confirmation.nextSteps.content, - values: { nextMonth: getNextPeriod().month }, - }), - marginBottom: 5, - }), - buildCustomField({ - id: 'confirmation', - title: '', - component: 'CopyUrl', - }), - buildDescriptionField({ - id: 'confirmationLinks', - title: m.confirmation.links.title, - titleVariant: 'h3', - description: (application) => { - const url = getValueViaPath( - application.externalData, - 'municipality.data.homepage', - ) - return { - ...m.confirmation.links.content, - values: { - statusPage: - typeof window !== 'undefined' ? window.location.href : '', - homePage: url ?? '', - }, - } - }, - marginTop: 5, - marginBottom: 5, - }), - buildImageField({ - id: 'confirmationImage', - title: '', - image: ConfirmationImage, - marginBottom: 5, - }), - ], -}) diff --git a/libs/application/templates/financial-aid/src/forms/ApplicationForm/confirmationSection/index.ts b/libs/application/templates/financial-aid/src/forms/ApplicationForm/confirmationSection/index.ts deleted file mode 100644 index 451ce25c211d..000000000000 --- a/libs/application/templates/financial-aid/src/forms/ApplicationForm/confirmationSection/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { buildSection } from '@island.is/application/core' -import { Routes } from '../../../lib/constants' -import * as m from '../../../lib/messages' -import { confirmationMultiField } from './confirmationMultiField' - -export const confirmationSection = buildSection({ - id: Routes.CONFIRMATION, - title: m.confirmation.general.pageTitle, - children: [confirmationMultiField], -}) diff --git a/libs/application/templates/financial-aid/src/forms/ApplicationForm/contactInfoSection/contactInfoMultiField.ts b/libs/application/templates/financial-aid/src/forms/ApplicationForm/contactInfoSection/contactInfoMultiField.ts deleted file mode 100644 index c4ec09b70ed8..000000000000 --- a/libs/application/templates/financial-aid/src/forms/ApplicationForm/contactInfoSection/contactInfoMultiField.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { buildMultiField, buildTextField } from '@island.is/application/core' -import { Routes } from '../../../lib/constants' -import * as m from '../../../lib/messages' - -export const contactInfoMultiField = buildMultiField({ - id: Routes.CONTACTINFO, - title: m.contactInfo.general.pageTitle, - description: m.contactInfo.general.description, - children: [ - buildTextField({ - id: `${Routes.CONTACTINFO}.email`, - title: m.contactInfo.emailInput.label, - placeholder: m.contactInfo.emailInput.placeholder, - }), - buildTextField({ - id: `${Routes.CONTACTINFO}.phone`, - title: m.contactInfo.phoneInput.label, - placeholder: '000-0000', - format: '###-####', - }), - ], -}) diff --git a/libs/application/templates/financial-aid/src/forms/ApplicationForm/contactInfoSection/index.ts b/libs/application/templates/financial-aid/src/forms/ApplicationForm/contactInfoSection/index.ts deleted file mode 100644 index 54dee03e5fc9..000000000000 --- a/libs/application/templates/financial-aid/src/forms/ApplicationForm/contactInfoSection/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { buildSection } from '@island.is/application/core' -import { Routes } from '../../../lib/constants' -import * as m from '../../../lib/messages' -import { contactInfoMultiField } from './contactInfoMultiField' - -export const contactInfoSection = buildSection({ - id: Routes.CONTACTINFO, - title: m.contactInfo.general.sectionTitle, - children: [contactInfoMultiField], -}) diff --git a/libs/application/templates/financial-aid/src/forms/ApplicationForm/financesSection/bankInfoSubSection.ts b/libs/application/templates/financial-aid/src/forms/ApplicationForm/financesSection/bankInfoSubSection.ts deleted file mode 100644 index dccf4eedf682..000000000000 --- a/libs/application/templates/financial-aid/src/forms/ApplicationForm/financesSection/bankInfoSubSection.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { - buildSubSection, - buildMultiField, - buildDescriptionField, - buildBankAccountField, -} from '@island.is/application/core' -import { Routes } from '../../../lib/constants' -import * as m from '../../../lib/messages' - -export const bankInfoSubSection = buildSubSection({ - id: Routes.BANKINFO, - title: m.bankInfoForm.general.sectionTitle, - children: [ - buildMultiField({ - id: Routes.BANKINFO, - title: m.bankInfoForm.general.pageTitle, - description: m.bankInfoForm.general.info, - children: [ - buildBankAccountField({ - id: Routes.BANKINFO, - title: '', - }), - buildDescriptionField({ - id: `${Routes.BANKINFO}.description`, - title: m.bankInfoForm.general.descriptionTitle, - titleVariant: 'h3', - marginTop: 3, - description: m.bankInfoForm.general.description, - }), - ], - }), - ], -}) diff --git a/libs/application/templates/financial-aid/src/forms/ApplicationForm/financesSection/incomeFileSubSection.ts b/libs/application/templates/financial-aid/src/forms/ApplicationForm/financesSection/incomeFileSubSection.ts deleted file mode 100644 index 4648c58268e2..000000000000 --- a/libs/application/templates/financial-aid/src/forms/ApplicationForm/financesSection/incomeFileSubSection.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { - buildCustomField, - buildFileUploadField, - buildMultiField, - buildSubSection, - getValueViaPath, -} from '@island.is/application/core' -import { ApproveOptions } from '../../../lib/types' -import { FILE_SIZE_LIMIT, Routes, UPLOAD_ACCEPT } from '../../../lib/constants' -import * as m from '../../../lib/messages' - -export const incomeFilesSubSection = buildSubSection({ - condition: (answers) => { - const income = getValueViaPath(answers, 'income.type') - - return income === ApproveOptions.Yes - }, - id: Routes.INCOMEFILES, - title: m.incomeFilesForm.general.sectionTitle, - children: [ - buildMultiField({ - id: Routes.INCOMEFILES, - title: m.incomeFilesForm.general.pageTitle, - description: m.incomeFilesForm.general.descriptionTaxSuccess, - children: [ - buildCustomField({ - id: Routes.INCOMEFILES, - title: m.incomeFilesForm.general.pageTitle, - component: 'IncomeFilesForm', - }), - ], - }), - ], -}) diff --git a/libs/application/templates/financial-aid/src/forms/ApplicationForm/financesSection/incomeSubSection.ts b/libs/application/templates/financial-aid/src/forms/ApplicationForm/financesSection/incomeSubSection.ts deleted file mode 100644 index c246424e0bb8..000000000000 --- a/libs/application/templates/financial-aid/src/forms/ApplicationForm/financesSection/incomeSubSection.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { - buildDescriptionField, - buildMultiField, - buildRadioField, - buildSubSection, -} from '@island.is/application/core' -import * as m from '../../../lib/messages' -import { Routes } from '../../../lib/constants' -import { getIncomeOptions } from '../../../lib/utils/getIncomeOptions' - -export const incomeSubSection = buildSubSection({ - id: Routes.INCOME, - title: m.incomeForm.general.sectionTitle, - children: [ - buildMultiField({ - id: Routes.INCOME, - title: m.incomeForm.general.pageTitle, - children: [ - buildRadioField({ - id: `${Routes.INCOME}.type`, - title: '', - width: 'half', - options: getIncomeOptions(), - }), - buildDescriptionField({ - id: `${Routes.INCOME}.descriptionLeft`, - title: m.incomeForm.bulletList.headline, - titleVariant: 'h3', - marginTop: 3, - description: m.incomeForm.examplesOfIncome.incomeExampleList, - }), - ], - }), - ], -}) diff --git a/libs/application/templates/financial-aid/src/forms/ApplicationForm/financesSection/index.ts b/libs/application/templates/financial-aid/src/forms/ApplicationForm/financesSection/index.ts deleted file mode 100644 index 805514527265..000000000000 --- a/libs/application/templates/financial-aid/src/forms/ApplicationForm/financesSection/index.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { buildSection } from '@island.is/application/core' -import * as m from '../../../lib/messages' -import { incomeSubSection } from './incomeSubSection' -import { incomeFilesSubSection } from './incomeFileSubSection' -import { taxReturnFilesSubSection } from './taxReturnFilesSubSection' -import { personalTaxCreditSubSection } from './personalTaxCreditSubSection' -import { bankInfoSubSection } from './bankInfoSubSection' -import { Routes } from '../../../lib/constants' - -export const financesSection = buildSection({ - id: Routes.FINANCES, - title: m.section.finances, - children: [ - incomeSubSection, - incomeFilesSubSection, - taxReturnFilesSubSection, - personalTaxCreditSubSection, - bankInfoSubSection, - ], -}) diff --git a/libs/application/templates/financial-aid/src/forms/ApplicationForm/financesSection/personalTaxCreditSubSection.ts b/libs/application/templates/financial-aid/src/forms/ApplicationForm/financesSection/personalTaxCreditSubSection.ts deleted file mode 100644 index 5f52dd95bee7..000000000000 --- a/libs/application/templates/financial-aid/src/forms/ApplicationForm/financesSection/personalTaxCreditSubSection.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { - buildSubSection, - buildMultiField, - buildRadioField, - buildDescriptionField, -} from '@island.is/application/core' - -import { Routes } from '../../../lib/constants' -import * as m from '../../../lib/messages' -import { getPersonalTaxCreditOptions } from '../../../lib/utils/getPersonalTaxCreditOptions' - -export const personalTaxCreditSubSection = buildSubSection({ - id: Routes.PERSONALTAXCREDIT, - title: m.personalTaxCreditForm.general.sectionTitle, - children: [ - buildMultiField({ - id: Routes.PERSONALTAXCREDIT, - title: m.personalTaxCreditForm.general.pageTitle, - description: m.personalTaxCreditForm.general.recommendedChoice, - children: [ - buildRadioField({ - id: `${Routes.PERSONALTAXCREDIT}.type`, - title: '', - width: 'half', - options: getPersonalTaxCreditOptions(), - }), - buildDescriptionField({ - id: `${Routes.PERSONALTAXCREDIT}.description`, - title: m.personalTaxCreditForm.general.descriptionTitle, - titleVariant: 'h3', - marginTop: 3, - description: m.personalTaxCreditForm.general.description, - }), - ], - }), - ], -}) diff --git a/libs/application/templates/financial-aid/src/forms/ApplicationForm/financesSection/taxReturnFilesSubSection.ts b/libs/application/templates/financial-aid/src/forms/ApplicationForm/financesSection/taxReturnFilesSubSection.ts deleted file mode 100644 index b5e179eacb52..000000000000 --- a/libs/application/templates/financial-aid/src/forms/ApplicationForm/financesSection/taxReturnFilesSubSection.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { - buildCustomField, - buildSubSection, - getValueViaPath, -} from '@island.is/application/core' -import { Routes } from '../../../lib/constants' -import * as m from '../../../lib/messages' - -export const taxReturnFilesSubSection = buildSubSection({ - id: Routes.TAXRETURNFILES, - title: m.taxReturnForm.general.sectionTitle, - condition: (_, externalData) => { - const personalTaxSuccess = getValueViaPath( - externalData, - 'taxData.data.municipalitiesDirectTaxPayments.success', - ) - const personalTaxReturn = getValueViaPath( - externalData, - 'taxData.data.municipalitiesPersonalTaxReturn.personalTaxReturn', - ) - return personalTaxSuccess === false || personalTaxReturn == null - }, - children: [ - buildCustomField({ - id: Routes.TAXRETURNFILES, - title: m.taxReturnForm.general.pageTitle, - component: 'TaxReturnFilesForm', - }), - ], -}) diff --git a/libs/application/templates/financial-aid/src/forms/ApplicationForm/index.ts b/libs/application/templates/financial-aid/src/forms/ApplicationForm/index.ts deleted file mode 100644 index d1fe7595bd0e..000000000000 --- a/libs/application/templates/financial-aid/src/forms/ApplicationForm/index.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { buildForm } from '@island.is/application/core' -import * as m from '../../lib/messages' -import { Application, Form, FormModes } from '@island.is/application/types' -import { financesSection } from './financesSection' -import { contactInfoSection } from './contactInfoSection' -import { summarySection } from './summarySection' -import { confirmationSection } from './confirmationSection' -import { Logo } from '../../components/Logo/Logo' -import { createElement } from 'react' -import { personalInterestSection } from './personalInterestSection' - -export const ApplicationForm: Form = buildForm({ - id: 'FinancialAidApplication', - title: m.application.name, - mode: FormModes.DRAFT, - logo: (application: Application) => { - const logo = createElement(Logo, { application }) - return () => logo - }, - children: [ - personalInterestSection, - financesSection, - contactInfoSection, - summarySection, - confirmationSection, - ], -}) diff --git a/libs/application/templates/financial-aid/src/forms/ApplicationForm/personalInterestSection/childrenFilesSubSection.ts b/libs/application/templates/financial-aid/src/forms/ApplicationForm/personalInterestSection/childrenFilesSubSection.ts deleted file mode 100644 index 709c9f211acd..000000000000 --- a/libs/application/templates/financial-aid/src/forms/ApplicationForm/personalInterestSection/childrenFilesSubSection.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { - buildCustomField, - buildMultiField, - buildSubSection, - getValueViaPath, -} from '@island.is/application/core' -import { Routes } from '../../../lib/constants' -import * as m from '../../../lib/messages' -import { ApplicantChildCustodyInformation } from '@island.is/application/types' - -export const childrenFilesSubSection = buildSubSection({ - condition: (_, externalData) => { - const childWithInfo = getValueViaPath< - Array - >(externalData, 'childrenCustodyInformation.data', []) - - return Boolean(childWithInfo?.length) - }, - id: Routes.CHILDRENFILES, - title: m.childrenFilesForm.general.sectionTitle, - children: [ - buildMultiField({ - title: m.childrenFilesForm.general.pageTitle, - description: m.childrenFilesForm.general.description, - children: [ - buildCustomField({ - id: Routes.CHILDRENFILES, - title: m.childrenFilesForm.general.pageTitle, - component: 'ChildrenFilesForm', - }), - ], - }), - ], -}) diff --git a/libs/application/templates/financial-aid/src/forms/ApplicationForm/personalInterestSection/childrenSubSection.ts b/libs/application/templates/financial-aid/src/forms/ApplicationForm/personalInterestSection/childrenSubSection.ts deleted file mode 100644 index 1e4b1a31cffc..000000000000 --- a/libs/application/templates/financial-aid/src/forms/ApplicationForm/personalInterestSection/childrenSubSection.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { - buildCustomField, - buildDescriptionField, - buildMultiField, - buildSubSection, - buildTextField, - getValueViaPath, -} from '@island.is/application/core' -import { Routes } from '../../../lib/constants' -import * as m from '../../../lib/messages' -import { ApplicantChildCustodyInformation } from '@island.is/application/types' -import { childrenForm } from '../../../lib/messages' -import { SummaryComment } from '../../../lib/types' - -export const childrenSubSection = buildSubSection({ - condition: (_, externalData) => { - const childWithInfo = getValueViaPath< - Array - >(externalData, 'childrenCustodyInformation.data', []) - - return Boolean(childWithInfo?.length) - }, - id: Routes.CHILDRENSCHOOLINFO, - title: m.childrenForm.general.sectionTitle, - children: [ - buildMultiField({ - title: m.childrenForm.general.sectionTitle, - description: childrenForm.general.description, - children: [ - buildDescriptionField({ - id: 'childrenSubsectionDescription', - title: '', - description: childrenForm.page.content, - marginBottom: 4, - }), - buildCustomField({ - id: Routes.CHILDRENSCHOOLINFO, - title: m.childrenForm.general.pageTitle, - component: 'ChildrenForm', - }), - buildDescriptionField({ - id: 'childrenCommentDescription', - title: childrenForm.page.commentTitle, - titleVariant: 'h3', - description: childrenForm.page.commentText, - }), - buildTextField({ - id: SummaryComment.CHILDRENCOMMENT, - title: '', - variant: 'textarea', - placeholder: childrenForm.inputs.commentLabel, - rows: 8, - }), - ], - }), - ], -}) diff --git a/libs/application/templates/financial-aid/src/forms/ApplicationForm/personalInterestSection/employmentSubSection.ts b/libs/application/templates/financial-aid/src/forms/ApplicationForm/personalInterestSection/employmentSubSection.ts deleted file mode 100644 index 74d8f2223f04..000000000000 --- a/libs/application/templates/financial-aid/src/forms/ApplicationForm/personalInterestSection/employmentSubSection.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { - buildMultiField, - buildRadioField, - buildSubSection, - buildTextField, -} from '@island.is/application/core' -import { Routes } from '../../../lib/constants' -import * as m from '../../../lib/messages' -import { getEmploymentOptions } from '../../../lib/utils/getEmploymentOptions' -import { FormValue } from '@island.is/application/types' -import { Employment } from '@island.is/financial-aid/shared/lib' - -export const employmentSubSection = buildSubSection({ - id: Routes.EMPLOYMENT, - title: m.employmentForm.general.sectionTitle, - children: [ - buildMultiField({ - id: Routes.EMPLOYMENT, - title: m.employmentForm.general.pageTitle, - children: [ - buildRadioField({ - id: `${Routes.EMPLOYMENT}.type`, - title: '', - options: getEmploymentOptions(), - }), - buildTextField({ - condition: (answers) => - (answers[Routes.EMPLOYMENT] as FormValue)?.type === - Employment.OTHER, - id: `${Routes.EMPLOYMENT}.custom`, - title: m.input.label, - variant: 'textarea', - rows: 6, - required: true, - }), - ], - }), - ], -}) diff --git a/libs/application/templates/financial-aid/src/forms/ApplicationForm/personalInterestSection/homeCircumstancesSubSection.ts b/libs/application/templates/financial-aid/src/forms/ApplicationForm/personalInterestSection/homeCircumstancesSubSection.ts deleted file mode 100644 index ea63e1136aa7..000000000000 --- a/libs/application/templates/financial-aid/src/forms/ApplicationForm/personalInterestSection/homeCircumstancesSubSection.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { - buildMultiField, - buildRadioField, - buildSubSection, - buildTextField, -} from '@island.is/application/core' -import { Routes } from '../../../lib/constants' -import * as m from '../../../lib/messages' - -import { getHomeCircumstancesOptions } from '../../../lib/utils/getHomeCircumstancesOptions' -import { HomeCircumstances } from '@island.is/financial-aid/shared/lib' -import { FormValue } from '@island.is/application/types' - -export const homeCircumstancesSubSection = buildSubSection({ - id: Routes.HOMECIRCUMSTANCES, - title: m.homeCircumstancesForm.general.sectionTitle, - children: [ - buildMultiField({ - id: Routes.HOMECIRCUMSTANCES, - title: m.homeCircumstancesForm.general.pageTitle, - children: [ - buildRadioField({ - id: `${Routes.HOMECIRCUMSTANCES}.type`, - title: '', - options: getHomeCircumstancesOptions(), - required: true, - }), - buildTextField({ - condition: (answers: FormValue) => - (answers[Routes.HOMECIRCUMSTANCES] as FormValue)?.type === - HomeCircumstances.OTHER, - id: `${Routes.HOMECIRCUMSTANCES}.custom`, - title: m.input.label, - variant: 'textarea', - rows: 6, - }), - ], - }), - ], -}) diff --git a/libs/application/templates/financial-aid/src/forms/ApplicationForm/personalInterestSection/inARelationshipSubSection.ts b/libs/application/templates/financial-aid/src/forms/ApplicationForm/personalInterestSection/inARelationshipSubSection.ts deleted file mode 100644 index 07fb93cf609e..000000000000 --- a/libs/application/templates/financial-aid/src/forms/ApplicationForm/personalInterestSection/inARelationshipSubSection.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { - buildCheckboxField, - buildDescriptionField, - buildMultiField, - buildSubSection, - buildTextField, -} from '@island.is/application/core' -import { Routes } from '../../../lib/constants' -import * as m from '../../../lib/messages' - -export const inARelationshipSubSection = buildSubSection({ - condition: (_, externalData) => - externalData.nationalRegistrySpouse.data !== null, - id: Routes.INRELATIONSHIP, - title: m.inRelationship.general.sectionTitle, - children: [ - buildMultiField({ - title: m.inRelationship.general.sectionTitle, - description: m.inRelationship.general.intro, - children: [ - buildDescriptionField({ - id: 'spouse.description', - title: '', - description: m.inRelationship.general.description, - }), - buildTextField({ - id: 'spouse.email', - title: m.inRelationship.inputs.spouseEmail, - variant: 'email', - required: true, - width: 'full', - placeholder: m.inRelationship.inputs.spouseEmailPlaceholder, - }), - buildCheckboxField({ - id: 'spouse.approveTerms', - title: '', - required: true, - defaultValue: [], - options: [ - { - value: 'yes', - label: m.inRelationship.inputs.checkboxLabel, - }, - ], - }), - ], - }), - ], -}) diff --git a/libs/application/templates/financial-aid/src/forms/ApplicationForm/personalInterestSection/index.ts b/libs/application/templates/financial-aid/src/forms/ApplicationForm/personalInterestSection/index.ts deleted file mode 100644 index 5f17d38b3701..000000000000 --- a/libs/application/templates/financial-aid/src/forms/ApplicationForm/personalInterestSection/index.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { buildSection } from '@island.is/application/core' -import * as m from '../../../lib/messages' -import { inARelationshipSubSection } from './inARelationshipSubSection' -import { Routes } from '../../../lib/constants' -import { unknownRelationshipSubSection } from './unknownRelationshipSubSection' -import { childrenSubSection } from './childrenSubSection' -import { childrenFilesSubSection } from './childrenFilesSubSection' -import { homeCircumstancesSubSection } from './homeCircumstancesSubSection' -import { studentSubSection } from './studentSubSection' -import { employmentSubSection } from './employmentSubSection' - -export const personalInterestSection = buildSection({ - id: Routes.PERSONALINTEREST, - title: m.section.personalInterest, - children: [ - inARelationshipSubSection, - unknownRelationshipSubSection, - childrenSubSection, - childrenFilesSubSection, - homeCircumstancesSubSection, - studentSubSection, - employmentSubSection, - ], -}) diff --git a/libs/application/templates/financial-aid/src/forms/ApplicationForm/personalInterestSection/studentSubSection.ts b/libs/application/templates/financial-aid/src/forms/ApplicationForm/personalInterestSection/studentSubSection.ts deleted file mode 100644 index 950c0b44fe25..000000000000 --- a/libs/application/templates/financial-aid/src/forms/ApplicationForm/personalInterestSection/studentSubSection.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { - buildMultiField, - buildRadioField, - buildSubSection, - buildTextField, -} from '@island.is/application/core' -import { Routes } from '../../../lib/constants' -import * as m from '../../../lib/messages' -import { getStudentOptions } from '../../../lib/utils/getStudentOptions' -import { FormValue } from '@island.is/application/types' -import { ApproveOptions } from '../../../lib/types' - -export const studentSubSection = buildSubSection({ - id: Routes.STUDENT, - title: m.studentForm.general.sectionTitle, - children: [ - buildMultiField({ - id: Routes.STUDENT, - title: m.studentForm.general.pageTitle, - children: [ - buildRadioField({ - id: `${Routes.STUDENT}.isStudent`, - title: '', - options: getStudentOptions(), - }), - buildTextField({ - condition: (answers) => - (answers[Routes.STUDENT] as FormValue)?.isStudent === - ApproveOptions.Yes, - id: `${Routes.STUDENT}.custom`, - title: m.studentForm.input.label, - placeholder: m.studentForm.input.example, - required: true, - }), - ], - }), - ], -}) diff --git a/libs/application/templates/financial-aid/src/forms/ApplicationForm/personalInterestSection/unknownRelationshipSubSection.ts b/libs/application/templates/financial-aid/src/forms/ApplicationForm/personalInterestSection/unknownRelationshipSubSection.ts deleted file mode 100644 index 0a68eb415e8f..000000000000 --- a/libs/application/templates/financial-aid/src/forms/ApplicationForm/personalInterestSection/unknownRelationshipSubSection.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { - buildCheckboxField, - buildDescriptionField, - buildMultiField, - buildRadioField, - buildSubSection, - buildTextField, - getValueViaPath, -} from '@island.is/application/core' -import { Routes } from '../../../lib/constants' -import * as m from '../../../lib/messages' -import { getUnknownRelationshipOptions } from '../../../lib/utils/getUnknownRelationshipOptions' -import { FormValue } from '@island.is/application/types' -import { ApproveOptions } from '../../../lib/types' - -export const unknownRelationshipSubSection = buildSubSection({ - condition: (_, externalData) => { - const spouseData = getValueViaPath( - externalData, - 'nationalRegistrySpouse.data', - ) - return spouseData == null - }, - title: m.unknownRelationship.general.sectionTitle, - id: Routes.UNKNOWNRELATIONSHIP, - children: [ - buildMultiField({ - id: Routes.UNKNOWNRELATIONSHIP, - title: m.unknownRelationship.general.pageTitle, - description: m.unknownRelationship.general.intro, - children: [ - buildDescriptionField({ - id: `${Routes.UNKNOWNRELATIONSHIP}.description`, - title: '', - description: m.unknownRelationship.general.description, - marginBottom: 3, - }), - buildRadioField({ - id: 'relationshipStatus.unregisteredCohabitation', - title: m.unknownRelationship.form.title, - options: getUnknownRelationshipOptions(), - }), - buildTextField({ - condition: (answers) => - (answers.relationshipStatus as FormValue) - ?.unregisteredCohabitation === ApproveOptions.Yes, - id: 'relationshipStatus.spouseNationalId', - title: m.unknownRelationship.inputs.spouseNationalId, - placeholder: m.unknownRelationship.inputs.spouseNationalIdPlaceholder, - format: '######-####', - }), - buildTextField({ - condition: (answers) => - (answers.relationshipStatus as FormValue) - ?.unregisteredCohabitation === ApproveOptions.Yes, - id: 'relationshipStatus.spouseEmail', - variant: 'email', - title: m.unknownRelationship.inputs.spouseEmail, - placeholder: m.unknownRelationship.inputs.spouseEmailPlaceholder, - }), - buildCheckboxField({ - condition: (answers) => - (answers.relationshipStatus as FormValue) - ?.unregisteredCohabitation === ApproveOptions.Yes, - id: 'relationshipStatus.spouseApproveTerms', - title: '', - options: [ - { value: 'yes', label: m.unknownRelationship.inputs.checkboxLabel }, - ], - }), - ], - }), - ], -}) diff --git a/libs/application/templates/financial-aid/src/forms/ApplicationForm/summarySection/index.ts b/libs/application/templates/financial-aid/src/forms/ApplicationForm/summarySection/index.ts deleted file mode 100644 index f7b3ea045af6..000000000000 --- a/libs/application/templates/financial-aid/src/forms/ApplicationForm/summarySection/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { buildSection } from '@island.is/application/core' -import { Routes } from '../../../lib/constants' -import * as m from '../../../lib/messages' -import { summaryMultiField } from './summaryMultiField' - -export const summarySection = buildSection({ - id: Routes.SUMMARY, - title: m.summaryForm.general.sectionTitle, - children: [summaryMultiField], -}) diff --git a/libs/application/templates/financial-aid/src/forms/ApplicationForm/summarySection/summaryMultiField.ts b/libs/application/templates/financial-aid/src/forms/ApplicationForm/summarySection/summaryMultiField.ts deleted file mode 100644 index 8bc51bde59a5..000000000000 --- a/libs/application/templates/financial-aid/src/forms/ApplicationForm/summarySection/summaryMultiField.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { - buildCustomField, - buildMultiField, - buildSubmitField, -} from '@island.is/application/core' -import { Routes } from '../../../lib/constants' -import * as m from '../../../lib/messages' -import { DefaultEvents } from '@island.is/application/types' - -export const summaryMultiField = buildMultiField({ - id: Routes.SUMMARY, - title: m.summaryForm.general.pageTitle, - children: [ - buildCustomField({ - id: Routes.SUMMARY, - title: m.summaryForm.general.pageTitle, - component: 'SummaryForm', - }), - buildSubmitField({ - id: 'submitApplication', - title: '', - actions: [ - { - event: DefaultEvents.SUBMIT, - name: m.summaryForm.general.submit, - type: 'primary', - }, - ], - }), - ], -}) diff --git a/libs/application/templates/financial-aid/src/forms/MuncipalityNotRegistered.ts b/libs/application/templates/financial-aid/src/forms/MuncipalityNotRegistered.ts new file mode 100644 index 000000000000..7c6b016c59f6 --- /dev/null +++ b/libs/application/templates/financial-aid/src/forms/MuncipalityNotRegistered.ts @@ -0,0 +1,17 @@ +import { buildCustomField, buildForm } from '@island.is/application/core' +import { Form } from '@island.is/application/types' +import { Routes } from '../lib/constants' + +import * as m from '../lib/messages' + +export const MuncipalityNotRegistered: Form = buildForm({ + id: 'FinancialAidApplication', + title: '', + children: [ + buildCustomField({ + id: Routes.SERVICECENTER, + title: m.serviceCenter.general.pageTitle, + component: 'ServiceCenter', + }), + ], +}) diff --git a/libs/application/templates/financial-aid/src/forms/MunicipalityNotRegisteredForm/MunicipalityNotRegistered.ts b/libs/application/templates/financial-aid/src/forms/MunicipalityNotRegisteredForm/MunicipalityNotRegistered.ts deleted file mode 100644 index b85092d18df8..000000000000 --- a/libs/application/templates/financial-aid/src/forms/MunicipalityNotRegisteredForm/MunicipalityNotRegistered.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { - buildDescriptionField, - buildForm, - buildLinkField, - buildMultiField, -} from '@island.is/application/core' -import { Application, Form } from '@island.is/application/types' -import { Routes } from '../../lib/constants' -import * as m from '../../lib/messages' -import { createElement } from 'react' -import { Logo } from '../../components/Logo/Logo' -import { getApplicantsServiceCenter } from '../../lib/utils/getApplicantsServiceCenter' - -export const MunicipalityNotRegistered: Form = buildForm({ - id: 'FinancialAidApplication', - title: '', - logo: (application: Application) => { - const logo = createElement(Logo, { application }) - return () => logo - }, - children: [ - buildMultiField({ - id: Routes.SERVICECENTER, - title: m.serviceCenter.general.pageTitle, - description: (application) => { - const applicantsServiceCenter = getApplicantsServiceCenter(application) - return { - ...m.serviceCenter.general.description, - values: { applicantsServiceCenter: applicantsServiceCenter?.name }, - } - }, - children: [ - buildDescriptionField({ - id: `${Routes.SERVICECENTER}-description`, - title: '', - description: m.serviceCenter.general.notRegistered, - }), - buildLinkField({ - id: `${Routes.SERVICECENTER}-link`, - title: (application) => { - const applicantsServiceCenter = - getApplicantsServiceCenter(application) - return { - ...m.serviceCenter.general.linkToServiceCenter, - values: { - applicantsServiceCenter: applicantsServiceCenter?.name, - }, - } - }, - iconProps: { icon: 'open' }, - link: (application) => { - const applicantsServiceCenter = - getApplicantsServiceCenter(application) - - return applicantsServiceCenter?.link - }, - }), - ], - }), - ], -}) diff --git a/libs/application/templates/financial-aid/src/forms/Prerequisites.ts b/libs/application/templates/financial-aid/src/forms/Prerequisites.ts new file mode 100644 index 000000000000..b90625f5965f --- /dev/null +++ b/libs/application/templates/financial-aid/src/forms/Prerequisites.ts @@ -0,0 +1,115 @@ +import { + buildCustomField, + buildDataProviderItem, + buildExternalDataProvider, + buildForm, + buildMultiField, + buildSection, + buildSubmitField, +} from '@island.is/application/core' +import { DefaultEvents, Form, FormModes } from '@island.is/application/types' + +import * as m from '../lib/messages' +import { Routes } from '../lib/constants' +import { + CurrentApplicationApi, + NationalRegistryUserApi, + NationalRegistrySpouseApi, + ChildrenCustodyInformationApi, + MunicipalityApi, + TaxDataApi, +} from '../dataProviders' + +export const Prerequisites: Form = buildForm({ + id: 'FinancialAidApplication', + title: m.application.name, + mode: FormModes.DRAFT, + children: [ + buildSection({ + id: 'externalData', + title: m.section.dataGathering, + children: [ + buildExternalDataProvider({ + title: m.externalData.general.pageTitle, + id: 'approveExternalData', + subTitle: m.externalData.general.subTitle, + description: m.externalData.general.description, + checkboxLabel: m.externalData.general.checkboxLabel, + dataProviders: [ + buildDataProviderItem({ + provider: NationalRegistryUserApi, + title: m.externalData.applicant.title, + subTitle: m.externalData.applicant.subTitle, + }), + buildDataProviderItem({ + provider: NationalRegistrySpouseApi, + title: '', + subTitle: '', + }), + buildDataProviderItem({ + provider: ChildrenCustodyInformationApi, + title: '', + subTitle: '', + }), + buildDataProviderItem({ + provider: MunicipalityApi, + title: '', + subTitle: '', + }), + buildDataProviderItem({ + provider: CurrentApplicationApi, + title: '', + subTitle: '', + }), + buildDataProviderItem({ + provider: TaxDataApi, + title: m.externalData.taxData.title, + subTitle: m.externalData.taxData.dataInfo, + }), + buildDataProviderItem({ + id: 'moreTaxInfo', + type: undefined, + title: '', + subTitle: m.externalData.taxData.process, + }), + ], + }), + ], + }), + buildSection({ + id: Routes.ACCECPTCONTRACT, + title: m.aboutForm.general.sectionTitle, + children: [ + buildMultiField({ + id: Routes.ACCECPTCONTRACT, + title: m.aboutForm.general.pageTitle, + children: [ + buildCustomField({ + id: Routes.ACCECPTCONTRACT, + title: m.aboutForm.general.pageTitle, + component: 'AboutForm', + }), + buildSubmitField({ + id: 'toDraft', + title: '', + refetchApplicationAfterSubmit: true, + actions: [ + { + event: DefaultEvents.SUBMIT, + name: m.aboutForm.goToApplication.button, + type: 'primary', + }, + ], + }), + ], + }), + // This is here to be able to show submit button on former screen :( :( :( + buildMultiField({ + id: '', + title: '', + children: [], + }), + ], + }), + ], +}) diff --git a/libs/application/templates/financial-aid/src/forms/PrerequisitesForm/externalDataSection.ts b/libs/application/templates/financial-aid/src/forms/PrerequisitesForm/externalDataSection.ts deleted file mode 100644 index 7a842ad82001..000000000000 --- a/libs/application/templates/financial-aid/src/forms/PrerequisitesForm/externalDataSection.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { - buildDataProviderItem, - buildExternalDataProvider, - buildSection, -} from '@island.is/application/core' -import * as m from '../../lib/messages' -import { - ChildrenCustodyInformationApi, - CurrentApplicationApi, - MunicipalityApi, - NationalRegistrySpouseApi, - NationalRegistryUserApi, - TaxDataApi, -} from '../../dataProviders' - -export const externalDataSection = buildSection({ - id: 'externalData', - title: m.section.dataGathering, - children: [ - buildExternalDataProvider({ - title: m.externalData.general.pageTitle, - id: 'approveExternalData', - subTitle: m.externalData.general.subTitle, - description: m.externalData.general.description, - checkboxLabel: m.externalData.general.checkboxLabel, - dataProviders: [ - buildDataProviderItem({ - provider: NationalRegistryUserApi, - title: m.externalData.applicant.title, - subTitle: m.externalData.applicant.subTitle, - }), - buildDataProviderItem({ - provider: NationalRegistrySpouseApi, - title: '', - subTitle: '', - }), - buildDataProviderItem({ - provider: ChildrenCustodyInformationApi, - title: '', - subTitle: '', - }), - buildDataProviderItem({ - provider: MunicipalityApi, - title: '', - subTitle: '', - }), - buildDataProviderItem({ - provider: CurrentApplicationApi, - title: '', - subTitle: '', - }), - buildDataProviderItem({ - provider: TaxDataApi, - title: m.externalData.taxData.title, - subTitle: m.externalData.taxData.dataInfo, - }), - buildDataProviderItem({ - id: 'moreTaxInfo', - type: undefined, - title: '', - subTitle: m.externalData.taxData.process, - }), - ], - }), - ], -}) diff --git a/libs/application/templates/financial-aid/src/forms/PrerequisitesForm/index.ts b/libs/application/templates/financial-aid/src/forms/PrerequisitesForm/index.ts deleted file mode 100644 index f9d16abaaec9..000000000000 --- a/libs/application/templates/financial-aid/src/forms/PrerequisitesForm/index.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { buildForm } from '@island.is/application/core' -import { Application, Form, FormModes } from '@island.is/application/types' -import * as m from '../../lib/messages' -import { externalDataSection } from './externalDataSection' -import { informationSection } from './informationSection' -import { createElement } from 'react' -import { Logo } from '../../components/Logo/Logo' - -export const PrerequisitesForm: Form = buildForm({ - id: 'FinancialAidApplication', - title: m.application.name, - renderLastScreenButton: true, - mode: FormModes.DRAFT, - logo: (application: Application) => { - const logo = createElement(Logo, { application }) - return () => logo - }, - children: [externalDataSection, informationSection], -}) diff --git a/libs/application/templates/financial-aid/src/forms/PrerequisitesForm/informationSection.ts b/libs/application/templates/financial-aid/src/forms/PrerequisitesForm/informationSection.ts deleted file mode 100644 index a435819050ad..000000000000 --- a/libs/application/templates/financial-aid/src/forms/PrerequisitesForm/informationSection.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { - buildAccordionField, - buildDescriptionField, - buildMultiField, - buildSection, - buildSubmitField, - getValueViaPath, -} from '@island.is/application/core' -import * as m from '../../lib/messages' -import { Routes } from '../../lib/constants' -import { DefaultEvents } from '@island.is/application/types' -import { currentMonth } from '@island.is/financial-aid/shared/lib' - -export const informationSection = buildSection({ - id: Routes.ACCECPTCONTRACT, - title: m.aboutForm.general.sectionTitle, - children: [ - buildMultiField({ - id: Routes.ACCECPTCONTRACT, - title: m.aboutForm.general.pageTitle, - children: [ - buildDescriptionField({ - id: `${Routes.ACCECPTCONTRACT}-description`, - title: () => { - return { - ...m.aboutForm.general.description, - values: { currentMonth: currentMonth() }, - } - }, - titleVariant: 'h5', - description: m.aboutForm.bulletList.content, - marginBottom: 5, - }), - buildAccordionField({ - id: `${Routes.ACCECPTCONTRACT}-accordion`, - title: m.privacyPolicyAccordion.general.sectionTitle, - accordionItems: (application) => { - const url = getValueViaPath( - application.externalData, - 'municipality.data.homepage', - ) - return [ - { - itemTitle: m.privacyPolicyAccordion.accordion.title, - itemContent: { - ...m.privacyPolicyAccordion.accordion.about, - values: { - webInfo: url - ? `vefsíðunni [${url}](${url})` - : 'vefsíðu sveitarfélagsins', - }, - }, - }, - ] - }, - }), - buildSubmitField({ - id: 'toDraft', - title: '', - refetchApplicationAfterSubmit: true, - actions: [ - { - event: DefaultEvents.SUBMIT, - name: m.aboutForm.goToApplication.button, - type: 'primary', - }, - ], - }), - ], - }), - ], -}) diff --git a/libs/application/templates/financial-aid/src/forms/PrerequisitesSpouse.ts b/libs/application/templates/financial-aid/src/forms/PrerequisitesSpouse.ts new file mode 100644 index 000000000000..aca037dad8f1 --- /dev/null +++ b/libs/application/templates/financial-aid/src/forms/PrerequisitesSpouse.ts @@ -0,0 +1,88 @@ +import { + buildCustomField, + buildDataProviderItem, + buildExternalDataProvider, + buildForm, + buildMultiField, + buildSection, + buildSubmitField, +} from '@island.is/application/core' +import { DefaultEvents, Form, FormModes } from '@island.is/application/types' + +import * as m from '../lib/messages' +import { Routes } from '../lib/constants' +import { CurrentApplicationApi, TaxDataSpouseApi } from '../dataProviders' + +export const PrerequisitesSpouse: Form = buildForm({ + id: 'FinancialAidApplication', + title: m.application.name, + mode: FormModes.IN_PROGRESS, + children: [ + buildSection({ + id: 'externalDataSpouse', + title: m.section.dataGathering, + children: [ + buildExternalDataProvider({ + title: m.externalData.general.pageTitle, + id: 'approveExternalDataSpouse', + subTitle: m.externalData.general.subTitle, + description: m.externalData.general.description, + checkboxLabel: m.externalData.general.checkboxLabel, + dataProviders: [ + buildDataProviderItem({ + provider: TaxDataSpouseApi, + title: m.externalData.taxData.title, + subTitle: m.externalData.taxData.dataInfo, + }), + buildDataProviderItem({ + provider: CurrentApplicationApi, + title: '', + subTitle: '', + }), + buildDataProviderItem({ + id: 'moreTaxInfo', + type: undefined, + title: '', + subTitle: m.externalData.taxData.process, + }), + ], + }), + ], + }), + buildSection({ + id: Routes.SPOUSEACCECPTCONTRACT, + title: m.aboutSpouseForm.general.sectionTitle, + children: [ + buildMultiField({ + id: Routes.SPOUSEACCECPTCONTRACT, + title: m.aboutForm.general.pageTitle, + children: [ + buildCustomField({ + id: Routes.SPOUSEACCECPTCONTRACT, + title: m.aboutSpouseForm.general.pageTitle, + component: 'AboutSpouseForm', + }), + buildSubmitField({ + id: 'toDraft', + title: '', + refetchApplicationAfterSubmit: true, + actions: [ + { + event: DefaultEvents.SUBMIT, + name: m.aboutForm.goToApplication.button, + type: 'primary', + }, + ], + }), + ], + }), + // This is here to be able to show submit button on former screen :( :( :( + buildMultiField({ + id: '', + title: '', + children: [], + }), + ], + }), + ], +}) diff --git a/libs/application/templates/financial-aid/src/forms/PrerequisitesSpouseForm/index.ts b/libs/application/templates/financial-aid/src/forms/PrerequisitesSpouseForm/index.ts deleted file mode 100644 index d05efe453597..000000000000 --- a/libs/application/templates/financial-aid/src/forms/PrerequisitesSpouseForm/index.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { buildForm } from '@island.is/application/core' -import { Application, Form, FormModes } from '@island.is/application/types' -import * as m from '../../lib/messages' -import { prerequisitesSection } from './prerequisitesSection' -import { informationSection } from './informationSection' -import { createElement } from 'react' -import { Logo } from '../../components/Logo/Logo' - -export const PrerequisitesSpouseForm: Form = buildForm({ - id: 'FinancialAidApplication', - title: m.application.name, - renderLastScreenButton: true, - mode: FormModes.IN_PROGRESS, - logo: (application: Application) => { - const logo = createElement(Logo, { application }) - return () => logo - }, - children: [prerequisitesSection, informationSection], -}) diff --git a/libs/application/templates/financial-aid/src/forms/PrerequisitesSpouseForm/informationSection.ts b/libs/application/templates/financial-aid/src/forms/PrerequisitesSpouseForm/informationSection.ts deleted file mode 100644 index f97d6f60b274..000000000000 --- a/libs/application/templates/financial-aid/src/forms/PrerequisitesSpouseForm/informationSection.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { - buildAccordionField, - buildDescriptionField, - buildMultiField, - buildSection, - buildSubmitField, - getValueViaPath, -} from '@island.is/application/core' -import { Routes } from '../../lib/constants' -import * as m from '../../lib/messages' -import { DefaultEvents } from '@island.is/application/types' -import { - currentMonth, - getNextPeriod, -} from '@island.is/financial-aid/shared/lib' - -export const informationSection = buildSection({ - id: Routes.SPOUSEACCECPTCONTRACT, - title: m.aboutSpouseForm.general.sectionTitle, - children: [ - buildMultiField({ - id: Routes.SPOUSEACCECPTCONTRACT, - title: m.aboutForm.general.pageTitle, - children: [ - buildDescriptionField({ - id: `${Routes.SPOUSEACCECPTCONTRACT}-description`, - title: '', - description: (application) => { - const { externalData } = application - const spouseName = getValueViaPath( - externalData, - 'nationalRegistry.data.fullName', - ) - return { - ...m.aboutSpouseForm.general.description, - values: { - spouseName, - currentMonth: currentMonth(), - nextMonth: getNextPeriod().month, - }, - } - }, - marginBottom: 5, - }), - buildAccordionField({ - id: `${Routes.SPOUSEACCECPTCONTRACT}-accordion`, - title: m.privacyPolicyAccordion.general.sectionTitle, - accordionItems: (application) => { - const url = getValueViaPath( - application.externalData, - 'municipality.data.homepage', - ) - - return [ - { - itemTitle: m.privacyPolicyAccordion.accordion.title, - itemContent: { - ...m.privacyPolicyAccordion.accordion.about, - values: { - webInfo: url - ? `vefsíðunni [${url}](${url})` - : 'vefsíðu sveitarfélagsins', - }, - }, - }, - ] - }, - }), - buildSubmitField({ - id: 'toDraft', - title: '', - refetchApplicationAfterSubmit: true, - actions: [ - { - event: DefaultEvents.SUBMIT, - name: m.aboutForm.goToApplication.button, - type: 'primary', - }, - ], - }), - ], - }), - ], -}) diff --git a/libs/application/templates/financial-aid/src/forms/PrerequisitesSpouseForm/prerequisitesSection.ts b/libs/application/templates/financial-aid/src/forms/PrerequisitesSpouseForm/prerequisitesSection.ts deleted file mode 100644 index c14859c3cacf..000000000000 --- a/libs/application/templates/financial-aid/src/forms/PrerequisitesSpouseForm/prerequisitesSection.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { - buildDataProviderItem, - buildExternalDataProvider, - buildSection, -} from '@island.is/application/core' -import * as m from '../../lib/messages' -import { CurrentApplicationApi, TaxDataSpouseApi } from '../../dataProviders' - -export const prerequisitesSection = buildSection({ - id: 'externalDataSpouse', - title: m.section.dataGathering, - children: [ - buildExternalDataProvider({ - title: m.externalData.general.pageTitle, - id: 'approveExternalDataSpouse', - subTitle: m.externalData.general.subTitle, - description: m.externalData.general.description, - checkboxLabel: m.externalData.general.checkboxLabel, - dataProviders: [ - buildDataProviderItem({ - provider: TaxDataSpouseApi, - title: m.externalData.taxData.title, - subTitle: m.externalData.taxData.dataInfo, - }), - buildDataProviderItem({ - provider: CurrentApplicationApi, - title: '', - subTitle: '', - }), - buildDataProviderItem({ - id: 'moreTaxInfo', - type: undefined, - title: '', - subTitle: m.externalData.taxData.process, - }), - ], - }), - ], -}) diff --git a/libs/application/templates/financial-aid/src/forms/Spouse.ts b/libs/application/templates/financial-aid/src/forms/Spouse.ts new file mode 100644 index 000000000000..afa35aeed8bc --- /dev/null +++ b/libs/application/templates/financial-aid/src/forms/Spouse.ts @@ -0,0 +1,109 @@ +import { + buildCustomField, + buildForm, + buildMultiField, + buildSection, + buildSubmitField, +} from '@island.is/application/core' +import { DefaultEvents, Form, FormModes } from '@island.is/application/types' + +import * as m from '../lib/messages' +import { ApproveOptions, ExternalData } from '../lib/types' +import { Routes } from '../lib/constants' + +export const Spouse: Form = buildForm({ + id: 'FinancialAidApplication', + title: m.application.name, + mode: FormModes.IN_PROGRESS, + children: [ + buildSection({ + id: Routes.SPOUSEINCOME, + title: m.incomeForm.general.sectionTitle, + children: [ + buildCustomField({ + id: Routes.SPOUSEINCOME, + title: m.incomeForm.general.pageTitle, + component: 'IncomeForm', + }), + ], + }), + buildSection({ + condition: (answers) => answers.spouseIncome === ApproveOptions.Yes, + id: Routes.SPOUSEINCOMEFILES, + title: m.incomeFilesForm.general.sectionTitle, + children: [ + buildCustomField({ + id: Routes.SPOUSEINCOMEFILES, + title: m.incomeFilesForm.general.pageTitle, + component: 'IncomeFilesForm', + }), + ], + }), + buildSection({ + condition: (_, externalData) => + (externalData as unknown as ExternalData)?.taxDataSpouse?.data + ?.municipalitiesDirectTaxPayments?.success === false || + (externalData as unknown as ExternalData)?.taxDataSpouse?.data + ?.municipalitiesPersonalTaxReturn?.personalTaxReturn == null, + id: Routes.SPOUSETAXRETURNFILES, + title: m.taxReturnForm.general.sectionTitle, + children: [ + buildCustomField({ + id: Routes.SPOUSETAXRETURNFILES, + title: m.taxReturnForm.general.pageTitle, + component: 'TaxReturnFilesForm', + }), + ], + }), + buildSection({ + id: Routes.SPOUSECONTACTINFO, + title: m.contactInfo.general.sectionTitle, + children: [ + buildCustomField({ + id: Routes.SPOUSECONTACTINFO, + title: m.contactInfo.general.pageTitle, + component: 'ContactInfo', + }), + ], + }), + buildSection({ + id: Routes.SPOUSESUMMARY, + title: m.summaryForm.general.sectionTitle, + children: [ + buildMultiField({ + id: Routes.SPOUSESUMMARY, + title: m.summaryForm.general.pageTitle, + children: [ + buildCustomField({ + id: Routes.SPOUSESUMMARY, + title: m.summaryForm.general.pageTitle, + component: 'SpouseSummaryForm', + }), + buildSubmitField({ + id: 'submitApplication', + title: '', + actions: [ + { + event: DefaultEvents.SUBMIT, + name: m.summaryForm.general.submit, + type: 'primary', + }, + ], + }), + ], + }), + ], + }), + buildSection({ + id: Routes.SPOUSECONFIRMATION, + title: m.confirmation.general.sectionTitle, + children: [ + buildCustomField({ + id: Routes.SPOUSECONFIRMATION, + title: m.confirmation.general.pageTitle, + component: 'SpouseConfirmation', + }), + ], + }), + ], +}) diff --git a/libs/application/templates/financial-aid/src/forms/SpouseForm/index.ts b/libs/application/templates/financial-aid/src/forms/SpouseForm/index.ts deleted file mode 100644 index 0c813f8f5ed6..000000000000 --- a/libs/application/templates/financial-aid/src/forms/SpouseForm/index.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { buildForm } from '@island.is/application/core' -import { Application, Form, FormModes } from '@island.is/application/types' -import * as m from '../../lib/messages' -import { spouseIncomeSection } from './spouseIncomeSection' -import { spouseIncomeFilesSection } from './spouseIncomeFilesSection' -import { spouseTaxReturnFilesSection } from './spouseTaxReturnFilesSection' -import { spouseContactInfoSection } from './spouseContactInfoSection' -import { spouseSummarySection } from './spouseSumarySection' -import { spouseConfirmationSection } from './spouseConfirmationSection' -import { createElement } from 'react' -import { Logo } from '../../components/Logo/Logo' - -export const spouseForm: Form = buildForm({ - id: 'FinancialAidApplication', - title: m.application.name, - mode: FormModes.IN_PROGRESS, - logo: (application: Application) => { - const logo = createElement(Logo, { application }) - return () => logo - }, - children: [ - spouseIncomeSection, - spouseIncomeFilesSection, - spouseTaxReturnFilesSection, - spouseContactInfoSection, - spouseSummarySection, - spouseConfirmationSection, - ], -}) diff --git a/libs/application/templates/financial-aid/src/forms/SpouseForm/spouseConfirmationSection.ts b/libs/application/templates/financial-aid/src/forms/SpouseForm/spouseConfirmationSection.ts deleted file mode 100644 index 76ff837b9908..000000000000 --- a/libs/application/templates/financial-aid/src/forms/SpouseForm/spouseConfirmationSection.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { - buildAlertMessageField, - buildDescriptionField, - buildImageField, - buildMultiField, - buildSection, -} from '@island.is/application/core' -import * as m from '../../lib/messages' -import { Routes } from '../../lib/constants' -import { ConfirmationImage } from '../../assets/ConfirmationImage' -import { Application, FormValue } from '@island.is/application/types' -import { - getSpouseNextStepsDescription, - hasSpouseIncomeFiles, -} from '../../lib/utils' -import { getNextPeriod } from '@island.is/financial-aid/shared/lib' - -export const spouseConfirmationSection = buildSection({ - id: Routes.SPOUSECONFIRMATION, - title: m.confirmation.general.sectionTitle, - children: [ - buildMultiField({ - id: Routes.SPOUSECONFIRMATION, - title: m.confirmation.general.pageTitle, - children: [ - buildAlertMessageField({ - id: 'confirmationAlert', - title: m.confirmation.alertMessages.success, - alertType: 'success', - condition: (formValue) => { - return hasSpouseIncomeFiles(formValue) - }, - }), - buildAlertMessageField({ - id: 'confirmationAlert', - title: m.confirmation.alertMessages.dataNeeded, - message: m.confirmation.alertMessages.dataNeededText, - alertType: 'warning', - condition: (formValue) => { - return !hasSpouseIncomeFiles(formValue) - }, - }), - buildDescriptionField({ - id: 'confirmationDescription', - title: m.confirmation.nextSteps.title, - titleVariant: 'h3', - marginBottom: 2, - }), - buildDescriptionField({ - id: 'confirmationDescriptionFirstBullet', - title: '', - description: (formValue: Application) => - getSpouseNextStepsDescription( - formValue.answers, - formValue.externalData, - ), - }), - buildDescriptionField({ - id: 'confirmationDescriptionBullets', - title: '', - description: () => { - return { - ...m.confirmation.nextSteps.content, - values: { - nextMonth: getNextPeriod().month, - }, - } - }, - marginBottom: 5, - }), - buildDescriptionField({ - id: 'confirmationLinks', - title: m.confirmation.links.title, - titleVariant: 'h3', - description: m.confirmation.links.content, - marginTop: 5, - marginBottom: 5, - }), - buildImageField({ - id: 'confirmationImage', - title: '', - image: ConfirmationImage, - marginBottom: 5, - }), - ], - }), - ], -}) diff --git a/libs/application/templates/financial-aid/src/forms/SpouseForm/spouseContactInfoSection.ts b/libs/application/templates/financial-aid/src/forms/SpouseForm/spouseContactInfoSection.ts deleted file mode 100644 index f78bdbcb624c..000000000000 --- a/libs/application/templates/financial-aid/src/forms/SpouseForm/spouseContactInfoSection.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { - buildMultiField, - buildSection, - buildTextField, -} from '@island.is/application/core' -import { Routes } from '../../lib/constants' -import * as m from '../../lib/messages' - -export const spouseContactInfoSection = buildSection({ - id: Routes.SPOUSECONTACTINFO, - title: m.contactInfo.general.sectionTitle, - children: [ - buildMultiField({ - id: Routes.SPOUSECONTACTINFO, - title: m.contactInfo.general.pageTitle, - description: m.contactInfo.general.description, - children: [ - buildTextField({ - id: `${Routes.SPOUSECONTACTINFO}.email`, - title: m.contactInfo.emailInput.label, - placeholder: m.contactInfo.emailInput.placeholder, - }), - buildTextField({ - id: `${Routes.SPOUSECONTACTINFO}.phone`, - title: m.contactInfo.phoneInput.label, - placeholder: '000-0000', - format: '###-####', - }), - ], - }), - ], -}) diff --git a/libs/application/templates/financial-aid/src/forms/SpouseForm/spouseIncomeFilesSection.ts b/libs/application/templates/financial-aid/src/forms/SpouseForm/spouseIncomeFilesSection.ts deleted file mode 100644 index c595cce82ef0..000000000000 --- a/libs/application/templates/financial-aid/src/forms/SpouseForm/spouseIncomeFilesSection.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { - buildCustomField, - buildSection, - getValueViaPath, -} from '@island.is/application/core' -import { ApproveOptions } from '../../lib/types' -import { Routes } from '../../lib/constants' -import * as m from '../../lib/messages' - -export const spouseIncomeFilesSection = buildSection({ - condition: (answers) => { - const income = getValueViaPath(answers, 'spouseIncome.type') - return income === ApproveOptions.Yes - }, - id: Routes.SPOUSEINCOMEFILES, - title: m.incomeFilesForm.general.sectionTitle, - children: [ - buildCustomField({ - id: Routes.SPOUSEINCOMEFILES, - title: m.incomeFilesForm.general.pageTitle, - component: 'IncomeFilesForm', - }), - ], -}) diff --git a/libs/application/templates/financial-aid/src/forms/SpouseForm/spouseIncomeSection.ts b/libs/application/templates/financial-aid/src/forms/SpouseForm/spouseIncomeSection.ts deleted file mode 100644 index 0a9e0d55c351..000000000000 --- a/libs/application/templates/financial-aid/src/forms/SpouseForm/spouseIncomeSection.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { - buildDescriptionField, - buildMultiField, - buildRadioField, - buildSection, -} from '@island.is/application/core' -import * as m from '../../lib/messages' -import { Routes } from '../../lib/constants' -import { getIncomeOptions } from '../../lib/utils/getIncomeOptions' - -export const spouseIncomeSection = buildSection({ - id: Routes.SPOUSEINCOME, - title: m.incomeForm.general.sectionTitle, - children: [ - buildMultiField({ - id: Routes.SPOUSEINCOME, - title: m.incomeForm.general.pageTitle, - children: [ - buildRadioField({ - id: `${Routes.SPOUSEINCOME}.type`, - title: '', - width: 'half', - options: getIncomeOptions(), - }), - buildDescriptionField({ - id: `${Routes.SPOUSEINCOME}.bullets`, - title: m.incomeForm.bulletList.headline, - titleVariant: 'h3', - marginTop: 3, - description: m.incomeForm.examplesOfIncome.incomeExampleList, - }), - ], - }), - ], -}) diff --git a/libs/application/templates/financial-aid/src/forms/SpouseForm/spouseSumarySection.ts b/libs/application/templates/financial-aid/src/forms/SpouseForm/spouseSumarySection.ts deleted file mode 100644 index 338fb247c27c..000000000000 --- a/libs/application/templates/financial-aid/src/forms/SpouseForm/spouseSumarySection.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { - buildCustomField, - buildMultiField, - buildSection, - buildSubmitField, -} from '@island.is/application/core' -import * as m from '../../lib/messages' -import { Routes } from '../../lib/constants' -import { DefaultEvents } from '@island.is/application/types' - -export const spouseSummarySection = buildSection({ - id: Routes.SPOUSESUMMARY, - title: m.summaryForm.general.sectionTitle, - children: [ - buildMultiField({ - id: Routes.SPOUSESUMMARY, - title: m.summaryForm.general.pageTitle, - children: [ - buildCustomField({ - id: Routes.SPOUSESUMMARY, - title: m.summaryForm.general.pageTitle, - component: 'SpouseSummaryForm', - }), - buildSubmitField({ - id: 'submitApplication', - title: '', - actions: [ - { - event: DefaultEvents.SUBMIT, - name: m.summaryForm.general.submit, - type: 'primary', - }, - ], - }), - ], - }), - ], -}) diff --git a/libs/application/templates/financial-aid/src/forms/SpouseForm/spouseTaxReturnFilesSection.ts b/libs/application/templates/financial-aid/src/forms/SpouseForm/spouseTaxReturnFilesSection.ts deleted file mode 100644 index 20a6b88a1273..000000000000 --- a/libs/application/templates/financial-aid/src/forms/SpouseForm/spouseTaxReturnFilesSection.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { - buildCustomField, - buildDescriptionField, - buildFileUploadField, - buildMultiField, - buildSection, - getValueViaPath, -} from '@island.is/application/core' -import { FILE_SIZE_LIMIT, Routes, UPLOAD_ACCEPT } from '../../lib/constants' -import * as m from '../../lib/messages' -import { ExternalData } from '@island.is/application/types' - -export const spouseTaxReturnFilesSection = buildSection({ - condition: (_, externalData) => { - const spouseTaxSuccess = getValueViaPath( - externalData, - 'taxDataSpouse.data.municipalitiesDirectTaxPayments.success', - ) - const spouseTaxReturn = getValueViaPath( - externalData, - 'taxDataSpouse.data.municipalitiesPersonalTaxReturn.personalTaxReturn', - ) - return spouseTaxSuccess === false || spouseTaxReturn == null - }, - id: Routes.SPOUSETAXRETURNFILES, - title: m.taxReturnForm.general.sectionTitle, - children: [ - buildCustomField({ - id: Routes.SPOUSETAXRETURNFILES, - title: m.taxReturnForm.general.pageTitle, - component: 'TaxReturnFilesForm', - }), - ], -}) diff --git a/libs/application/templates/financial-aid/src/forms/SpouseSubmitted.ts b/libs/application/templates/financial-aid/src/forms/SpouseSubmitted.ts new file mode 100644 index 000000000000..a66001769230 --- /dev/null +++ b/libs/application/templates/financial-aid/src/forms/SpouseSubmitted.ts @@ -0,0 +1,64 @@ +import { + buildCustomField, + buildForm, + buildMultiField, + buildSubmitField, +} from '@island.is/application/core' +import { DefaultEvents, Form } from '@island.is/application/types' +import { Routes } from '../lib/constants' + +import * as m from '../lib/messages' + +export const SpouseSubmitted: Form = buildForm({ + id: 'FinancialAidApplication', + title: m.status.sectionTitle, + children: [ + buildMultiField({ + id: Routes.SPOUSESTATUS, + title: m.status.pageTitle, + children: [ + buildCustomField({ + id: Routes.SPOUSESTATUS, + title: m.status.spousePageTitle, + component: 'SpouseStatus', + }), + // Empty submit field to hide all buttons in the footer + buildSubmitField({ + id: '', + title: '', + actions: [], + }), + ], + }), + buildMultiField({ + id: Routes.MISSINGFILES, + title: m.missingFiles.general.pageTitle, + children: [ + buildCustomField( + { + id: Routes.MISSINGFILES, + title: m.missingFiles.general.pageTitle, + component: 'MissingFiles', + }, + { isSpouse: true }, + ), + buildSubmitField({ + id: 'missingFilesSubmit', + title: '', + actions: [ + { + event: DefaultEvents.EDIT, + name: m.missingFiles.general.submit, + type: 'primary', + }, + ], + }), + ], + }), + buildCustomField({ + id: Routes.MISSINGFILESCONFIRMATION, + title: m.missingFiles.confirmation.title, + component: 'MissingFilesConfirmation', + }), + ], +}) diff --git a/libs/application/templates/financial-aid/src/forms/SpouseSubmittedForm/MissingFiles.ts b/libs/application/templates/financial-aid/src/forms/SpouseSubmittedForm/MissingFiles.ts deleted file mode 100644 index 7fc1b6835e58..000000000000 --- a/libs/application/templates/financial-aid/src/forms/SpouseSubmittedForm/MissingFiles.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { - buildCustomField, - buildDescriptionField, - buildFileUploadField, - buildMultiField, - buildSection, - buildSubmitField, - buildTextField, -} from '@island.is/application/core' -import { FILE_SIZE_LIMIT, Routes, UPLOAD_ACCEPT } from '../../lib/constants' -import { DefaultEvents } from '@island.is/application/types' -import * as m from '../../lib/messages' - -export const MissingFiles = buildSection({ - id: Routes.MISSINGFILESSPOUSE, - title: m.missingFiles.general.pageTitle, - children: [ - buildMultiField({ - id: Routes.MISSINGFILESSPOUSE, - title: m.missingFiles.general.pageTitle, - description: m.missingFiles.general.description, - children: [ - buildCustomField( - { - id: Routes.MISSINGFILES, - title: m.missingFiles.general.pageTitle, - component: 'MissingFiles', - }, - { isSpouse: true }, - ), - buildSubmitField({ - id: 'missingFilesSubmit', - title: '', - actions: [ - { - event: DefaultEvents.EDIT, - name: m.missingFiles.general.submit, - type: 'primary', - }, - ], - }), - ], - }), - ], -}) diff --git a/libs/application/templates/financial-aid/src/forms/SpouseSubmittedForm/MissingFilesConfirmation.ts b/libs/application/templates/financial-aid/src/forms/SpouseSubmittedForm/MissingFilesConfirmation.ts deleted file mode 100644 index f2e55cc6a8b4..000000000000 --- a/libs/application/templates/financial-aid/src/forms/SpouseSubmittedForm/MissingFilesConfirmation.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { - buildCustomField, - buildMessageWithLinkButtonField, - buildMultiField, - buildSection, -} from '@island.is/application/core' -import { Routes } from '../../lib/constants' -import * as m from '../../lib/messages' - -export const MissingFilesConfirmation = buildSection({ - id: Routes.MISSINGFILESCONFIRMATION, - title: m.missingFiles.confirmation.sectionTitle, - children: [ - buildMultiField({ - id: Routes.SPOUSESTATUS, - title: m.missingFiles.confirmation.sectionTitle, - description: m.missingFiles.confirmation.subtitle, - children: [ - buildCustomField( - { - id: Routes.SPOUSESTATUS, - title: '', - component: 'MissingFilesConfirmation', - }, - { isSpouse: true }, - ), - buildMessageWithLinkButtonField({ - id: 'goToServicePortal', - title: '', - url: '/minarsidur/umsoknir', - buttonTitle: m.missingFiles.confirmation.openServicePortalButtonTitle, - message: m.missingFiles.confirmation.openServicePortalMessageText, - marginTop: 6, - }), - ], - }), - ], -}) diff --git a/libs/application/templates/financial-aid/src/forms/SpouseSubmittedForm/SpouseStatus.ts b/libs/application/templates/financial-aid/src/forms/SpouseSubmittedForm/SpouseStatus.ts deleted file mode 100644 index 18f7993df8b0..000000000000 --- a/libs/application/templates/financial-aid/src/forms/SpouseSubmittedForm/SpouseStatus.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { buildCustomField, buildSection } from '@island.is/application/core' -import { Routes } from '../../lib/constants' -import * as m from '../../lib/messages' - -export const SpouseStatus = buildSection({ - id: Routes.SPOUSESTATUS, - title: m.status.pageTitle, - children: [ - buildCustomField({ - id: Routes.SPOUSESTATUS, - title: m.status.spousePageTitle, - component: 'SpouseStatus', - }), - ], -}) diff --git a/libs/application/templates/financial-aid/src/forms/SpouseSubmittedForm/index.ts b/libs/application/templates/financial-aid/src/forms/SpouseSubmittedForm/index.ts deleted file mode 100644 index aa76330d23fc..000000000000 --- a/libs/application/templates/financial-aid/src/forms/SpouseSubmittedForm/index.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { buildForm } from '@island.is/application/core' -import { Application, Form } from '@island.is/application/types' - -import * as m from '../../lib/messages' -import { createElement } from 'react' -import { Logo } from '../../components/Logo/Logo' -import { SpouseStatus } from './SpouseStatus' -import { MissingFiles } from './MissingFiles' -import { MissingFilesConfirmation } from './MissingFilesConfirmation' - -export const SpouseSubmitted: Form = buildForm({ - id: 'FinancialAidApplication', - title: m.status.sectionTitle, - logo: (application: Application) => { - const logo = createElement(Logo, { application }) - return () => logo - }, - children: [SpouseStatus, MissingFiles, MissingFilesConfirmation], -}) diff --git a/libs/application/templates/financial-aid/src/lib/FinancialAidTemplate.ts b/libs/application/templates/financial-aid/src/lib/FinancialAidTemplate.ts index 0c19192d553d..77dae941748a 100644 --- a/libs/application/templates/financial-aid/src/lib/FinancialAidTemplate.ts +++ b/libs/application/templates/financial-aid/src/lib/FinancialAidTemplate.ts @@ -7,16 +7,19 @@ import { DefaultEvents, Application, } from '@island.is/application/types' + import { assign } from 'xstate' + import { Roles, ApplicationStates, ONE_DAY, ONE_MONTH } from './constants' + import { application, stateDescriptions } from './messages' import { dataSchema } from './dataSchema' import { - isMunicipalityNotRegistered, + isMuncipalityNotRegistered, hasActiveCurrentApplication, hasSpouseCheck, } from './utils' -import { FinancialAidAnswers, FinancialAidExternalData } from '..' +import { FAApplication } from '..' import { CreateApplicationApi, CurrentApplicationApi, @@ -62,8 +65,8 @@ const FinancialAidTemplate: ApplicationTemplate< { id: Roles.APPLICANT, formLoader: () => - import('../forms/PrerequisitesForm').then((module) => - Promise.resolve(module.PrerequisitesForm), + import('../forms/Prerequisites').then((module) => + Promise.resolve(module.Prerequisites), ), write: 'all', delete: true, @@ -81,8 +84,8 @@ const FinancialAidTemplate: ApplicationTemplate< on: { SUBMIT: [ { - target: ApplicationStates.MUNICIPALITYNOTREGISTERED, - cond: isMunicipalityNotRegistered, + target: ApplicationStates.MUNCIPALITYNOTREGISTERED, + cond: isMuncipalityNotRegistered, }, { target: ApplicationStates.SUBMITTED, @@ -106,8 +109,8 @@ const FinancialAidTemplate: ApplicationTemplate< { id: Roles.APPLICANT, formLoader: () => - import('../forms/ApplicationForm').then((module) => - Promise.resolve(module.ApplicationForm), + import('../forms/Application').then((module) => + Promise.resolve(module.Application), ), read: 'all', write: 'all', @@ -139,8 +142,8 @@ const FinancialAidTemplate: ApplicationTemplate< { id: Roles.SPOUSE, formLoader: () => - import('../forms/PrerequisitesSpouseForm').then((module) => - Promise.resolve(module.PrerequisitesSpouseForm), + import('../forms/PrerequisitesSpouse').then((module) => + Promise.resolve(module.PrerequisitesSpouse), ), read: 'all', write: 'all', @@ -149,7 +152,7 @@ const FinancialAidTemplate: ApplicationTemplate< { id: Roles.APPLICANT, formLoader: () => - import('../forms/ApplicantSubmittedForm').then((module) => + import('../forms/ApplicantSubmitted').then((module) => Promise.resolve(module.ApplicantSubmitted), ), read: 'all', @@ -163,8 +166,8 @@ const FinancialAidTemplate: ApplicationTemplate< target: ApplicationStates.SUBMITTED, cond: hasActiveCurrentApplication, }, + { target: ApplicationStates.SPOUSE }, ], - EDIT: { target: ApplicationStates.SPOUSE }, }, }, [ApplicationStates.SPOUSE]: { @@ -179,8 +182,8 @@ const FinancialAidTemplate: ApplicationTemplate< { id: Roles.SPOUSE, formLoader: () => - import('../forms/SpouseForm').then((module) => - Promise.resolve(module.spouseForm), + import('../forms/Spouse').then((module) => + Promise.resolve(module.Spouse), ), read: 'all', write: 'all', @@ -188,7 +191,7 @@ const FinancialAidTemplate: ApplicationTemplate< { id: Roles.APPLICANT, formLoader: () => - import('../forms/ApplicantSubmittedForm').then((module) => + import('../forms/ApplicantSubmitted').then((module) => Promise.resolve(module.ApplicantSubmitted), ), read: 'all', @@ -213,7 +216,7 @@ const FinancialAidTemplate: ApplicationTemplate< { id: Roles.APPLICANT, formLoader: () => - import('../forms/ApplicantSubmittedForm').then((module) => + import('../forms/ApplicantSubmitted').then((module) => Promise.resolve(module.ApplicantSubmitted), ), read: 'all', @@ -222,7 +225,7 @@ const FinancialAidTemplate: ApplicationTemplate< { id: Roles.SPOUSE, formLoader: () => - import('../forms/SpouseSubmittedForm').then((module) => + import('../forms/SpouseSubmitted').then((module) => Promise.resolve(module.SpouseSubmitted), ), read: 'all', @@ -234,7 +237,7 @@ const FinancialAidTemplate: ApplicationTemplate< EDIT: { target: ApplicationStates.SUBMITTED }, }, }, - [ApplicationStates.MUNICIPALITYNOTREGISTERED]: { + [ApplicationStates.MUNCIPALITYNOTREGISTERED]: { meta: { name: application.name.defaultMessage, status: 'rejected', @@ -247,10 +250,8 @@ const FinancialAidTemplate: ApplicationTemplate< { id: Roles.APPLICANT, formLoader: () => - import( - '../forms/MunicipalityNotRegisteredForm/MunicipalityNotRegistered' - ).then((module) => - Promise.resolve(module.MunicipalityNotRegistered), + import('../forms/MuncipalityNotRegistered').then((module) => + Promise.resolve(module.MuncipalityNotRegistered), ), read: 'all', }, @@ -262,13 +263,11 @@ const FinancialAidTemplate: ApplicationTemplate< stateMachineOptions: { actions: { assignToSpouse: assign((context) => { - const { externalData, answers } = context.application - const answersSchema = answers as unknown as FinancialAidAnswers - const externalDataSchema = - externalData as unknown as FinancialAidExternalData + const { externalData, answers } = + context.application as unknown as FAApplication const spouse = - externalDataSchema.nationalRegistrySpouse.data?.nationalId || - answersSchema.relationshipStatus.spouseNationalId + externalData.nationalRegistrySpouse.data?.nationalId || + answers.relationshipStatus.spouseNationalId if (spouse) { return { diff --git a/libs/application/templates/financial-aid/src/lib/constants.ts b/libs/application/templates/financial-aid/src/lib/constants.ts index 07f2a51a5bad..a41797cc3785 100644 --- a/libs/application/templates/financial-aid/src/lib/constants.ts +++ b/libs/application/templates/financial-aid/src/lib/constants.ts @@ -4,7 +4,7 @@ export enum ApplicationStates { SUBMITTED = 'submitted', SPOUSE = 'spouse', PREREQUISITESSPOUSE = 'prerequisitesSpouse', - MUNICIPALITYNOTREGISTERED = 'municipalityNotRegistered', + MUNCIPALITYNOTREGISTERED = 'muncipalityNotRegistered', } export enum Roles { @@ -16,14 +16,12 @@ export const ONE_MONTH = 24 * 3600 * 1000 * 31 export const ONE_DAY = 24 * 3600 * 1000 export enum Routes { - PERSONALINTEREST = 'personalInterest', ACCECPTCONTRACT = 'acceptContract', INRELATIONSHIP = 'inRelationship', UNKNOWNRELATIONSHIP = 'unknownRelationship', HOMECIRCUMSTANCES = 'homeCircumstances', STUDENT = 'student', EMPLOYMENT = 'employment', - FINANCES = 'finances', INCOME = 'income', PERSONALTAXCREDIT = 'personalTaxCredit', BANKINFO = 'bankInfo', @@ -42,7 +40,6 @@ export enum Routes { SPOUSESUMMARY = 'spouseSummary', SPOUSECONFIRMATION = 'spouseConfirmation', MISSINGFILES = 'missingFiles', - MISSINGFILESSPOUSE = 'missingFilesSpouse', APPLICANTSTATUS = 'applicantStatus', MISSINGFILESCONFIRMATION = 'missingFilesConfirmation', SPOUSESTATUS = 'spouseStatus', @@ -57,6 +54,14 @@ export enum ApiActions { SENDSPOUSEEMAIL = 'sendSpouseEmail', } -export const UPLOAD_ACCEPT = '.pdf, .doc, .docx, .rtf, .jpg, .jpeg, .png, .heic' - +export const UPLOAD_ACCEPT = [ + '.pdf', + '.doc', + '.docx', + '.rtf', + '.jpg', + '.jpeg', + '.png', + '.heic', +] export const FILE_SIZE_LIMIT = 10000000 // 10MB diff --git a/libs/application/templates/financial-aid/src/lib/dataSchema.ts b/libs/application/templates/financial-aid/src/lib/dataSchema.ts index 76990442bfad..fc7ea183384c 100644 --- a/libs/application/templates/financial-aid/src/lib/dataSchema.ts +++ b/libs/application/templates/financial-aid/src/lib/dataSchema.ts @@ -7,167 +7,144 @@ import { import { isValidEmail, isValidNationalId, isValidPhone } from './utils' import { ApproveOptions } from './types' -const fileSchema = z.object({ - name: z.string(), - key: z.string(), - url: z.string().optional(), -}) - -const approveExternalDataSchema = z.boolean().refine((v) => v, { - params: error.validation.dataGathering, -}) - -const spouseSchema = z.object({ - email: z.string().refine((v) => isValidEmail(v), { - params: error.validation.email, +export const dataSchema = z.object({ + approveExternalData: z.boolean().refine((v) => v, { + params: error.validation.dataGathering, }), - approveTerms: z.array(z.string()).refine((v) => v && v.length === 1, { - params: error.validation.approveSpouse, + approveExternalDataSpouse: z.boolean().refine((v) => v, { + params: error.validation.dataGathering, }), -}) - -const childSchoolInfoSchema = z.object({ - fullName: z.string(), - nationalId: z.string(), - school: z.string(), - livesWithApplicant: z.boolean(), - livesWithBothParents: z.boolean(), -}) - -const relationshipStatusScema = z - .object({ - unregisteredCohabitation: z.nativeEnum(ApproveOptions).refine((v) => v, { - params: error.validation.radioErrorMessage, - }), - spouseEmail: z.string().optional(), - spouseNationalId: z.string().optional(), - spouseApproveTerms: z.array(z.string()).optional(), - }) - .refine( - (v) => - v.unregisteredCohabitation === ApproveOptions.Yes - ? v.spouseEmail && - isValidEmail(v.spouseEmail) && - v.spouseNationalId && - isValidNationalId(v.spouseNationalId) && - v.spouseApproveTerms && - v.spouseApproveTerms.length === 1 - : true, - { - //More detailed error messages are in the UnknownRelationshipForm component + spouse: z.object({ + email: z.string().refine((v) => isValidEmail(v), { params: error.validation.email, - }, - ) - -const studentSchema = z - .object({ - isStudent: z - .enum([ApproveOptions.Yes, ApproveOptions.No]) - .refine((v) => v, { - params: error.validation.radioErrorMessage, - }), - custom: z.string().optional(), - }) - .refine((v) => (v.isStudent === ApproveOptions.Yes ? v.custom : true), { - params: error.validation.inputErrorMessage, - }) - -const homeCircumstancesSchema = z - .object({ - type: z.nativeEnum(HomeCircumstances).refine((v) => v, { - params: error.validation.radioErrorMessage, }), - custom: z.string().optional(), - }) - .refine((v) => (v.type === HomeCircumstances.OTHER ? v.custom : true), { - params: error.validation.inputErrorMessage, - path: ['custom'], - }) - -const incomeSchema = z.object({ - type: z.nativeEnum(ApproveOptions).refine((v) => v, { - params: error.validation.radioErrorMessage, + approveTerms: z.array(z.string()).refine((v) => v && v.length === 1, { + params: error.validation.approveSpouse, + }), }), -}) - -const incomeFilesSchema = z - .array(fileSchema) - .refine((v) => v.length > 0, { params: error.validation.missingFiles }) - -const employmentSchema = z - .object({ - type: z.nativeEnum(Employment).refine((v) => v, { - params: error.validation.radioErrorMessage, + childrenSchoolInfo: z.array( + z.object({ + fullName: z.string(), + nationalId: z.string(), + school: z.string(), + livesWithApplicant: z.boolean(), + livesWithBothParents: z.boolean(), }), - custom: z.string().optional(), - }) - .refine((v) => (v.type === Employment.OTHER ? v.custom : true), { - params: error.validation.inputErrorMessage, - path: ['custom'], - }) - -const bankInfoSchema = z.object({ - bankNumber: z.string().optional(), - ledger: z.string().optional(), - accountNumber: z.string().optional(), -}) - -const personalTexCreditSchema = z.object({ - type: z.nativeEnum(ApproveOptions).refine((v) => v, { + ), + childrenComment: z.string().optional(), + relationshipStatus: z + .object({ + unregisteredCohabitation: z + .enum([ApproveOptions.Yes, ApproveOptions.No]) + .refine((v) => v, { + params: error.validation.radioErrorMessage, + }), + spouseEmail: z.string().optional(), + spouseNationalId: z.string().optional(), + spouseApproveTerms: z.array(z.string()).optional(), + }) + .refine( + (v) => + v.unregisteredCohabitation === ApproveOptions.Yes + ? v.spouseEmail && + isValidEmail(v.spouseEmail) && + v.spouseNationalId && + isValidNationalId(v.spouseNationalId) && + v.spouseApproveTerms && + v.spouseApproveTerms.length === 1 + : true, + { + //More detailed error messages are in the UnknownRelationshipForm component + params: error.validation.email, + }, + ), + student: z + .object({ + isStudent: z + .enum([ApproveOptions.Yes, ApproveOptions.No]) + .refine((v) => v, { + params: error.validation.radioErrorMessage, + }), + custom: z.string().optional(), + }) + .refine((v) => (v.isStudent === ApproveOptions.Yes ? v.custom : true), { + params: error.validation.inputErrorMessage, + }), + homeCircumstances: z + .object({ + type: z + .enum([ + HomeCircumstances.WITHPARENTS, + HomeCircumstances.WITHOTHERS, + HomeCircumstances.OWNPLACE, + HomeCircumstances.REGISTEREDLEASE, + HomeCircumstances.UNREGISTEREDLEASE, + HomeCircumstances.OTHER, + ]) + .refine((v) => v, { + params: error.validation.radioErrorMessage, + }), + custom: z.string().optional(), + }) + .refine((v) => (v.type === HomeCircumstances.OTHER ? v.custom : true), { + params: error.validation.inputErrorMessage, + path: ['custom'], + }), + income: z.enum([ApproveOptions.Yes, ApproveOptions.No]).refine((v) => v, { params: error.validation.radioErrorMessage, }), -}) - -const contactInfoSchema = z.object({ - email: z.string().refine((v) => isValidEmail(v), { - params: error.validation.email, - }), - phone: z.string().refine((v) => isValidPhone(v), { - params: error.validation.phone, - }), -}) - -const spouseIncomeSchema = z.object({ - type: z.nativeEnum(ApproveOptions).refine((v) => v, { - params: error.validation.radioErrorMessage, + employment: z + .object({ + type: z + .enum([ + Employment.WORKING, + Employment.UNEMPLOYED, + Employment.CANNOTWORK, + Employment.OTHER, + ]) + .refine((v) => v, { + params: error.validation.radioErrorMessage, + }), + custom: z.string().optional(), + }) + .refine((v) => (v.type === Employment.OTHER ? v.custom : true), { + params: error.validation.inputErrorMessage, + path: ['custom'], + }), + bankInfo: z.object({ + bankNumber: z.string().optional(), + ledger: z.string().optional(), + accountNumber: z.string().optional(), }), -}) - -const spouseContactInfoSchema = z.object({ - email: z.string().refine((v) => isValidEmail(v), { - params: error.validation.email, + personalTaxCredit: z + .enum([ApproveOptions.Yes, ApproveOptions.No]) + .refine((v) => v, { + params: error.validation.radioErrorMessage, + }), + formComment: z.string().optional(), + contactInfo: z.object({ + email: z.string().refine((v) => isValidEmail(v), { + params: error.validation.email, + }), + phone: z.string().refine((v) => isValidPhone(v), { + params: error.validation.phone, + }), }), - phone: z.string().refine((v) => isValidPhone(v), { - params: error.validation.phone, + spouseIncome: z + .enum([ApproveOptions.Yes, ApproveOptions.No]) + .refine((v) => v, { + params: error.validation.radioErrorMessage, + }), + spouseContactInfo: z.object({ + email: z.string().refine((v) => isValidEmail(v), { + params: error.validation.email, + }), + phone: z.string().refine((v) => isValidPhone(v), { + params: error.validation.phone, + }), }), -}) - -export const dataSchema = z.object({ - // Validation for ApplicationForm - approveExternalData: approveExternalDataSchema, - spouse: spouseSchema, - childrenSchoolInfo: z.array(childSchoolInfoSchema), - childrenComment: z.string().optional(), - relationshipStatus: relationshipStatusScema, - student: studentSchema, - homeCircumstances: homeCircumstancesSchema, - income: incomeSchema, - incomeFiles: incomeFilesSchema, - taxReturnFiles: z.array(fileSchema).optional(), - spouseIncomeFiles: z.array(fileSchema).optional(), - spouseTaxReturnFiles: z.array(fileSchema).optional(), - childrenFiles: z.array(fileSchema).optional(), - employment: employmentSchema, - bankInfo: bankInfoSchema, - personalTaxCredit: personalTexCreditSchema, - contactInfo: contactInfoSchema, - formComment: z.string().optional(), - // Validation for SpouseForm - approveExternalDataSpouse: approveExternalDataSchema, - spouseIncome: spouseIncomeSchema, - spouseContactInfo: spouseContactInfoSchema, spouseFormComment: z.string().optional(), spouseName: z.string().optional(), }) -export type AnswersSchema = z.infer +export type answersSchema = z.infer diff --git a/libs/application/templates/financial-aid/src/lib/formatters.ts b/libs/application/templates/financial-aid/src/lib/formatters.ts index 1bd6b8510865..df808d6f5357 100644 --- a/libs/application/templates/financial-aid/src/lib/formatters.ts +++ b/libs/application/templates/financial-aid/src/lib/formatters.ts @@ -12,15 +12,14 @@ import format from 'date-fns/format' import * as m from './messages' import { Routes } from './constants' -import { ApproveOptions } from './types' -import { findFamilyStatus } from './utils' import { + ApproveOptions, ExternalData, - FormValue, - NationalRegistryIndividual, -} from '@island.is/application/types' -import { AnswersSchema } from './dataSchema' -import { getValueViaPath } from '@island.is/application/core' + OverrideAnswerSchema, + SchoolType, +} from './types' +import { findFamilyStatus } from './utils' +import { NationalRegistryIndividual } from '@island.is/application/types' export const getMessageHomeCircumstances: KeyMapping< HomeCircumstances, @@ -90,70 +89,64 @@ export const formatBankInfo = (bankInfo: { bankInfo?.accountNumber : '' -export const formItems = (answers: FormValue, externalData: ExternalData) => { - const answersSchema = answers as AnswersSchema - const homeCircumstancesType = answersSchema?.homeCircumstances?.type - const isStudent = answersSchema?.student?.isStudent - - return [ - { - route: Routes.INRELATIONSHIP, - label: m.inRelationship.general.sectionTitle, - info: getMessageFamilyStatus[findFamilyStatus(answers, externalData)], - }, - { - route: Routes.HOMECIRCUMSTANCES, - label: m.homeCircumstancesForm.general.sectionTitle, - info: - homeCircumstancesType === HomeCircumstances.OTHER - ? answersSchema?.homeCircumstances?.custom - : getMessageHomeCircumstances[homeCircumstancesType], - }, - { - route: Routes.STUDENT, - label: m.studentForm.general.sectionTitle, - info: getMessageApproveOptions[isStudent], - comment: - isStudent === ApproveOptions.Yes - ? answersSchema?.student?.custom - : undefined, - }, - { - route: Routes.EMPLOYMENT, - label: m.employmentForm.general.sectionTitle, - info: - answersSchema?.employment?.type === Employment.OTHER - ? answersSchema?.employment.custom - : getMessageEmploymentStatus[answersSchema?.employment?.type], - }, - { - route: Routes.INCOME, - label: m.incomeForm.general.sectionTitle, - info: getMessageApproveOptionsForIncome[answersSchema?.income.type], - }, - { - route: Routes.PERSONALTAXCREDIT, - label: m.summaryForm.formInfo.personalTaxCreditTitle, - info: getMessageApproveOptions[answersSchema?.personalTaxCredit.type], - }, - { - route: Routes.BANKINFO, - label: m.bankInfoForm.general.sectionTitle, - info: formatBankInfo(answersSchema?.bankInfo), - }, - ] -} - -export const spouseFormItems = (answers: FormValue) => { - const type = getValueViaPath(answers, 'spouseIncome.type') - return [ - { - route: Routes.SPOUSEINCOME, - label: m.incomeForm.general.sectionTitle, - info: getMessageApproveOptionsForIncome[type ?? ApproveOptions.No], - }, - ] -} +export const formItems = ( + answers: OverrideAnswerSchema, + externalData: ExternalData, +) => [ + { + route: Routes.INRELATIONSHIP, + label: m.inRelationship.general.sectionTitle, + info: getMessageFamilyStatus[findFamilyStatus(answers, externalData)], + }, + { + route: Routes.HOMECIRCUMSTANCES, + label: m.homeCircumstancesForm.general.sectionTitle, + info: + answers?.homeCircumstances?.type === HomeCircumstances.OTHER + ? answers?.homeCircumstances?.custom + : getMessageHomeCircumstances[answers?.homeCircumstances?.type], + }, + { + route: Routes.STUDENT, + label: m.studentForm.general.sectionTitle, + info: getMessageApproveOptions[answers?.student?.isStudent], + comment: + answers?.student?.isStudent === ApproveOptions.Yes + ? answers?.student?.custom + : undefined, + }, + { + route: Routes.EMPLOYMENT, + label: m.employmentForm.general.sectionTitle, + info: + answers?.employment?.type === Employment.OTHER + ? answers?.employment.custom + : getMessageEmploymentStatus[answers?.employment?.type], + }, + { + route: Routes.INCOME, + label: m.incomeForm.general.sectionTitle, + info: getMessageApproveOptionsForIncome[answers?.income], + }, + { + route: Routes.PERSONALTAXCREDIT, + label: m.summaryForm.formInfo.personalTaxCreditTitle, + info: getMessageApproveOptions[answers?.personalTaxCredit], + }, + { + route: Routes.BANKINFO, + label: m.bankInfoForm.general.sectionTitle, + info: formatBankInfo(answers?.bankInfo), + }, +] + +export const spouseFormItems = (answers: OverrideAnswerSchema) => [ + { + route: Routes.SPOUSEINCOME, + label: m.incomeForm.general.sectionTitle, + info: getMessageApproveOptionsForIncome[answers?.spouseIncome], + }, +] export const getStateMessageAndColor: KeyMapping< ApplicationState, diff --git a/libs/application/templates/financial-aid/src/lib/hooks/useApplication.ts b/libs/application/templates/financial-aid/src/lib/hooks/useApplication.ts index 02bc73e79e9a..f9c95db835ef 100644 --- a/libs/application/templates/financial-aid/src/lib/hooks/useApplication.ts +++ b/libs/application/templates/financial-aid/src/lib/hooks/useApplication.ts @@ -1,8 +1,8 @@ import { gql, useMutation, useQuery } from '@apollo/client' import { + Application, ApplicationEventType, ApplicationState, - Application as FinancialAidAnswers, } from '@island.is/financial-aid/shared/lib' export const ApplicationQuery = gql` @@ -57,7 +57,7 @@ export const ApplicationMutation = gql` const useApplication = (id?: string) => { const { data, loading } = useQuery<{ - municipalitiesFinancialAidApplication: FinancialAidAnswers + municipalitiesFinancialAidApplication: Application }>(ApplicationQuery, { variables: { input: { id } }, fetchPolicy: 'no-cache', @@ -65,7 +65,7 @@ const useApplication = (id?: string) => { }) const [updateApplicationMutation] = useMutation<{ - updateMunicipalitiesFinancialAidApplication: FinancialAidAnswers + updateMunicipalitiesFinancialAidApplication: Application }>(ApplicationMutation) const updateApplication = async ( diff --git a/libs/application/templates/financial-aid/src/lib/messages/aboutForm.ts b/libs/application/templates/financial-aid/src/lib/messages/aboutForm.ts index 7f11e8e37130..f1a73448235d 100644 --- a/libs/application/templates/financial-aid/src/lib/messages/aboutForm.ts +++ b/libs/application/templates/financial-aid/src/lib/messages/aboutForm.ts @@ -13,7 +13,7 @@ export const aboutForm = { description: 'About form page title', }, description: { - id: 'fa.application:section.aboutForm.general.description#markdown', + id: 'fa.application:section.aboutForm.general.description', defaultMessage: 'Þú ert að sækja um fjárhagsaðstoð hjá þínu sveitarfélagi fyrir {currentMonth} mánuð. Áður en þú heldur áfram er gott að hafa eftirfarandi í huga:', description: 'About form page description', diff --git a/libs/application/templates/financial-aid/src/lib/messages/aboutSpouseForm.ts b/libs/application/templates/financial-aid/src/lib/messages/aboutSpouseForm.ts index 0096f39d76db..f43987110246 100644 --- a/libs/application/templates/financial-aid/src/lib/messages/aboutSpouseForm.ts +++ b/libs/application/templates/financial-aid/src/lib/messages/aboutSpouseForm.ts @@ -15,7 +15,7 @@ export const aboutSpouseForm = { description: { id: 'fa.application:section.aboutSpouseForm.general.description#markdown', defaultMessage: - 'Maki þinn ({spouseName}) hefur sótt um fjárhagsaðstoð fyrir {currentMonth} mánuð.\n\n Svo hægt sé að klára umsóknina þurfum við að fá þig til að hlaða upp **tekjugögnum** til að reikna út fjárhagsaðstoð til útgreiðslu í byrjun {nextMonth}.', + 'Maki þinn ({spouseName}) hefur sótt um fjárhagsaðstoð fyrir {currentMonth} mánuð.\n\n Svo hægt sé að klára umsóknina þurfum við að fá þig til að hlaða upp **tekjuagögnum** til að reikna út fjárhagsaðstoð til útgreiðslu í byrjun {nextMonth}.', description: 'About spouse form page description', }, }), diff --git a/libs/application/templates/financial-aid/src/lib/messages/error.ts b/libs/application/templates/financial-aid/src/lib/messages/error.ts index c124efdf0204..2cb9a24dc01a 100644 --- a/libs/application/templates/financial-aid/src/lib/messages/error.ts +++ b/libs/application/templates/financial-aid/src/lib/messages/error.ts @@ -32,11 +32,6 @@ export const error = { defaultMessage: 'Athugaðu hvort kennitala sé rétt slegin inn', description: 'Error message when national id is invalid or not present', }, - missingFiles: { - id: 'fa.application:error.missingFile', - defaultMessage: 'Viðhengi vantar', - description: 'Error message when there are no files', - }, approveSpouse: { id: 'fa.application:error.approveSpouse', defaultMessage: 'Þú þarft að samþykkja', diff --git a/libs/application/templates/financial-aid/src/lib/messages/incomeForm.ts b/libs/application/templates/financial-aid/src/lib/messages/incomeForm.ts index d1f7215d114f..b0051a6d96a3 100644 --- a/libs/application/templates/financial-aid/src/lib/messages/incomeForm.ts +++ b/libs/application/templates/financial-aid/src/lib/messages/incomeForm.ts @@ -21,11 +21,19 @@ export const incomeForm = { }, }), examplesOfIncome: defineMessages({ - incomeExampleList: { - id: 'fa.application:section.incomeForm.examplesOfIncome.incomeExampleList#markdown', + leftSidedList: { + id: 'fa.application:section.incomeForm.examplesOfIncome.leftSidedList#markdown', defaultMessage: - '* Greiðslur frá atvinnurekanda \n* Greiðslur frá Vinnumálastofnun \n* Greiðslur frá Tryggingastofnun \n* Greiðslur frá fæðingarorlofssjóði \n* Greiðslur frá Sjúkratryggingum Íslands \n* Styrkir frá lífeyrissjóðum', - description: 'Income form bullet list of examples of income', + '* Greiðslur frá atvinnurekanda \n* Greiðslur frá Vinnumálastofnun \n* Greiðslur frá Tryggingastofnun', + description: + 'Income form bullet list of examples of income, list is on the left side until window size is mobile', + }, + rightSidedList: { + id: 'fa.application:section.incomeForm.examplesOfIncome.rightSidedList#markdown', + defaultMessage: + '* Greiðslur frá fæðingarorlofssjóði \n* Greiðslur frá Sjúkratryggingum Íslands \n* Styrkir frá lífeyrissjóðum', + description: + 'Income form bullet list of examples of income, list is on the right side until window size is mobile', }, }), summary: defineMessages({ diff --git a/libs/application/templates/financial-aid/src/lib/messages/missingFiles.ts b/libs/application/templates/financial-aid/src/lib/messages/missingFiles.ts index 25e74a9b73b8..20d227008478 100644 --- a/libs/application/templates/financial-aid/src/lib/messages/missingFiles.ts +++ b/libs/application/templates/financial-aid/src/lib/messages/missingFiles.ts @@ -57,14 +57,9 @@ export const missingFiles = { }, }), confirmation: defineMessages({ - sectionTitle: { - id: 'fa.application:section.missingFiles.confirmation.sectionTitle', - defaultMessage: 'Yfirlit', - description: 'Title of the confirmation page', - }, title: { id: 'fa.application:section.missingFiles.confirmation.title', - defaultMessage: 'Yfirlit', + defaultMessage: 'Senda inn gögn', description: 'Title of the confirmation page', }, subtitle: { @@ -87,18 +82,5 @@ export const missingFiles = { defaultMessage: 'Skjal', description: 'Text for the file title', }, - openServicePortalMessageText: { - id: 'application.system:openServicePortal.messageText', - defaultMessage: - 'Upplýsingar í mínum síðum og í appi hefur þú aðgang að margvíslegum upplýsingum s.s stafrænt pósthólf, þínar upplýsingar, fjármál, umsóknir, menntun, fasteignir, ökutæki, skírteini, starfsleyfi ofl.', - description: - 'Text for form builder component left side of button to go to the service portal', - }, - openServicePortalButtonTitle: { - id: 'application.system:openServicePortal.buttonTitle', - defaultMessage: 'Áfram', - description: - 'Button text for form builder component, go to service portal', - }, }), } diff --git a/libs/application/templates/financial-aid/src/lib/messages/privacyPolicyAccordion.ts b/libs/application/templates/financial-aid/src/lib/messages/privacyPolicyAccordion.ts index 097d12bee15b..c24ed187ec8b 100644 --- a/libs/application/templates/financial-aid/src/lib/messages/privacyPolicyAccordion.ts +++ b/libs/application/templates/financial-aid/src/lib/messages/privacyPolicyAccordion.ts @@ -17,8 +17,20 @@ export const privacyPolicyAccordion = { about: { id: 'fa.application:section.privacyPolicyAccordion.accordion.about#markdown', defaultMessage: - '**Umsókn um fjárhagsaðstoð**\n\n Til þess að geta unnið úr umsókn þinni og lagt mat á rétt þinn til fjárhagsaðstoðar er sveitarfélaginu nauðsynlegt að vinna með nánar tilgreindar persónuupplýsingar þínar. Unnið er með upplýsingar sem þú lætur af hendi í umsóknarferlinu en einnig aflar sveitarfélagið upplýsinga um þig frá þriðju aðilum.\n\n Upplýsingarnar gætu verið notaðar til vinnslu tölfræðirannsókna.\n\n Í samræmi við lög um opinber skjalasöfn þá varðveitir sveitarélagið upplýsingarnar ótímabundið.\n\n Verði umbeðnar nauðsynlegar upplýsingar ekki veittar kann það að leiða til þess að ekki er unnt að verða við umsókn þessari.\n\n **Tilgangur vinnslu og lagagrundvöllur**\n\n Persónuupplýsingar þær sem óskað er eftir á umsóknarformi þessu og unnið er með, eru nauðsynlegar sveitarfélaginu til að geta metið og tryggt rétt umsækjanda til þjónustu í samræmi við umsókn þessa með vísan til lagaskyldu samkvæmt lögum um félagsþjónustu sveitarfélaga.\n\n **Hvaða upplýsingar er unnið með?**\n\n Það fer því eftir stöðu þinni hvaða upplýsingar sveitarfélaginu er nauðsynlegt að vinna með í tengslum við umsókn þína. Ákveðnar grunnupplýsingar eru hins vegar unnar um alla umsækjendur sem óska eftir fjárhagsaðstoð. Unnið er með eftirfarandi upplýsingar með hliðsjón af stöðu umsækjanda.\n\n **Allir umsækjendur** \n\n Nafn, lögheimili/aðsetur, kyn, hjúskaparstöðu, fjölskyldunúmer, fjölskyldugerð, kennitölu, símanúmer, netfang, stöðu umsækjanda, húsnæðisstaða, skattskyldar tekjur á yfirstandandi ári og allt árið á undan, álagningaskrá: eignir og skuldir auk virðisaukaskattskrá, upplýsingar um ofgreiðslur, bankareikningur, tímabil umsóknar, eðli umsóknar og aðrar upplýsingar sem umsækjandi vill koma á framfæri í umsóknarferli. Ef umsækjandi hefur einhvern á sinni framfærslu þá eru jafnframt sóttar eftirtaldar upplýsingar slíkra aðila frá Þjóðskrá sem eru nafn, kennitala og lögheimili. Ef umsækjandi greiðir meðlag er jafnframt óskað eftir upplýsingum um slíkar greiðslur. \n\n **Vinnufærir umsækjendur** \n\n Minnisblað atvinnuleitanda sem kallað er eftir frá umsækjanda. Staðfesting frá Vinnumálastofnun um rétt til atvinnuleysisbóta ásamt staðfestingu á skráningu á Atvinnutorgi.\n\n **Óvinnufærir umsækjendur**\n\n Sjúkradagpeningavottorð og læknisvottorð frá umsækjanda auk upplýsinga um greiðslur frá stéttarfélagi sem óskað er eftir frá Ríkisskattstjóra.\n\n Umsækjendur sem eru örorku-, endurhæfingar- eða ellilífeyrisþegar\n\n Sundurliðaðar tekjur og greiðslur frá Tryggingastofnun ríkisins í umsóknarmánuði og mánuði þar á undan. Upplýsingar um mæðra- og feðralaun og/eða makabætur, eftir því sem við á. \n\n **Annað**\n\n Ef umsækjandi er á leigumarkaði þá kann að vera óskað eftir afriti af þinglýstum leigusamningi frá umsækjanda. Dvalarleyfisskírteini ef umsækjandi er erlendur ríkisborgari utan Evrópska efnahagssvæðisins. Vottorð frá sýslumanni varðandi hjúskapar/sambúðarslit, ef við á.\n\n **Hvaðan koma upplýsingarnar?**\n\n Auk þeirra persónuupplýsinga sem þú veitir sveitarfélaginu í umsóknarferlinu þá er kallað eftir grunnupplýsingum um þig frá Þjóðskrá og fjárhagsupplýsingum frá Ríkisskattstjóra auk þess sem auðkenning þín er sótt til þjónustuveitanda auðkenningarþjónustu. Þá kann sveitarfélagið að kalla eftir persónuupplýsingum frá Vinnumálastofnun, Tryggingastofnun ríkisins og sýslumanni. \n\n ** Nánar um vinnslu persónuupplýsinga** \n\n Frekari upplýsingar um vinnslu persónuupplýsinga hjá sveitarfélaginu má finna í persónuverndarstefnu þess sem aðgengileg er á {webInfo}.\n\n.', + '**Umsókn um fjárhagsaðstoð**\n\n Til þess að geta unnið úr umsókn þinni og lagt mat á rétt þinn til fjárhagsaðstoðar er sveitarfélaginu nauðsynlegt að vinna með nánar tilgreindar persónuupplýsingar þínar. Unnið er með upplýsingar sem þú lætur af hendi í umsóknarferlinu en einnig aflar sveitarfélagið upplýsinga um þig frá þriðju aðilum.\n\n Upplýsingarnar gætu verið notaðar til vinnslu tölfræðirannsókna. \n\n Í samræmi við lög um opinber skjalasöfn þá varðveitir sveitarélagið upplýsingarnar ótímabundið. \n\n Verði umbeðnar nauðsynlegar upplýsingar ekki veittar kann það að leiða til þess að ekki er unnt að verða við umsókn þessari.\n\n**Tilgangur vinnslu og lagagrundvöllur** \n\n Persónuupplýsingar þær sem óskað er eftir á umsóknarformi þessu og unnið er með, eru nauðsynlegar sveitarfélaginu til að geta metið og tryggt rétt umsækjanda til þjónustu í samræmi við umsókn þessa með vísan til lagaskyldu samkvæmt lögum um félagsþjónustu sveitarfélaga. \n\n **Hvaða upplýsingar er unnið með?**\n\n Það fer því eftir stöðu þinni hvaða upplýsingar sveitarfélaginu er nauðsynlegt að vinna með í tengslum við umsókn þína. Ákveðnar grunnupplýsingar eru hins vegar unnar um alla umsækjendur sem óska eftir fjárhagsaðstoð. Unnið er með eftirfarandi upplýsingar með hliðsjón af stöðu umsækjanda. \n\n **Allir umsækjendur** \n\n Nafn, lögheimili/aðsetur, kyn, hjúskaparstöðu, fjölskyldunúmer, fjölskyldugerð, kennitölu, símanúmer, netfang, stöðu umsækjanda, húsnæðisstaða, skattskyldar tekjur á yfirstandandi ári og allt árið á undan, álagningaskrá: eignir og skuldir auk virðisaukaskattskrá, upplýsingar um ofgreiðslur, bankareikningur, tímabil umsóknar, eðli umsóknar og aðrar upplýsingar sem umsækjandi vill koma á framfæri í umsóknarferli. Ef umsækjandi hefur einhvern á sinni framfærslu þá eru jafnframt sóttar eftirtaldar upplýsingar slíkra aðila frá Þjóðskrá sem eru nafn, kennitala og lögheimili. Ef umsækjandi greiðir meðlag er jafnframt óskað eftir upplýsingum um slíkar greiðslur. \n\n **Vinnufærir umsækjendur** \n\n Minnisblað atvinnuleitanda sem kallað er eftir frá umsækjanda. Staðfesting frá Vinnumálastofnun um rétt til atvinnuleysisbóta ásamt staðfestingu á skráningu á Atvinnutorgi.\n\n **Óvinnufærir umsækjendur**\n\nSjúkradagpeningavottorð og læknisvottorð frá umsækjanda auk upplýsinga um greiðslur frá stéttarfélagi sem óskað er eftir frá Ríkisskattstjóra.\n\nUmsækjendur sem eru örorku-, endurhæfingar- eða ellilífeyrisþegar\n\nSundurliðaðar tekjur og greiðslur frá Tryggingastofnun ríkisins í umsóknarmánuði og mánuði þar á undan. Upplýsingar um mæðra- og feðralaun og/eða makabætur, eftir því sem við á. \n\n**Annað**\n\nEf umsækjandi er á leigumarkaði þá kann að vera óskað eftir afriti af þinglýstum leigusamningi frá umsækjanda. Dvalarleyfisskírteini ef umsækjandi er erlendur ríkisborgari utan Evrópska efnahagssvæðisins. Vottorð frá sýslumanni varðandi hjúskapar/sambúðarslit, ef við á. \n\n**Hvaðan koma upplýsingarnar?**\n\nAuk þeirra persónuupplýsinga sem þú veitir sveitarfélaginu í umsóknarferlinu þá er kallað eftir grunnupplýsingum um þig frá Þjóðskrá og fjárhagsupplýsingum frá Ríkisskattstjóra auk þess sem auðkenning þín er sótt til þjónustuveitanda auðkenningarþjónustu. Þá kann sveitarfélagið að kalla eftir persónuupplýsingum frá Vinnumálastofnun, Tryggingastofnun ríkisins og sýslumanni. \n\n ** Nánar um vinnslu persónuupplýsinga** \n\n', description: 'Privacy policy accordion content', }, + moreInfo: { + id: 'fa.application:section.privacyPolicyAccordion.accordion.moreInfo', + defaultMessage: + 'Frekari upplýsingar um vinnslu persónuupplýsinga hjá sveitarfélaginu má finna í persónuverndarstefnu þess sem aðgengileg er á vefsíðu sveitarfélagsins.', + description: 'Privacy policy accordion more info text', + }, + moreInfoHomepage: { + id: 'fa.application:section.privacyPolicyAccordion.accordion.moreInfoHomepage#markdown', + defaultMessage: + 'Frekari upplýsingar um vinnslu persónuupplýsinga hjá sveitarfélaginu má finna í persónuverndarstefnu þess sem aðgengileg er á vefsíðunni [{homePageNameUrl}]({homePageNameUrl}).', + description: 'Privacy policy accordion more info text with homepage', + }, }), } diff --git a/libs/application/templates/financial-aid/src/lib/messages/unknownRelationship.ts b/libs/application/templates/financial-aid/src/lib/messages/unknownRelationship.ts index 45d74f8125d8..6b9a780ae044 100644 --- a/libs/application/templates/financial-aid/src/lib/messages/unknownRelationship.ts +++ b/libs/application/templates/financial-aid/src/lib/messages/unknownRelationship.ts @@ -52,7 +52,7 @@ export const unknownRelationship = { }, spouseNationalIdPlaceholder: { id: 'fa.application:section.personalInterest.unknownRelationship.inputs.spouseNationalIdPlaceholder', - defaultMessage: 'Sláðu inn kennitölu maka', + defaultMessage: 'Sláðu inn netfang maka', description: 'Spouse national id input placeholder', }, spouseEmail: { diff --git a/libs/application/templates/financial-aid/src/lib/types.ts b/libs/application/templates/financial-aid/src/lib/types.ts index 48f62d65f3ee..5ee270b2a4f1 100644 --- a/libs/application/templates/financial-aid/src/lib/types.ts +++ b/libs/application/templates/financial-aid/src/lib/types.ts @@ -1,6 +1,8 @@ import { ApplicantChildCustodyInformation, + Application, ApplicationAnswerFile, + FieldBaseProps, NationalRegistryIndividual, NationalRegistrySpouse, } from '@island.is/application/types' @@ -9,18 +11,18 @@ import { Municipality, PersonalTaxReturn, } from '@island.is/financial-aid/shared/lib' -import { AnswersSchema } from './dataSchema' +import { answersSchema } from './dataSchema' export enum ApproveOptions { Yes = 'Yes', No = 'No', } -export type FinancialAidAnswers = AnswersSchema +type Override = Omit & T2 -export type ErrorSchema = NestedType +export type ErrorSchema = NestedType -export interface FinancialAidExternalData { +export interface ExternalData { nationalRegistry: { data: NationalRegistryIndividual date: string @@ -70,14 +72,27 @@ export type NestedType = { : string } -export type ChildrenSchoolInfo = { - nationalId: string - school: string - fullName: string - livesWithApplicant?: boolean - livesWithBothParents?: boolean +export interface OverrideAnswerSchema extends answersSchema { + incomeFiles: ApplicationAnswerFile[] + taxReturnFiles: ApplicationAnswerFile[] + spouseIncomeFiles: ApplicationAnswerFile[] + spouseTaxReturnFiles: ApplicationAnswerFile[] + childrenFiles: ApplicationAnswerFile[] } +export type FAApplication = Override< + Application, + { + answers: OverrideAnswerSchema + externalData: ExternalData + } +> + +export type FAFieldBaseProps = Override< + FieldBaseProps, + { application: FAApplication; errors: ErrorSchema } +> + export interface TaxData { municipalitiesPersonalTaxReturn: { personalTaxReturn: PersonalTaxReturn | null @@ -105,8 +120,6 @@ export type UploadFileType = | 'taxReturnFiles' | 'spouseIncomeFiles' | 'spouseTaxReturnFiles' - | 'missingFiles' - | 'missingFilesSpouse' export enum SummaryComment { FORMCOMMENT = 'formComment', @@ -119,13 +132,3 @@ export enum SchoolType { ELEMENTARY = 'elementary', HIGHSCHOOL = 'highSchool', } - -export interface TaxData { - municipalitiesPersonalTaxReturn: { - personalTaxReturn: PersonalTaxReturn | null - } - municipalitiesDirectTaxPayments: { - directTaxPayments: DirectTaxPayment[] - success: boolean - } -} diff --git a/libs/application/templates/financial-aid/src/lib/utils.ts b/libs/application/templates/financial-aid/src/lib/utils.ts index c4e3afe6080d..8bee37ef8f45 100644 --- a/libs/application/templates/financial-aid/src/lib/utils.ts +++ b/libs/application/templates/financial-aid/src/lib/utils.ts @@ -4,22 +4,26 @@ import { getValueViaPath } from '@island.is/application/core' import { ApplicantChildCustodyInformation, ApplicationContext, - ExternalData, - FormValue, } from '@island.is/application/types' + import { FamilyStatus, MartialStatusType, martialStatusTypeFromMartialCode, Municipality, } from '@island.is/financial-aid/shared/lib' -import { ApproveOptions, CurrentApplication, UploadFileType } from '..' + +import { + ApproveOptions, + CurrentApplication, + FAApplication, + OverrideAnswerSchema, + SchoolType, + UploadFileType, +} from '..' import { UploadFile } from '@island.is/island-ui/core' import { ApplicationStates } from './constants' import sortBy from 'lodash/sortBy' -import * as m from '../lib/messages' -import { AnswersSchema } from './dataSchema' -import { isRunningOnEnvironment } from '@island.is/shared/utils' const emailRegex = /^[\w!#$%&'*+/=?`{|}~^-]+(?:\.[\w!#$%&'*+/=?`{|}~^-]+)*@(?:[A-Z0-9-]+\.)+[A-Z]{2,6}$/i @@ -31,17 +35,33 @@ export const isValidPhone = (value: string) => { export const isValidNationalId = (value: string) => kennitala.isValid(value) export function hasSpouseCheck(context: ApplicationContext) { - const { externalData, answers } = context.application + const { externalData, answers } = + context.application as unknown as FAApplication return hasSpouse(answers, externalData) } -export function isMunicipalityNotRegistered(context: ApplicationContext) { +export const hasSpouse = ( + answers: FAApplication['answers'], + externalData: FAApplication['externalData'], +) => { + const nationalRegistrySpouse = externalData.nationalRegistrySpouse.data + + const unregisteredCohabitation = + answers?.relationshipStatus?.unregisteredCohabitation + + return ( + Boolean(nationalRegistrySpouse) || + unregisteredCohabitation === ApproveOptions.Yes + ) +} + +export function isMuncipalityNotRegistered(context: ApplicationContext) { const { externalData } = context.application - const municipality = getValueViaPath( + const municipality = getValueViaPath( externalData, `municipality.data`, - ) + ) as Municipality | null return municipality == null || !municipality.active } @@ -49,55 +69,39 @@ export const encodeFilenames = (filename: string) => filename && encodeURI(filename.normalize().replace(/ +/g, '_')) export function findFamilyStatus( - answers: FormValue, - externalData: ExternalData, + answers: FAApplication['answers'], + externalData: FAApplication['externalData'], ) { - const maritalStatus = getValueViaPath( - externalData, - 'nationalRegistrySpouse.data.maritalStatus', - ) - const unregisteredCohabitation = getValueViaPath( - answers, - 'relationshipStatus.unregisteredCohabitation', - ) - - if ( - martialStatusTypeFromMartialCode(maritalStatus) === - MartialStatusType.MARRIED - ) { - return FamilyStatus.MARRIED + switch (true) { + case martialStatusTypeFromMartialCode( + externalData.nationalRegistrySpouse.data?.maritalStatus, + ) === MartialStatusType.MARRIED: + return FamilyStatus.MARRIED + case externalData.nationalRegistrySpouse.data != null: + return FamilyStatus.COHABITATION + case answers?.relationshipStatus?.unregisteredCohabitation === + ApproveOptions.Yes: + return FamilyStatus.UNREGISTERED_COBAHITATION + default: + return FamilyStatus.NOT_COHABITATION } - - if (externalData.nationalRegistrySpouse.data != null) { - return FamilyStatus.COHABITATION - } - - if (unregisteredCohabitation === ApproveOptions.Yes) { - return FamilyStatus.UNREGISTERED_COBAHITATION - } - - return FamilyStatus.NOT_COHABITATION } export function hasActiveCurrentApplication(context: ApplicationContext) { - // On prod there should only be one active application per user - // When working with gervimaður we might need to have many active applications - const isProd = isRunningOnEnvironment('production') - if (!isProd) { - return false - } - const { externalData } = context.application - const currentApplication = getValueViaPath( + const currentApplication = getValueViaPath( externalData, 'currentApplication.data', - ) + ) as CurrentApplication return currentApplication?.currentApplicationId != null } -export const hasFiles = (fileType: UploadFileType, answers: AnswersSchema) => { - const files = answers[fileType as keyof AnswersSchema] as UploadFile[] +export const hasFiles = ( + fileType: UploadFileType, + answers: OverrideAnswerSchema, +) => { + const files = answers[fileType as keyof OverrideAnswerSchema] as UploadFile[] return files && files.length > 0 } @@ -118,64 +122,3 @@ export const sortChildrenUnderAgeByAge = ( return kennitala.info(child.nationalId)?.birthday }) } - -export const hasSpouse = (answers: FormValue, externalData: ExternalData) => { - const nationalRegistrySpouse = externalData.nationalRegistrySpouse.data - - const unregisteredCohabitation = getValueViaPath( - answers, - 'relationshipStatus.unregisteredCohabitation', - ) - - return ( - Boolean(nationalRegistrySpouse) || - unregisteredCohabitation === ApproveOptions.Yes - ) -} - -export const getNextStepsDescription = ( - answers: FormValue, - externalData: ExternalData, -) => { - const applicantHasSpouse = hasSpouse(answers, externalData) - const missingIncomeFiles = - answers.income === ApproveOptions.Yes && - !hasFiles('incomeFiles', answers as AnswersSchema) - - if (applicantHasSpouse && missingIncomeFiles) { - return m.confirmation.nextSteps.contentBothMissingFiles - } else if (applicantHasSpouse) { - return m.confirmation.nextSteps.contentSpouseMissingFiles - } else if (missingIncomeFiles) { - return m.confirmation.nextSteps.contentMissingFiles - } - - return '' -} - -export const getSpouseNextStepsDescription = ( - answers: FormValue, - externalData: ExternalData, -) => { - const incomeFiles = hasSpouseIncomeFiles(answers) - - return incomeFiles ? '' : m.confirmation.nextSteps.contentMissingFiles -} - -type File = { - name: string - key: string -} - -export const hasIncomeFiles = (formValue: FormValue) => { - const income = formValue.income === ApproveOptions.Yes - const incomeFiles = formValue.incomeFiles as Array - - return income && incomeFiles && incomeFiles.length > 0 -} - -export const hasSpouseIncomeFiles = (formValue: FormValue) => { - const income = formValue.spouseIncomeFiles as Array - - return income && income.length > 0 -} diff --git a/libs/application/templates/financial-aid/src/lib/utils/getApplicantsServiceCenter.ts b/libs/application/templates/financial-aid/src/lib/utils/getApplicantsServiceCenter.ts deleted file mode 100644 index e93f1b7df1a2..000000000000 --- a/libs/application/templates/financial-aid/src/lib/utils/getApplicantsServiceCenter.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { getValueViaPath } from '@island.is/application/core' -import { Application } from '@island.is/application/types' -import { serviceCenters } from '@island.is/financial-aid/shared/data' - -export const getApplicantsServiceCenter = (application: Application) => { - const { externalData } = application - const municipalityCode = getValueViaPath( - externalData, - 'nationalRegistry.data.address.municipalityCode', - ) - - const applicantsCenter = serviceCenters.find( - (serviceCenter) => serviceCenter.number === Number(municipalityCode), - ) - return applicantsCenter -} diff --git a/libs/application/templates/financial-aid/src/lib/utils/getEmploymentOptions.ts b/libs/application/templates/financial-aid/src/lib/utils/getEmploymentOptions.ts deleted file mode 100644 index defd8a098f83..000000000000 --- a/libs/application/templates/financial-aid/src/lib/utils/getEmploymentOptions.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Employment } from '@island.is/financial-aid/shared/lib' -import { employmentForm } from '../messages' - -export const getEmploymentOptions = () => { - const options = [ - { - value: Employment.WORKING, - label: employmentForm.employment.working, - }, - { - value: Employment.UNEMPLOYED, - label: employmentForm.employment.unemployed, - }, - { - value: Employment.CANNOTWORK, - label: employmentForm.employment.cannotWork, - }, - { - value: Employment.OTHER, - label: employmentForm.employment.other, - }, - ] - - return options -} diff --git a/libs/application/templates/financial-aid/src/lib/utils/getHomeCircumstancesOptions.ts b/libs/application/templates/financial-aid/src/lib/utils/getHomeCircumstancesOptions.ts deleted file mode 100644 index 76ed5ad46652..000000000000 --- a/libs/application/templates/financial-aid/src/lib/utils/getHomeCircumstancesOptions.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { HomeCircumstances } from '@island.is/financial-aid/shared/lib' -import { homeCircumstancesForm } from '../messages' - -export const getHomeCircumstancesOptions = () => { - const options = [ - { - value: HomeCircumstances.OWNPLACE, - label: homeCircumstancesForm.circumstances.ownPlace, - }, - { - value: HomeCircumstances.REGISTEREDLEASE, - label: homeCircumstancesForm.circumstances.registeredLease, - }, - { - value: HomeCircumstances.UNREGISTEREDLEASE, - label: homeCircumstancesForm.circumstances.unregisteredLease, - }, - - { - value: HomeCircumstances.WITHOTHERS, - label: homeCircumstancesForm.circumstances.withOthers, - }, - { - value: HomeCircumstances.WITHPARENTS, - label: homeCircumstancesForm.circumstances.withParents, - }, - { - value: HomeCircumstances.OTHER, - label: homeCircumstancesForm.circumstances.other, - }, - ] - - return options -} diff --git a/libs/application/templates/financial-aid/src/lib/utils/getIncomeOptions.ts b/libs/application/templates/financial-aid/src/lib/utils/getIncomeOptions.ts deleted file mode 100644 index 5edc40f31baa..000000000000 --- a/libs/application/templates/financial-aid/src/lib/utils/getIncomeOptions.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { incomeForm } from '../messages' -import { ApproveOptions } from '../types' - -export const getIncomeOptions = () => { - const options = [ - { - value: ApproveOptions.Yes, - label: incomeForm.options.yes, - }, - { - value: ApproveOptions.No, - label: incomeForm.options.no, - }, - ] - return options -} diff --git a/libs/application/templates/financial-aid/src/lib/utils/getPersonalTaxCreditOptions.ts b/libs/application/templates/financial-aid/src/lib/utils/getPersonalTaxCreditOptions.ts deleted file mode 100644 index efccd0da38f9..000000000000 --- a/libs/application/templates/financial-aid/src/lib/utils/getPersonalTaxCreditOptions.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { personalTaxCreditForm } from '../messages' -import { ApproveOptions } from '../types' - -export const getPersonalTaxCreditOptions = () => { - const options = [ - { - value: ApproveOptions.Yes, - label: personalTaxCreditForm.radioChoices.useTaxCredit, - }, - { - value: ApproveOptions.No, - label: personalTaxCreditForm.radioChoices.wontUseTaxCredit, - }, - ] - - return options -} diff --git a/libs/application/templates/financial-aid/src/lib/utils/getStudentOptions.ts b/libs/application/templates/financial-aid/src/lib/utils/getStudentOptions.ts deleted file mode 100644 index fe306da75b69..000000000000 --- a/libs/application/templates/financial-aid/src/lib/utils/getStudentOptions.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { approveOptions } from '../messages' -import { ApproveOptions } from '../types' - -export const getStudentOptions = () => { - const options = [ - { - value: ApproveOptions.No, - label: approveOptions.no, - }, - { - value: ApproveOptions.Yes, - label: approveOptions.yes, - }, - ] - - return options -} diff --git a/libs/application/templates/financial-aid/src/lib/utils/getUnknownRelationshipOptions.tsx b/libs/application/templates/financial-aid/src/lib/utils/getUnknownRelationshipOptions.tsx deleted file mode 100644 index 66083f84666d..000000000000 --- a/libs/application/templates/financial-aid/src/lib/utils/getUnknownRelationshipOptions.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { unknownRelationship } from '../messages' -import { ApproveOptions } from '../types' - -export const getUnknownRelationshipOptions = () => { - const options = [ - { - value: ApproveOptions.No, - label: unknownRelationship.form.radioButtonNo, - }, - { - value: ApproveOptions.Yes, - label: unknownRelationship.form.radioButtonYes, - }, - ] - - return options -} diff --git a/libs/application/templates/financial-aid/src/assets/svg/akrahreppur.svg b/libs/application/templates/financial-aid/src/svg/akrahreppur.svg similarity index 100% rename from libs/application/templates/financial-aid/src/assets/svg/akrahreppur.svg rename to libs/application/templates/financial-aid/src/svg/akrahreppur.svg diff --git a/libs/application/templates/financial-aid/src/assets/svg/akranes.svg b/libs/application/templates/financial-aid/src/svg/akranes.svg similarity index 100% rename from libs/application/templates/financial-aid/src/assets/svg/akranes.svg rename to libs/application/templates/financial-aid/src/svg/akranes.svg diff --git a/libs/application/templates/financial-aid/src/assets/svg/akureyri.svg b/libs/application/templates/financial-aid/src/svg/akureyri.svg similarity index 100% rename from libs/application/templates/financial-aid/src/assets/svg/akureyri.svg rename to libs/application/templates/financial-aid/src/svg/akureyri.svg diff --git a/libs/application/templates/financial-aid/src/assets/svg/arborg.svg b/libs/application/templates/financial-aid/src/svg/arborg.svg similarity index 100% rename from libs/application/templates/financial-aid/src/assets/svg/arborg.svg rename to libs/application/templates/financial-aid/src/svg/arborg.svg diff --git a/libs/application/templates/financial-aid/src/assets/svg/arneshreppur.svg b/libs/application/templates/financial-aid/src/svg/arneshreppur.svg similarity index 100% rename from libs/application/templates/financial-aid/src/assets/svg/arneshreppur.svg rename to libs/application/templates/financial-aid/src/svg/arneshreppur.svg diff --git a/libs/application/templates/financial-aid/src/assets/svg/asahreppur.svg b/libs/application/templates/financial-aid/src/svg/asahreppur.svg similarity index 100% rename from libs/application/templates/financial-aid/src/assets/svg/asahreppur.svg rename to libs/application/templates/financial-aid/src/svg/asahreppur.svg diff --git a/libs/application/templates/financial-aid/src/assets/svg/blaskogabyggd.svg b/libs/application/templates/financial-aid/src/svg/blaskogabyggd.svg similarity index 100% rename from libs/application/templates/financial-aid/src/assets/svg/blaskogabyggd.svg rename to libs/application/templates/financial-aid/src/svg/blaskogabyggd.svg diff --git a/libs/application/templates/financial-aid/src/assets/svg/blonduosbaer.svg b/libs/application/templates/financial-aid/src/svg/blonduosbaer.svg similarity index 100% rename from libs/application/templates/financial-aid/src/assets/svg/blonduosbaer.svg rename to libs/application/templates/financial-aid/src/svg/blonduosbaer.svg diff --git a/libs/application/templates/financial-aid/src/assets/svg/bolungarvik.svg b/libs/application/templates/financial-aid/src/svg/bolungarvik.svg similarity index 100% rename from libs/application/templates/financial-aid/src/assets/svg/bolungarvik.svg rename to libs/application/templates/financial-aid/src/svg/bolungarvik.svg diff --git a/libs/application/templates/financial-aid/src/assets/svg/borgarbyggd.svg b/libs/application/templates/financial-aid/src/svg/borgarbyggd.svg similarity index 100% rename from libs/application/templates/financial-aid/src/assets/svg/borgarbyggd.svg rename to libs/application/templates/financial-aid/src/svg/borgarbyggd.svg diff --git a/libs/application/templates/financial-aid/src/assets/svg/dalabyggd.svg b/libs/application/templates/financial-aid/src/svg/dalabyggd.svg similarity index 100% rename from libs/application/templates/financial-aid/src/assets/svg/dalabyggd.svg rename to libs/application/templates/financial-aid/src/svg/dalabyggd.svg diff --git a/libs/application/templates/financial-aid/src/assets/svg/dalvikurbyggd.svg b/libs/application/templates/financial-aid/src/svg/dalvikurbyggd.svg similarity index 100% rename from libs/application/templates/financial-aid/src/assets/svg/dalvikurbyggd.svg rename to libs/application/templates/financial-aid/src/svg/dalvikurbyggd.svg diff --git a/libs/application/templates/financial-aid/src/assets/svg/eyja-og-miklaholtshreppur.svg b/libs/application/templates/financial-aid/src/svg/eyja-og-miklaholtshreppur.svg similarity index 100% rename from libs/application/templates/financial-aid/src/assets/svg/eyja-og-miklaholtshreppur.svg rename to libs/application/templates/financial-aid/src/svg/eyja-og-miklaholtshreppur.svg diff --git a/libs/application/templates/financial-aid/src/assets/svg/eyjafjardarsveit.svg b/libs/application/templates/financial-aid/src/svg/eyjafjardarsveit.svg similarity index 100% rename from libs/application/templates/financial-aid/src/assets/svg/eyjafjardarsveit.svg rename to libs/application/templates/financial-aid/src/svg/eyjafjardarsveit.svg diff --git a/libs/application/templates/financial-aid/src/assets/svg/fjallabyggd.svg b/libs/application/templates/financial-aid/src/svg/fjallabyggd.svg similarity index 100% rename from libs/application/templates/financial-aid/src/assets/svg/fjallabyggd.svg rename to libs/application/templates/financial-aid/src/svg/fjallabyggd.svg diff --git a/libs/application/templates/financial-aid/src/assets/svg/fjardabyggd.svg b/libs/application/templates/financial-aid/src/svg/fjardabyggd.svg similarity index 100% rename from libs/application/templates/financial-aid/src/assets/svg/fjardabyggd.svg rename to libs/application/templates/financial-aid/src/svg/fjardabyggd.svg diff --git a/libs/application/templates/financial-aid/src/assets/svg/fljotsdalshreppur.svg b/libs/application/templates/financial-aid/src/svg/fljotsdalshreppur.svg similarity index 100% rename from libs/application/templates/financial-aid/src/assets/svg/fljotsdalshreppur.svg rename to libs/application/templates/financial-aid/src/svg/fljotsdalshreppur.svg diff --git a/libs/application/templates/financial-aid/src/assets/svg/floahreppur.svg b/libs/application/templates/financial-aid/src/svg/floahreppur.svg similarity index 100% rename from libs/application/templates/financial-aid/src/assets/svg/floahreppur.svg rename to libs/application/templates/financial-aid/src/svg/floahreppur.svg diff --git a/libs/application/templates/financial-aid/src/assets/svg/gardabaer.svg b/libs/application/templates/financial-aid/src/svg/gardabaer.svg similarity index 100% rename from libs/application/templates/financial-aid/src/assets/svg/gardabaer.svg rename to libs/application/templates/financial-aid/src/svg/gardabaer.svg diff --git a/libs/application/templates/financial-aid/src/assets/svg/grimsnes-og-grafningshreppur.svg b/libs/application/templates/financial-aid/src/svg/grimsnes-og-grafningshreppur.svg similarity index 100% rename from libs/application/templates/financial-aid/src/assets/svg/grimsnes-og-grafningshreppur.svg rename to libs/application/templates/financial-aid/src/svg/grimsnes-og-grafningshreppur.svg diff --git a/libs/application/templates/financial-aid/src/assets/svg/grindavikurbaer.svg b/libs/application/templates/financial-aid/src/svg/grindavikurbaer.svg similarity index 100% rename from libs/application/templates/financial-aid/src/assets/svg/grindavikurbaer.svg rename to libs/application/templates/financial-aid/src/svg/grindavikurbaer.svg diff --git a/libs/application/templates/financial-aid/src/assets/svg/grundafjardarbaer.svg b/libs/application/templates/financial-aid/src/svg/grundafjardarbaer.svg similarity index 100% rename from libs/application/templates/financial-aid/src/assets/svg/grundafjardarbaer.svg rename to libs/application/templates/financial-aid/src/svg/grundafjardarbaer.svg diff --git a/libs/application/templates/financial-aid/src/assets/svg/grytubakkahreppur.svg b/libs/application/templates/financial-aid/src/svg/grytubakkahreppur.svg similarity index 100% rename from libs/application/templates/financial-aid/src/assets/svg/grytubakkahreppur.svg rename to libs/application/templates/financial-aid/src/svg/grytubakkahreppur.svg diff --git a/libs/application/templates/financial-aid/src/assets/svg/hafnarfjordur.svg b/libs/application/templates/financial-aid/src/svg/hafnarfjordur.svg similarity index 100% rename from libs/application/templates/financial-aid/src/assets/svg/hafnarfjordur.svg rename to libs/application/templates/financial-aid/src/svg/hafnarfjordur.svg diff --git a/libs/application/templates/financial-aid/src/assets/svg/helgafellssveit.svg b/libs/application/templates/financial-aid/src/svg/helgafellssveit.svg similarity index 100% rename from libs/application/templates/financial-aid/src/assets/svg/helgafellssveit.svg rename to libs/application/templates/financial-aid/src/svg/helgafellssveit.svg diff --git a/libs/application/templates/financial-aid/src/assets/svg/horgarsveit.svg b/libs/application/templates/financial-aid/src/svg/horgarsveit.svg similarity index 100% rename from libs/application/templates/financial-aid/src/assets/svg/horgarsveit.svg rename to libs/application/templates/financial-aid/src/svg/horgarsveit.svg diff --git a/libs/application/templates/financial-aid/src/assets/svg/hornafjordur.svg b/libs/application/templates/financial-aid/src/svg/hornafjordur.svg similarity index 100% rename from libs/application/templates/financial-aid/src/assets/svg/hornafjordur.svg rename to libs/application/templates/financial-aid/src/svg/hornafjordur.svg diff --git a/libs/application/templates/financial-aid/src/assets/svg/hrunamannahreppur.svg b/libs/application/templates/financial-aid/src/svg/hrunamannahreppur.svg similarity index 100% rename from libs/application/templates/financial-aid/src/assets/svg/hrunamannahreppur.svg rename to libs/application/templates/financial-aid/src/svg/hrunamannahreppur.svg diff --git a/libs/application/templates/financial-aid/src/assets/svg/hunathing-vestra.svg b/libs/application/templates/financial-aid/src/svg/hunathing-vestra.svg similarity index 100% rename from libs/application/templates/financial-aid/src/assets/svg/hunathing-vestra.svg rename to libs/application/templates/financial-aid/src/svg/hunathing-vestra.svg diff --git a/libs/application/templates/financial-aid/src/assets/svg/hunavatnshreppur.svg b/libs/application/templates/financial-aid/src/svg/hunavatnshreppur.svg similarity index 100% rename from libs/application/templates/financial-aid/src/assets/svg/hunavatnshreppur.svg rename to libs/application/templates/financial-aid/src/svg/hunavatnshreppur.svg diff --git a/libs/application/templates/financial-aid/src/assets/svg/hvalfjardarsveit.svg b/libs/application/templates/financial-aid/src/svg/hvalfjardarsveit.svg similarity index 100% rename from libs/application/templates/financial-aid/src/assets/svg/hvalfjardarsveit.svg rename to libs/application/templates/financial-aid/src/svg/hvalfjardarsveit.svg diff --git a/libs/application/templates/financial-aid/src/assets/svg/hveragerdisbaer.svg b/libs/application/templates/financial-aid/src/svg/hveragerdisbaer.svg similarity index 100% rename from libs/application/templates/financial-aid/src/assets/svg/hveragerdisbaer.svg rename to libs/application/templates/financial-aid/src/svg/hveragerdisbaer.svg diff --git a/libs/application/templates/financial-aid/src/assets/svg/isafjardarbaer.svg b/libs/application/templates/financial-aid/src/svg/isafjardarbaer.svg similarity index 100% rename from libs/application/templates/financial-aid/src/assets/svg/isafjardarbaer.svg rename to libs/application/templates/financial-aid/src/svg/isafjardarbaer.svg diff --git a/libs/application/templates/financial-aid/src/assets/svg/kaldrananeshreppur.svg b/libs/application/templates/financial-aid/src/svg/kaldrananeshreppur.svg similarity index 100% rename from libs/application/templates/financial-aid/src/assets/svg/kaldrananeshreppur.svg rename to libs/application/templates/financial-aid/src/svg/kaldrananeshreppur.svg diff --git a/libs/application/templates/financial-aid/src/assets/svg/kjosarhreppur.svg b/libs/application/templates/financial-aid/src/svg/kjosarhreppur.svg similarity index 100% rename from libs/application/templates/financial-aid/src/assets/svg/kjosarhreppur.svg rename to libs/application/templates/financial-aid/src/svg/kjosarhreppur.svg diff --git a/libs/application/templates/financial-aid/src/assets/svg/kopavogur.svg b/libs/application/templates/financial-aid/src/svg/kopavogur.svg similarity index 100% rename from libs/application/templates/financial-aid/src/assets/svg/kopavogur.svg rename to libs/application/templates/financial-aid/src/svg/kopavogur.svg diff --git a/libs/application/templates/financial-aid/src/assets/svg/langanesbyggd.svg b/libs/application/templates/financial-aid/src/svg/langanesbyggd.svg similarity index 100% rename from libs/application/templates/financial-aid/src/assets/svg/langanesbyggd.svg rename to libs/application/templates/financial-aid/src/svg/langanesbyggd.svg diff --git a/libs/application/templates/financial-aid/src/assets/svg/mosfellsbaer.svg b/libs/application/templates/financial-aid/src/svg/mosfellsbaer.svg similarity index 100% rename from libs/application/templates/financial-aid/src/assets/svg/mosfellsbaer.svg rename to libs/application/templates/financial-aid/src/svg/mosfellsbaer.svg diff --git a/libs/application/templates/financial-aid/src/assets/svg/mulathing.svg b/libs/application/templates/financial-aid/src/svg/mulathing.svg similarity index 100% rename from libs/application/templates/financial-aid/src/assets/svg/mulathing.svg rename to libs/application/templates/financial-aid/src/svg/mulathing.svg diff --git a/libs/application/templates/financial-aid/src/assets/svg/myrdalshreppur.svg b/libs/application/templates/financial-aid/src/svg/myrdalshreppur.svg similarity index 100% rename from libs/application/templates/financial-aid/src/assets/svg/myrdalshreppur.svg rename to libs/application/templates/financial-aid/src/svg/myrdalshreppur.svg diff --git a/libs/application/templates/financial-aid/src/assets/svg/nordurthing.svg b/libs/application/templates/financial-aid/src/svg/nordurthing.svg similarity index 100% rename from libs/application/templates/financial-aid/src/assets/svg/nordurthing.svg rename to libs/application/templates/financial-aid/src/svg/nordurthing.svg diff --git a/libs/application/templates/financial-aid/src/assets/svg/olfus.svg b/libs/application/templates/financial-aid/src/svg/olfus.svg similarity index 100% rename from libs/application/templates/financial-aid/src/assets/svg/olfus.svg rename to libs/application/templates/financial-aid/src/svg/olfus.svg diff --git a/libs/application/templates/financial-aid/src/assets/svg/rangarthing-ytra.svg b/libs/application/templates/financial-aid/src/svg/rangarthing-ytra.svg similarity index 100% rename from libs/application/templates/financial-aid/src/assets/svg/rangarthing-ytra.svg rename to libs/application/templates/financial-aid/src/svg/rangarthing-ytra.svg diff --git a/libs/application/templates/financial-aid/src/assets/svg/rangarthing_eystra.svg b/libs/application/templates/financial-aid/src/svg/rangarthing_eystra.svg similarity index 100% rename from libs/application/templates/financial-aid/src/assets/svg/rangarthing_eystra.svg rename to libs/application/templates/financial-aid/src/svg/rangarthing_eystra.svg diff --git a/libs/application/templates/financial-aid/src/assets/svg/reykholahreppur.svg b/libs/application/templates/financial-aid/src/svg/reykholahreppur.svg similarity index 100% rename from libs/application/templates/financial-aid/src/assets/svg/reykholahreppur.svg rename to libs/application/templates/financial-aid/src/svg/reykholahreppur.svg diff --git a/libs/application/templates/financial-aid/src/assets/svg/reykjanesbaer.svg b/libs/application/templates/financial-aid/src/svg/reykjanesbaer.svg similarity index 100% rename from libs/application/templates/financial-aid/src/assets/svg/reykjanesbaer.svg rename to libs/application/templates/financial-aid/src/svg/reykjanesbaer.svg diff --git a/libs/application/templates/financial-aid/src/assets/svg/sambandid.svg b/libs/application/templates/financial-aid/src/svg/sambandid.svg similarity index 100% rename from libs/application/templates/financial-aid/src/assets/svg/sambandid.svg rename to libs/application/templates/financial-aid/src/svg/sambandid.svg diff --git a/libs/application/templates/financial-aid/src/assets/svg/seltjarnarnes.svg b/libs/application/templates/financial-aid/src/svg/seltjarnarnes.svg similarity index 100% rename from libs/application/templates/financial-aid/src/assets/svg/seltjarnarnes.svg rename to libs/application/templates/financial-aid/src/svg/seltjarnarnes.svg diff --git a/libs/application/templates/financial-aid/src/assets/svg/skaftarhreppur.svg b/libs/application/templates/financial-aid/src/svg/skaftarhreppur.svg similarity index 100% rename from libs/application/templates/financial-aid/src/assets/svg/skaftarhreppur.svg rename to libs/application/templates/financial-aid/src/svg/skaftarhreppur.svg diff --git a/libs/application/templates/financial-aid/src/assets/svg/skagabyggd.svg b/libs/application/templates/financial-aid/src/svg/skagabyggd.svg similarity index 100% rename from libs/application/templates/financial-aid/src/assets/svg/skagabyggd.svg rename to libs/application/templates/financial-aid/src/svg/skagabyggd.svg diff --git a/libs/application/templates/financial-aid/src/assets/svg/skagafjordur.svg b/libs/application/templates/financial-aid/src/svg/skagafjordur.svg similarity index 100% rename from libs/application/templates/financial-aid/src/assets/svg/skagafjordur.svg rename to libs/application/templates/financial-aid/src/svg/skagafjordur.svg diff --git a/libs/application/templates/financial-aid/src/assets/svg/skagastrond.svg b/libs/application/templates/financial-aid/src/svg/skagastrond.svg similarity index 100% rename from libs/application/templates/financial-aid/src/assets/svg/skagastrond.svg rename to libs/application/templates/financial-aid/src/svg/skagastrond.svg diff --git a/libs/application/templates/financial-aid/src/assets/svg/skeida-og-gnupverjahreppur.svg b/libs/application/templates/financial-aid/src/svg/skeida-og-gnupverjahreppur.svg similarity index 100% rename from libs/application/templates/financial-aid/src/assets/svg/skeida-og-gnupverjahreppur.svg rename to libs/application/templates/financial-aid/src/svg/skeida-og-gnupverjahreppur.svg diff --git a/libs/application/templates/financial-aid/src/assets/svg/skorradalur.svg b/libs/application/templates/financial-aid/src/svg/skorradalur.svg similarity index 100% rename from libs/application/templates/financial-aid/src/assets/svg/skorradalur.svg rename to libs/application/templates/financial-aid/src/svg/skorradalur.svg diff --git a/libs/application/templates/financial-aid/src/assets/svg/skutustadahreppur.svg b/libs/application/templates/financial-aid/src/svg/skutustadahreppur.svg similarity index 100% rename from libs/application/templates/financial-aid/src/assets/svg/skutustadahreppur.svg rename to libs/application/templates/financial-aid/src/svg/skutustadahreppur.svg diff --git a/libs/application/templates/financial-aid/src/assets/svg/snaefellsbaer.svg b/libs/application/templates/financial-aid/src/svg/snaefellsbaer.svg similarity index 100% rename from libs/application/templates/financial-aid/src/assets/svg/snaefellsbaer.svg rename to libs/application/templates/financial-aid/src/svg/snaefellsbaer.svg diff --git a/libs/application/templates/financial-aid/src/assets/svg/strandabyggd.svg b/libs/application/templates/financial-aid/src/svg/strandabyggd.svg similarity index 100% rename from libs/application/templates/financial-aid/src/assets/svg/strandabyggd.svg rename to libs/application/templates/financial-aid/src/svg/strandabyggd.svg diff --git a/libs/application/templates/financial-aid/src/assets/svg/stykkisholmsbaer.svg b/libs/application/templates/financial-aid/src/svg/stykkisholmsbaer.svg similarity index 100% rename from libs/application/templates/financial-aid/src/assets/svg/stykkisholmsbaer.svg rename to libs/application/templates/financial-aid/src/svg/stykkisholmsbaer.svg diff --git a/libs/application/templates/financial-aid/src/assets/svg/sudavikurhreppur.svg b/libs/application/templates/financial-aid/src/svg/sudavikurhreppur.svg similarity index 100% rename from libs/application/templates/financial-aid/src/assets/svg/sudavikurhreppur.svg rename to libs/application/templates/financial-aid/src/svg/sudavikurhreppur.svg diff --git a/libs/application/templates/financial-aid/src/assets/svg/sudurnesjabaer.svg b/libs/application/templates/financial-aid/src/svg/sudurnesjabaer.svg similarity index 100% rename from libs/application/templates/financial-aid/src/assets/svg/sudurnesjabaer.svg rename to libs/application/templates/financial-aid/src/svg/sudurnesjabaer.svg diff --git a/libs/application/templates/financial-aid/src/assets/svg/svalbardshreppur.svg b/libs/application/templates/financial-aid/src/svg/svalbardshreppur.svg similarity index 100% rename from libs/application/templates/financial-aid/src/assets/svg/svalbardshreppur.svg rename to libs/application/templates/financial-aid/src/svg/svalbardshreppur.svg diff --git a/libs/application/templates/financial-aid/src/assets/svg/svalbardsstrandarhreppur.svg b/libs/application/templates/financial-aid/src/svg/svalbardsstrandarhreppur.svg similarity index 100% rename from libs/application/templates/financial-aid/src/assets/svg/svalbardsstrandarhreppur.svg rename to libs/application/templates/financial-aid/src/svg/svalbardsstrandarhreppur.svg diff --git a/libs/application/templates/financial-aid/src/assets/svg/talknafjardarhreppur.svg b/libs/application/templates/financial-aid/src/svg/talknafjardarhreppur.svg similarity index 100% rename from libs/application/templates/financial-aid/src/assets/svg/talknafjardarhreppur.svg rename to libs/application/templates/financial-aid/src/svg/talknafjardarhreppur.svg diff --git a/libs/application/templates/financial-aid/src/assets/svg/thingeyjarsveit.svg b/libs/application/templates/financial-aid/src/svg/thingeyjarsveit.svg similarity index 100% rename from libs/application/templates/financial-aid/src/assets/svg/thingeyjarsveit.svg rename to libs/application/templates/financial-aid/src/svg/thingeyjarsveit.svg diff --git a/libs/application/templates/financial-aid/src/assets/svg/tjorneshreppur.svg b/libs/application/templates/financial-aid/src/svg/tjorneshreppur.svg similarity index 100% rename from libs/application/templates/financial-aid/src/assets/svg/tjorneshreppur.svg rename to libs/application/templates/financial-aid/src/svg/tjorneshreppur.svg diff --git a/libs/application/templates/financial-aid/src/assets/svg/vestmannaeyjabaer.svg b/libs/application/templates/financial-aid/src/svg/vestmannaeyjabaer.svg similarity index 100% rename from libs/application/templates/financial-aid/src/assets/svg/vestmannaeyjabaer.svg rename to libs/application/templates/financial-aid/src/svg/vestmannaeyjabaer.svg diff --git a/libs/application/templates/financial-aid/src/assets/svg/vesturbyggd.svg b/libs/application/templates/financial-aid/src/svg/vesturbyggd.svg similarity index 100% rename from libs/application/templates/financial-aid/src/assets/svg/vesturbyggd.svg rename to libs/application/templates/financial-aid/src/svg/vesturbyggd.svg diff --git a/libs/application/templates/financial-aid/src/assets/svg/vogar.svg b/libs/application/templates/financial-aid/src/svg/vogar.svg similarity index 100% rename from libs/application/templates/financial-aid/src/assets/svg/vogar.svg rename to libs/application/templates/financial-aid/src/svg/vogar.svg diff --git a/libs/application/templates/financial-aid/src/assets/svg/vopnafjardarhreppur.svg b/libs/application/templates/financial-aid/src/svg/vopnafjardarhreppur.svg similarity index 100% rename from libs/application/templates/financial-aid/src/assets/svg/vopnafjardarhreppur.svg rename to libs/application/templates/financial-aid/src/svg/vopnafjardarhreppur.svg diff --git a/libs/application/types/src/lib/Fields.ts b/libs/application/types/src/lib/Fields.ts index e5425ca66464..0ff0be0e3bfe 100644 --- a/libs/application/types/src/lib/Fields.ts +++ b/libs/application/types/src/lib/Fields.ts @@ -259,8 +259,6 @@ export enum FieldTypes { FIND_VEHICLE = 'FIND_VEHICLE', VEHICLE_RADIO = 'VEHICLE_RADIO', STATIC_TABLE = 'STATIC_TABLE', - ACCORDION = 'ACCORDION', - BANK_ACCOUNT = 'BANK_ACCOUNT', SLIDER = 'SLIDER', DISPLAY = 'DISPLAY', } @@ -296,8 +294,6 @@ export enum FieldComponents { FIND_VEHICLE = 'FindVehicleFormField', VEHICLE_RADIO = 'VehicleRadioFormField', STATIC_TABLE = 'StaticTableFormField', - ACCORDION = 'AccordionFormField', - BANK_ACCOUNT = 'BankAccountFormField', SLIDER = 'SliderFormField', DISPLAY = 'DisplayFormField', } @@ -545,30 +541,6 @@ export interface ImageField extends BaseField { imagePosition?: ImagePositionProps | Array } -export type AccordionItem = { - itemTitle: FormText - itemContent: FormText -} - -export interface AccordionField extends BaseField { - readonly type: FieldTypes.ACCORDION - component: FieldComponents.ACCORDION - accordionItems: - | Array - | ((application: Application) => Array) - marginTop?: ResponsiveProp - marginBottom?: ResponsiveProp - titleVariant?: TitleVariants -} - -export interface BankAccountField extends BaseField { - readonly type: FieldTypes.BANK_ACCOUNT - component: FieldComponents.BANK_ACCOUNT - marginTop?: ResponsiveProp - marginBottom?: ResponsiveProp - titleVariant?: TitleVariants -} - export interface PdfLinkButtonField extends BaseField { readonly type: FieldTypes.PDF_LINK_BUTTON component: FieldComponents.PDF_LINK_BUTTON @@ -844,7 +816,5 @@ export type Field = | FindVehicleField | VehicleRadioField | StaticTableField - | AccordionField - | BankAccountField | SliderField | DisplayField diff --git a/libs/application/types/src/lib/Form.ts b/libs/application/types/src/lib/Form.ts index e3656d70d405..379ccfa0cce4 100644 --- a/libs/application/types/src/lib/Form.ts +++ b/libs/application/types/src/lib/Form.ts @@ -79,7 +79,7 @@ export interface Form { children: FormChildren[] icon?: string id: string - logo?: FormComponent + logo?: React.FC> mode?: FormModes renderLastScreenBackButton?: boolean renderLastScreenButton?: boolean diff --git a/libs/application/ui-fields/src/lib/AccordionFormField/AccordionFormField.tsx b/libs/application/ui-fields/src/lib/AccordionFormField/AccordionFormField.tsx deleted file mode 100644 index 26365509c62f..000000000000 --- a/libs/application/ui-fields/src/lib/AccordionFormField/AccordionFormField.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import { - AccordionField, - AccordionItem as AccordionItemType, - FieldBaseProps, -} from '@island.is/application/types' -import { Accordion, AccordionItem, Box, Text } from '@island.is/island-ui/core' -import { useLocale } from '@island.is/localization' -import { formatText, formatTextWithLocale } from '@island.is/application/core' -import { Markdown } from '@island.is/shared/components' -import { useEffect, useState } from 'react' -import { Locale } from '@island.is/shared/types' - -interface Props extends FieldBaseProps { - field: AccordionField -} - -export const AccordionFormField = ({ field, application }: Props) => { - const [items, setItems] = useState>() - const { formatMessage, lang: locale } = useLocale() - const { accordionItems, marginBottom, marginTop, title, titleVariant } = field - - useEffect(() => { - if (typeof accordionItems === 'function') { - setItems(accordionItems(application)) - } else { - setItems(accordionItems) - } - }, [accordionItems]) - - if (!items || items.length === 0) { - return null - } - - return ( - - {title && ( - - - {formatTextWithLocale( - field.title, - application, - locale as Locale, - formatMessage, - )} - - - )} - - {items.map((item, index) => { - return ( - - - {formatText(item.itemContent, application, formatMessage)} - - - ) - })} - - - ) -} diff --git a/libs/application/ui-fields/src/lib/BankAccountFormField/BankAccountFormField.tsx b/libs/application/ui-fields/src/lib/BankAccountFormField/BankAccountFormField.tsx deleted file mode 100644 index 01b74ba49222..000000000000 --- a/libs/application/ui-fields/src/lib/BankAccountFormField/BankAccountFormField.tsx +++ /dev/null @@ -1,90 +0,0 @@ -import { - coreDefaultFieldMessages, - formatText, - formatTextWithLocale, -} from '@island.is/application/core' -import { BankAccountField, FieldBaseProps } from '@island.is/application/types' -import { Box, GridColumn, GridRow, Text } from '@island.is/island-ui/core' -import { useLocale } from '@island.is/localization' -import { InputController } from '@island.is/shared/form-fields' -import { Locale } from '@island.is/shared/types' - -interface Props extends FieldBaseProps { - field: BankAccountField -} - -export const BankAccountFormField = ({ field, application }: Props) => { - const { formatMessage, lang: locale } = useLocale() - const { marginBottom, marginTop, title, titleVariant, id } = field - const bankNumber = formatText( - coreDefaultFieldMessages.defaultBankAccountBankNumber, - application, - formatMessage, - ) - const ledger = formatText( - coreDefaultFieldMessages.defaultBankAccountLedger, - application, - formatMessage, - ) - const accountNumber = formatText( - coreDefaultFieldMessages.defaultBankAccountAccountNumber, - application, - formatMessage, - ) - - return ( - - {title && ( - - - {formatTextWithLocale( - field.title, - application, - locale as Locale, - formatMessage, - )} - - - )} - - - - - - - - - - - - - - - - - - - ) -} diff --git a/libs/application/ui-fields/src/lib/index.ts b/libs/application/ui-fields/src/lib/index.ts index d796e89afbe9..d805491651c5 100644 --- a/libs/application/ui-fields/src/lib/index.ts +++ b/libs/application/ui-fields/src/lib/index.ts @@ -28,7 +28,5 @@ export { FieldsRepeaterFormField } from './FieldsRepeaterFormField/FieldsRepeate 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/application/ui-shell/src/lib/FormShell.tsx b/libs/application/ui-shell/src/lib/FormShell.tsx index f2fc28a32899..d3be0a1e4747 100644 --- a/libs/application/ui-shell/src/lib/FormShell.tsx +++ b/libs/application/ui-shell/src/lib/FormShell.tsx @@ -1,5 +1,4 @@ import React, { FC, useEffect, useReducer, useState } from 'react' - import { Application, Form, @@ -12,7 +11,6 @@ import { GridContainer, GridRow, } from '@island.is/island-ui/core' - import { useLocale } from '@island.is/localization' import { useUserInfo } from '@island.is/react-spa/bff' import { ErrorShell } from '../components/ErrorShell' @@ -26,7 +24,6 @@ import { initializeReducer, } from '../reducer/ApplicationFormReducer' import { ActionTypes } from '../reducer/ReducerTypes' -import { getFormComponent } from '../utils' import * as styles from './FormShell.css' export const FormShell: FC< @@ -69,7 +66,7 @@ export const FormShell: FC< } = state.form const showProgressTag = mode !== FormModes.DRAFT const currentScreen = screens[activeScreen] - const FormLogo = getFormComponent(form.logo, storedApplication) + const FormLogo = form.logo const getDraftSectionCurrentScreen = (): number | undefined => { const currentDraftScreenSection = sections.find( diff --git a/libs/application/ui-shell/src/utils.ts b/libs/application/ui-shell/src/utils.ts index 9fb308f3a1b3..e027cfd073d5 100644 --- a/libs/application/ui-shell/src/utils.ts +++ b/libs/application/ui-shell/src/utils.ts @@ -1,10 +1,8 @@ import { getValueViaPath } from '@island.is/application/core' import { - Application, DataProviderItem, ExternalData, FieldTypes, - FormComponent, FormItemTypes, FormValue, RecordObject, @@ -156,38 +154,3 @@ export const parseMessage = (message?: string) => { return message } - -function isFunctionalComponent( - component: FormComponent | undefined, -): component is React.FC> { - if (!component) return false - return ( - typeof component === 'function' && - !(component.prototype && component.prototype.isReactComponent) && - component.length === 0 - ) -} - -function isFunctionReturningComponent( - component: FormComponent | undefined, -): component is ( - application: Application, -) => React.FC> | null | undefined { - if (!component) return false - return typeof component === 'function' && component.length === 1 -} - -export function getFormComponent( - component: FormComponent | undefined, - application: Application, -) { - if (isFunctionalComponent(component)) { - return component - } - - if (isFunctionReturningComponent(component)) { - return component(application) - } - - return null -} diff --git a/libs/financial-aid/shared/src/lib/formatters.ts b/libs/financial-aid/shared/src/lib/formatters.ts index b9a9aa00d238..46111cb4b699 100644 --- a/libs/financial-aid/shared/src/lib/formatters.ts +++ b/libs/financial-aid/shared/src/lib/formatters.ts @@ -360,12 +360,8 @@ export const applicationStateToFilterEnum: KeyMapping< export const aidCalculator = ( homeCircumstances: HomeCircumstances, - aid?: Aid, -): number | undefined => { - if (!aid) { - return undefined - } - + aid: Aid, +): number => { switch (homeCircumstances) { case 'OwnPlace': return aid.ownPlace diff --git a/libs/shared/components/src/Markdown/markdownOptions.tsx b/libs/shared/components/src/Markdown/markdownOptions.tsx index f5f1b34b5e52..bf1df1be6348 100644 --- a/libs/shared/components/src/Markdown/markdownOptions.tsx +++ b/libs/shared/components/src/Markdown/markdownOptions.tsx @@ -6,6 +6,7 @@ import { Text, } from '@island.is/island-ui/core' import { MarkdownToJSX } from 'markdown-to-jsx' +import React from 'react' import * as styles from './Markdown.css' import { OptionalOverrides } from './Markdown' From 02f2aa441b586ac1edd812384441ed2f5d793385 Mon Sep 17 00:00:00 2001 From: Steinar Freyr Kristinsson <86603428+advaniasteinar@users.noreply.github.com> Date: Tue, 3 Dec 2024 14:58:15 +0100 Subject: [PATCH 08/14] fix(Consultation-portal): KAM-2800 user cannot deregister from "all" cases (#17113) * fix(consultation-portal): Enable user to deregister from "all" cases * chore(consultation-portal): Remove unused hook --- .../SubscriptionTable/SubscriptionTable.tsx | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/apps/consultation-portal/screens/Subscriptions/components/SubscriptionTable/SubscriptionTable.tsx b/apps/consultation-portal/screens/Subscriptions/components/SubscriptionTable/SubscriptionTable.tsx index 1fb8965152a2..f1de635486df 100644 --- a/apps/consultation-portal/screens/Subscriptions/components/SubscriptionTable/SubscriptionTable.tsx +++ b/apps/consultation-portal/screens/Subscriptions/components/SubscriptionTable/SubscriptionTable.tsx @@ -1,10 +1,5 @@ import React from 'react' -import { - Stack, - Table as T, - Text, - useBreakpoint, -} from '@island.is/island-ui/core' +import { Stack, Table as T, Text } from '@island.is/island-ui/core' import { mapIsToEn, sortLocale } from '../../../../utils/helpers' import { SubscriptionArray } from '../../../../types/interfaces' import { Area } from '../../../../types/enums' @@ -37,7 +32,6 @@ const SubscriptionTable = ({ searchValue, isMySubscriptions, }: Props) => { - const { md: mdBreakpoint } = useBreakpoint() const { Table, Body } = T const loc = localization.subscriptionTable const mappedCurrentTab = mapIsToEn[currentTab] @@ -54,8 +48,13 @@ const SubscriptionTable = ({ data: thisData, }) - if ( + const hasNoItemsToShow = dataToRender.length === 0 && + dontShowNew !== false && + dontShowChanges !== false + + if ( + hasNoItemsToShow && !subscribedToAllNewObj.checked && !subscribedToAllChangesObj.checked ) { From 1dfbe91d82d9fe2b09a39d9ce8fa5c493f922048 Mon Sep 17 00:00:00 2001 From: Kristofer Date: Tue, 3 Dec 2024 15:48:45 +0000 Subject: [PATCH 09/14] chore(infra): Move `ARG` declaration for clarity (#17054) * Move ARG usage for cache-bust minimization * Fewer lines changed with `ENV` move * Less-changed ARGs * Add 'ARG APP' to all "Stage-specific ARGs" --------- Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- scripts/ci/Dockerfile | 72 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 60 insertions(+), 12 deletions(-) diff --git a/scripts/ci/Dockerfile b/scripts/ci/Dockerfile index 426f9b8ab8e5..2b9023583788 100644 --- a/scripts/ci/Dockerfile +++ b/scripts/ci/Dockerfile @@ -1,11 +1,17 @@ # This is a multi-stage Dockerfile which contains all CI-related operations as well as images to be deployed in production + +# Global ARGs (used across multiple stages) ARG PLAYWRIGHT_VERSION ARG DOCKER_ECR_REGISTRY=public.ecr.aws/docker ARG DOCKER_IMAGE_REGISTRY=${DOCKER_ECR_REGISTRY%/docker} # Alias DOCKER_IMAGE_REGISTRY to DOCKER_REGISTRY for backwards compatibility ARG DOCKER_REGISTRY=${DOCKER_IMAGE_REGISTRY}/docker ARG NODE_IMAGE_TAG +ARG APP_HOME +ARG APP_DIST_HOME=dist/${APP_HOME} +ARG APP +# Base image for dependencies FROM ${DOCKER_REGISTRY}/library/node:${NODE_IMAGE_TAG} AS deps # hadolint ignore=DL3018 @@ -21,6 +27,7 @@ COPY .yarn/ ./.yarn RUN apk add --update --no-cache python3 build-base gcc && ln -sf /usr/bin/python3 /usr/bin/python RUN CI=true yarn install --immutable +# Image with source code FROM deps AS src RUN wget -O /tmp/jq-linux64 https://github.com/stedolan/jq/releases/download/jq-1.6/jq-linux64 && \ @@ -30,7 +37,11 @@ RUN wget -O /tmp/jq-linux64 https://github.com/stedolan/jq/releases/download/jq- # image with the source code COPY . . + +# Build stage FROM src AS builder + +# Stage-specific ARGs ARG APP ARG APP_DIST_HOME ENV APP=${APP} @@ -39,6 +50,7 @@ ENV NODE_OPTIONS="--max-old-space-size=8192" RUN yarn run build ${APP} --prod + # This is base image for containers that are to be deployed FROM ${DOCKER_REGISTRY}/library/node:${NODE_IMAGE_TAG} AS output-base ARG APP @@ -64,9 +76,14 @@ USER runner FROM output-base-with-pg AS output-express +# Stage-specific ARGs +ARG APP_DIST_HOME +ARG GIT_BRANCH +ARG GIT_COMMIT_SHA +ARG GIT_REPOSITORY_URL + COPY --from=builder /build/${APP_DIST_HOME} /webapp/ -ARG GIT_BRANCH GIT_COMMIT_SHA GIT_REPOSITORY_URL ENV GIT_BRANCH=${GIT_BRANCH} GIT_COMMIT_SHA=${GIT_COMMIT_SHA} GIT_REPOSITORY_URL=${GIT_REPOSITORY_URL} ENV DD_GIT_BRANCH=${GIT_BRANCH} DD_GIT_COMMIT_SHA=${GIT_COMMIT_SHA} DD_GIT_REPOSITORY_URL=${GIT_REPOSITORY_URL} LABEL branch=${GIT_BRANCH} @@ -75,24 +92,35 @@ ENTRYPOINT [] CMD [ "node", "--no-experimental-fetch", "main.js" ] FROM output-base-with-pg AS output-next + +# Stage-specific ARGs +ARG APP +ARG APP_DIST_HOME +ARG GIT_BRANCH +ARG GIT_COMMIT_SHA +ARG GIT_REPOSITORY_URL + ENV PORT=4200 # TODO: smallify COPY --from=deps /build/node_modules /webapp/node_modules COPY --from=builder /build/${APP_DIST_HOME} /webapp/ -ARG GIT_BRANCH GIT_COMMIT_SHA GIT_REPOSITORY_URL ENV GIT_BRANCH=${GIT_BRANCH} GIT_COMMIT_SHA=${GIT_COMMIT_SHA} GIT_REPOSITORY_URL=${GIT_REPOSITORY_URL} ENV DD_GIT_BRANCH=${GIT_BRANCH} DD_GIT_COMMIT_SHA=${GIT_COMMIT_SHA} DD_GIT_REPOSITORY_URL=${GIT_REPOSITORY_URL} LABEL branch=${GIT_BRANCH} LABEL commit=${GIT_COMMIT_SHA} ENTRYPOINT [ "node", "main.js" ] + FROM ${DOCKER_REGISTRY}/library/nginx:1.21-alpine AS output-static + +# Stage-specific ARGs ARG APP ARG APP_DIST_HOME -ENV APP=${APP} -ENV BASEPATH=/ +ARG GIT_BRANCH +ARG GIT_COMMIT_SHA +ARG GIT_REPOSITORY_URL RUN mkdir -p /etc/nginx/templates # hadolint ignore=DL3018 @@ -105,14 +133,23 @@ COPY scripts/dockerfile-assets/bash/extract-environment.sh /docker-entrypoint.d COPY scripts/dockerfile-assets/bash/extract-environment.js /docker-entrypoint.d COPY --from=builder /build/${APP_DIST_HOME} /usr/share/nginx/html -ARG GIT_BRANCH GIT_COMMIT_SHA GIT_REPOSITORY_URL ENV GIT_BRANCH=${GIT_BRANCH} GIT_COMMIT_SHA=${GIT_COMMIT_SHA} GIT_REPOSITORY_URL=${GIT_REPOSITORY_URL} ENV DD_GIT_BRANCH=${GIT_BRANCH} DD_GIT_COMMIT_SHA=${GIT_COMMIT_SHA} DD_GIT_REPOSITORY_URL=${GIT_REPOSITORY_URL} LABEL branch=${GIT_BRANCH} LABEL commit=${GIT_COMMIT_SHA} +ENV APP=${APP} +ENV BASEPATH=/ + FROM output-base AS output-jest +# Stage-specific ARGs +ARG APP +ARG APP_DIST_HOME +ARG GIT_BRANCH +ARG GIT_COMMIT_SHA +ARG GIT_REPOSITORY_URL + RUN echo 'module.exports = {};' > jest.config.js # hadolint ignore=DL3016 @@ -122,7 +159,6 @@ COPY --from=builder /build/${APP_DIST_HOME} /webapp/ USER runner -ARG GIT_BRANCH GIT_COMMIT_SHA GIT_REPOSITORY_URL ENV GIT_BRANCH=${GIT_BRANCH} GIT_COMMIT_SHA=${GIT_COMMIT_SHA} GIT_REPOSITORY_URL=${GIT_REPOSITORY_URL} ENV DD_GIT_BRANCH=${GIT_BRANCH} DD_GIT_COMMIT_SHA=${GIT_COMMIT_SHA} DD_GIT_REPOSITORY_URL=${GIT_REPOSITORY_URL} LABEL branch=${GIT_BRANCH} @@ -130,11 +166,19 @@ LABEL commit=${GIT_COMMIT_SHA} CMD [ "jest", "main.spec.js" ] - FROM mcr.microsoft.com/playwright:v${PLAYWRIGHT_VERSION}-focal AS playwright-base FROM playwright-base AS output-playwright +# Stage-specific ARGs +ARG APP +ARG APP_DIST_HOME +ARG APP_HOME +ARG PLAYWRIGHT_BROWSER=chromium +ARG GIT_BRANCH +ARG GIT_COMMIT_SHA +ARG GIT_REPOSITORY_URL + # TODO: remove awscli dependency (157 MB extra) # hadolint ignore=DL3008 @@ -155,12 +199,10 @@ RUN mkdir ./.yarn COPY .yarn/releases ./.yarn/releases RUN yarn install -ENV PLAYWRIGHT_BROWSER=chromium RUN yarn playwright install ${PLAYWRIGHT_BROWSER} COPY --chown=pwuser:pwuser --chmod=0755 ${APP_HOME}/entrypoint.sh . -ARG GIT_BRANCH GIT_COMMIT_SHA GIT_REPOSITORY_URL ENV GIT_BRANCH=${GIT_BRANCH} GIT_COMMIT_SHA=${GIT_COMMIT_SHA} GIT_REPOSITORY_URL=${GIT_REPOSITORY_URL} ENV DD_GIT_BRANCH=${GIT_BRANCH} DD_GIT_COMMIT_SHA=${GIT_COMMIT_SHA} DD_GIT_REPOSITORY_URL=${GIT_REPOSITORY_URL} LABEL branch=${GIT_BRANCH} @@ -169,12 +211,17 @@ ENTRYPOINT ["./entrypoint.sh"] FROM playwright-base AS output-local -ARG APP_HOME + +# Stage-specific ARGs +ARG APP ARG APP_DIST_HOME +ARG APP_HOME +ARG GIT_BRANCH +ARG GIT_COMMIT_SHA +ARG GIT_REPOSITORY_URL WORKDIR ${APP_DIST_HOME} - # node user exists in the base image RUN mkdir -p /out \ && chown node:node /out @@ -183,12 +230,13 @@ COPY --chown=pwuser:pwuser --chmod=0755 ${APP_HOME}/entrypoint.sh . USER pwuser -ARG GIT_BRANCH GIT_COMMIT_SHA GIT_REPOSITORY_URL ENV GIT_BRANCH=${GIT_BRANCH} GIT_COMMIT_SHA=${GIT_COMMIT_SHA} GIT_REPOSITORY_URL=${GIT_REPOSITORY_URL} ENV DD_GIT_BRANCH=${GIT_BRANCH} DD_GIT_COMMIT_SHA=${GIT_COMMIT_SHA} DD_GIT_REPOSITORY_URL=${GIT_REPOSITORY_URL} LABEL branch=${GIT_BRANCH} LABEL commit=${GIT_COMMIT_SHA} ENTRYPOINT ["./entrypoint.sh"] + FROM output-base AS output-native + RUN echo "not-implemented" From 04edebb832d86bda2a98c3dd98251b055362e0d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B3nas=20G=2E=20Sigur=C3=B0sson?= Date: Tue, 3 Dec 2024 17:17:19 +0000 Subject: [PATCH 10/14] feat(app-sys): national-id-with-name fetch form more endpoints (#17101) * feat: fetch from more endpoints * feat: just extra company search * feat: add option to field builder to search for company * feat: error handling for company search * revert example form to the original state * . * chore: edit large margin * . * fix: lint * fix: lint --------- Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../application/core/src/lib/fieldBuilders.ts | 12 ++ libs/application/types/src/lib/Fields.ts | 5 + .../NationalIdWithName/NationalIdWithName.tsx | 109 +++++++++++++++--- .../NationalIdWithName/graphql/queries.ts | 8 ++ .../NationalIdWithNameFormField.tsx | 65 ++++++++--- 5 files changed, 164 insertions(+), 35 deletions(-) diff --git a/libs/application/core/src/lib/fieldBuilders.ts b/libs/application/core/src/lib/fieldBuilders.ts index 1ef54e3fa911..d5390fb6bbb6 100644 --- a/libs/application/core/src/lib/fieldBuilders.ts +++ b/libs/application/core/src/lib/fieldBuilders.ts @@ -748,6 +748,12 @@ export const buildNationalIdWithNameField = ( nameDefaultValue, errorMessage, minAgePerson, + searchPersons, + searchCompanies, + titleVariant, + description, + marginTop, + marginBottom, } = data return { ...extractCommonFields(data), @@ -761,9 +767,15 @@ export const buildNationalIdWithNameField = ( nameDefaultValue, errorMessage, minAgePerson, + searchPersons, + searchCompanies, children: undefined, type: FieldTypes.NATIONAL_ID_WITH_NAME, component: FieldComponents.NATIONAL_ID_WITH_NAME, + titleVariant, + description, + marginTop, + marginBottom, } } diff --git a/libs/application/types/src/lib/Fields.ts b/libs/application/types/src/lib/Fields.ts index 0ff0be0e3bfe..877dc89d4621 100644 --- a/libs/application/types/src/lib/Fields.ts +++ b/libs/application/types/src/lib/Fields.ts @@ -570,6 +570,11 @@ export interface NationalIdWithNameField extends InputField { nameDefaultValue?: string errorMessage?: string minAgePerson?: number + searchPersons?: boolean + searchCompanies?: boolean + titleVariant?: TitleVariants + marginTop?: ResponsiveProp + marginBottom?: ResponsiveProp } type Modify = Omit & R diff --git a/libs/application/ui-components/src/components/NationalIdWithName/NationalIdWithName.tsx b/libs/application/ui-components/src/components/NationalIdWithName/NationalIdWithName.tsx index d232bc7ef1fd..fb42f7b19422 100644 --- a/libs/application/ui-components/src/components/NationalIdWithName/NationalIdWithName.tsx +++ b/libs/application/ui-components/src/components/NationalIdWithName/NationalIdWithName.tsx @@ -1,5 +1,5 @@ import { FC, useEffect, useState } from 'react' -import { Box, GridRow, GridColumn } from '@island.is/island-ui/core' +import { GridRow, GridColumn } from '@island.is/island-ui/core' import { useLocale } from '@island.is/localization' import { coreErrorMessages, @@ -8,12 +8,16 @@ import { } from '@island.is/application/core' import { Application, StaticText } from '@island.is/application/types' import { gql, useLazyQuery } from '@apollo/client' -import { IdentityInput, Query } from '@island.is/api/schema' +import { + IdentityInput, + Query, + RskCompanyInfoInput, +} from '@island.is/api/schema' import { InputController } from '@island.is/shared/form-fields' import { useFormContext } from 'react-hook-form' import * as kennitala from 'kennitala' import debounce from 'lodash/debounce' -import { IDENTITY_QUERY } from './graphql/queries' +import { COMPANY_IDENTITY_QUERY, IDENTITY_QUERY } from './graphql/queries' interface NationalIdWithNameProps { id: string @@ -29,6 +33,8 @@ interface NationalIdWithNameProps { nameDefaultValue?: string errorMessage?: string minAgePerson?: number + searchPersons?: boolean + searchCompanies?: boolean } export const NationalIdWithName: FC< @@ -47,6 +53,8 @@ export const NationalIdWithName: FC< nameDefaultValue, errorMessage, minAgePerson, + searchPersons = true, + searchCompanies = false, }) => { const fieldId = customId.length > 0 ? customId : id const nameField = `${fieldId}.name` @@ -58,6 +66,8 @@ export const NationalIdWithName: FC< formState: { errors }, } = useFormContext() const [nationalIdInput, setNationalIdInput] = useState('') + const [personName, setPersonName] = useState('') + const [companyName, setCompanyName] = useState('') // get name validation errors const nameFieldErrors = errorMessage @@ -101,23 +111,73 @@ export const NationalIdWithName: FC< { onCompleted: (data) => { onNameChange && onNameChange(data.identity?.name ?? '') - setValue(nameField, data.identity?.name ?? undefined) + setPersonName(data.identity?.name ?? '') }, }, ) + // query to get company name by national id + const [ + getCompanyIdentity, + { + data: companyData, + loading: companyQueryLoading, + error: companyQueryError, + }, + ] = useLazyQuery( + gql` + ${COMPANY_IDENTITY_QUERY} + `, + { + onCompleted: (companyData) => { + onNameChange && + onNameChange(companyData.companyRegistryCompany?.name ?? '') + setCompanyName(companyData.companyRegistryCompany?.name ?? '') + }, + }, + ) + // fetch and update name when user has entered a valid national id useEffect(() => { if (nationalIdInput.length === 10 && kennitala.isValid(nationalIdInput)) { - getIdentity({ - variables: { - input: { - nationalId: nationalIdInput, - }, - }, - }) + { + searchPersons && + getIdentity({ + variables: { + input: { + nationalId: nationalIdInput, + }, + }, + }) + } + + { + searchCompanies && + getCompanyIdentity({ + variables: { + input: { + nationalId: nationalIdInput, + }, + }, + }) + } + } + }, [ + nationalIdInput, + getIdentity, + getCompanyIdentity, + searchPersons, + searchCompanies, + ]) + + useEffect(() => { + const nameInAnswers = getValueViaPath(application.answers, nameField) + if (personName && nameInAnswers !== personName) { + setValue(nameField, personName) + } else if (companyName && nameInAnswers !== companyName) { + setValue(nameField, companyName) } - }, [nationalIdInput, getIdentity]) + }, [personName, companyName, setValue, nameField, application.answers]) return ( @@ -138,7 +198,7 @@ export const NationalIdWithName: FC< onNationalIdChange && onNationalIdChange(v.target.value.replace(/\W/g, '')) })} - loading={queryLoading} + loading={searchPersons ? queryLoading : companyQueryLoading} error={nationalIdFieldErrors} disabled={disabled} /> @@ -154,12 +214,23 @@ export const NationalIdWithName: FC< } required={required} error={ - queryError || data?.identity === null - ? formatMessage( - coreErrorMessages.nationalRegistryNameNotFoundForNationalId, - ) - : nameFieldErrors && !data - ? nameFieldErrors + searchPersons + ? queryError || data?.identity === null + ? formatMessage( + coreErrorMessages.nationalRegistryNameNotFoundForNationalId, + ) + : nameFieldErrors && !data + ? nameFieldErrors + : undefined + : searchCompanies + ? companyQueryError || + companyData?.companyRegistryCompany === null + ? formatMessage( + coreErrorMessages.nationalRegistryNameNotFoundForNationalId, + ) + : nameFieldErrors && !companyData + ? nameFieldErrors + : undefined : undefined } disabled diff --git a/libs/application/ui-components/src/components/NationalIdWithName/graphql/queries.ts b/libs/application/ui-components/src/components/NationalIdWithName/graphql/queries.ts index faa1771139c6..38c8d782b2ad 100644 --- a/libs/application/ui-components/src/components/NationalIdWithName/graphql/queries.ts +++ b/libs/application/ui-components/src/components/NationalIdWithName/graphql/queries.ts @@ -6,3 +6,11 @@ export const IDENTITY_QUERY = ` } } ` + +export const COMPANY_IDENTITY_QUERY = ` + query CompanyIdentityQuery($input: RskCompanyInfoInput!) { + companyRegistryCompany(input: $input) { + name + } + } +` diff --git a/libs/application/ui-fields/src/lib/NationalIdWithNameFormField/NationalIdWithNameFormField.tsx b/libs/application/ui-fields/src/lib/NationalIdWithNameFormField/NationalIdWithNameFormField.tsx index 1caf9fd5be81..1eba53e088a2 100644 --- a/libs/application/ui-fields/src/lib/NationalIdWithNameFormField/NationalIdWithNameFormField.tsx +++ b/libs/application/ui-fields/src/lib/NationalIdWithNameFormField/NationalIdWithNameFormField.tsx @@ -1,10 +1,17 @@ -import { buildFieldRequired } from '@island.is/application/core' +import { FC } from 'react' +import { + buildFieldRequired, + formatTextWithLocale, +} from '@island.is/application/core' import { FieldBaseProps, NationalIdWithNameField, } from '@island.is/application/types' import { NationalIdWithName } from '@island.is/application/ui-components' -import { FC } from 'react' +import { Box, Text } from '@island.is/island-ui/core' +import { FieldDescription } from '@island.is/shared/form-fields' +import { Locale } from '@island.is/shared/types' +import { useLocale } from '@island.is/localization' interface Props extends FieldBaseProps { field: NationalIdWithNameField @@ -13,20 +20,46 @@ interface Props extends FieldBaseProps { export const NationalIdWithNameFormField: FC< React.PropsWithChildren > = ({ application, field }) => { + const { formatMessage, lang: locale } = useLocale() + return ( - + + {field.title && ( + + {formatTextWithLocale( + field.title, + application, + locale as Locale, + formatMessage, + )} + + )} + {field.description && ( + + )} + + ) } From 9f4f9aed9dd167566b6d26f6df2853404d726638 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gu=C3=B0j=C3=B3n=20Gu=C3=B0j=C3=B3nsson?= Date: Tue, 3 Dec 2024 22:32:33 +0000 Subject: [PATCH 11/14] chore(j-s): Security&Integrity (#16863) * Throws proper exceptions from digital mailbox * Refactors code * Refactors code * Refactors code * Removes unused imports * Removes unused imports * Removes unused import * Removes redundant code * Rewrites internal indictment case endpoint * Adds unit tests * Reorders query conditions * Rewrites defendant indictment cases endpoint * Adds civil claimant exists guard to update and delete civil claimant * Removes unused endpoint * Uses proper subpoena gueard for limited access subpoena controller * Splits subpoena exists guard and reorders controller decorators * Adds unit test * Renames variables for clarity * Uses correct http method when getting cases * Moves subpoena status updates to subpoena module * Removes comments * Removes comments * Adds unit tests * Renames unit test * Updates unit tests * Refactors code * Adds unit tests * Adds unit tests * Fixes subpoena refresh * Cleans up some code * Removes unnecessary export * Rmoves console log. * Update apps/judicial-system/backend/src/app/modules/case/guards/test/indictmentCaseExistsForDefendantGuard.spec.ts * Updates unit test * Fixes type import * Fixes typo * Fixes typo * Fixes type decorators * Fixes typo * Refactors code * Rewrites subpoena info diff --------- Co-authored-by: unakb Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../judicial-system/api/src/app/app.module.ts | 2 + .../app/modules/backend/backend.service.ts | 19 +- .../api/src/app/modules/defendant/index.ts | 1 - .../defendant/models/defendant.model.ts | 2 +- .../api/src/app/modules/index.ts | 1 + .../api/src/app/modules/police/index.ts | 1 - .../police/models/subpoenaStatus.model.ts | 21 -- .../src/app/modules/police/police.resolver.ts | 22 --- .../dto/subpoena.input.ts} | 6 +- .../api/src/app/modules/subpoena/index.ts | 1 + .../models/subpoena.model.ts | 0 .../app/modules/subpoena/subpoena.module.ts | 8 + .../app/modules/subpoena/subpoena.resolver.ts | 53 +++++ .../src/app/modules/case/case.controller.ts | 2 +- .../app/modules/case/dto/internalCases.dto.ts | 16 -- .../app/modules/case/filters/cases.filter.ts | 4 +- .../case/filters/test/cases.filter.spec.ts | 4 +- .../indictmentCaseExistsForDefendant.guard.ts | 37 ++++ .../case/guards/mergedCaseExists.guard.ts | 2 +- ...ictmentCaseExistsForDefendantGuard.spec.ts | 161 +++++++++++++++ .../guards/test/mergedCaseExistsGuard.spec.ts | 130 +++++++++++++ .../modules/case/internalCase.controller.ts | 37 ++-- .../app/modules/case/internalCase.service.ts | 48 ++++- .../case/test/createTestingCaseModule.ts | 4 + .../getIndictmentCaseByIdGuards.spec.ts | 21 ++ .../defendant/civilClaimant.controller.ts | 17 +- .../defendant/civilClaimant.service.ts | 40 ++-- .../modules/defendant/defendant.controller.ts | 2 +- .../modules/defendant/defendant.service.ts | 38 ++-- .../civilClaimantControllerGuards.spec.ts | 18 ++ .../deleteGuards.spec.ts | 7 +- .../civilClaimantController/update.spec.ts | 7 +- .../updateGuards.spec.ts | 7 +- .../modules/event-log/eventLog.controller.ts | 3 +- .../src/app/modules/file/file.controller.ts | 2 +- .../modules/file/internalFile.controller.ts | 2 +- .../file/limitedAccessFile.controller.ts | 2 +- .../indictmentCount.controller.ts | 2 +- .../internalNotification.controller.ts | 2 +- .../notification/notification.controller.ts | 2 +- .../backend/src/app/modules/police/index.ts | 1 + .../police/internalPolice.controller.ts | 34 ---- .../police/models/subpoenaInfo.model.ts | 49 +++++ .../app/modules/police/police.controller.ts | 35 +--- .../src/app/modules/police/police.module.ts | 3 +- .../src/app/modules/police/police.service.ts | 65 +++---- .../subpoena/dto/updateSubpoena.dto.ts | 10 +- .../guards/policeSubpoenaExists.guard.ts | 28 +++ .../subpoena/guards/subpoenaExists.guard.ts | 20 +- .../test/policeSubpoenaExistsGuard.spec.ts | 110 +++++++++++ .../guards/test/subpoenaExistsGuard.spec.ts | 109 +++++++++++ .../subpoena/internalSubpoena.controller.ts | 21 +- .../limitedAccessSubpoena.controller.ts | 6 +- .../modules/subpoena/subpoena.controller.ts | 40 +++- .../app/modules/subpoena/subpoena.service.ts | 183 ++++++++++++------ .../internalSubpoenaControllerGuards.spec.ts | 17 ++ .../updateSubpoeanaGuards.spec.ts | 19 ++ .../getSubpoenaPdfGuards.spec.ts | 4 +- .../src/app/modules/cases/case.controller.ts | 8 +- .../src/app/modules/cases/case.service.ts | 87 ++------- .../modules/defenders/defender.controller.ts | 15 +- .../src/components/FormProvider/case.graphql | 1 + .../FormProvider/limitedAccessCase.graphql | 1 + .../ServiceAnnouncement.tsx | 31 +-- .../Court/Indictments/Overview/Overview.tsx | 17 +- .../Indictments/Overview/Overview.tsx | 17 +- .../useSubpoena/getSubpoenaStatus.graphql | 9 - .../web/src/utils/hooks/useSubpoena/index.ts | 29 ++- .../utils/hooks/useSubpoena/subpoena.graphql | 14 ++ .../xrd-api/src/app/app.controller.ts | 15 +- .../xrd-api/src/app/app.service.ts | 1 + .../audit-trail/src/lib/auditTrail.service.ts | 1 - .../lawyers/src/lib/lawyers.service.ts | 11 +- 73 files changed, 1246 insertions(+), 519 deletions(-) delete mode 100644 apps/judicial-system/api/src/app/modules/police/models/subpoenaStatus.model.ts rename apps/judicial-system/api/src/app/modules/{police/dto/subpoenaStatus.input.ts => subpoena/dto/subpoena.input.ts} (70%) create mode 100644 apps/judicial-system/api/src/app/modules/subpoena/index.ts rename apps/judicial-system/api/src/app/modules/{defendant => subpoena}/models/subpoena.model.ts (100%) create mode 100644 apps/judicial-system/api/src/app/modules/subpoena/subpoena.module.ts create mode 100644 apps/judicial-system/api/src/app/modules/subpoena/subpoena.resolver.ts delete mode 100644 apps/judicial-system/backend/src/app/modules/case/dto/internalCases.dto.ts create mode 100644 apps/judicial-system/backend/src/app/modules/case/guards/indictmentCaseExistsForDefendant.guard.ts create mode 100644 apps/judicial-system/backend/src/app/modules/case/guards/test/indictmentCaseExistsForDefendantGuard.spec.ts create mode 100644 apps/judicial-system/backend/src/app/modules/case/guards/test/mergedCaseExistsGuard.spec.ts create mode 100644 apps/judicial-system/backend/src/app/modules/case/test/internalCaseController/getIndictmentCaseByIdGuards.spec.ts create mode 100644 apps/judicial-system/backend/src/app/modules/defendant/test/civilClaimantController/civilClaimantControllerGuards.spec.ts delete mode 100644 apps/judicial-system/backend/src/app/modules/police/internalPolice.controller.ts create mode 100644 apps/judicial-system/backend/src/app/modules/police/models/subpoenaInfo.model.ts create mode 100644 apps/judicial-system/backend/src/app/modules/subpoena/guards/policeSubpoenaExists.guard.ts create mode 100644 apps/judicial-system/backend/src/app/modules/subpoena/guards/test/policeSubpoenaExistsGuard.spec.ts create mode 100644 apps/judicial-system/backend/src/app/modules/subpoena/guards/test/subpoenaExistsGuard.spec.ts create mode 100644 apps/judicial-system/backend/src/app/modules/subpoena/test/internalSubpoenaController/internalSubpoenaControllerGuards.spec.ts create mode 100644 apps/judicial-system/backend/src/app/modules/subpoena/test/internalSubpoenaController/updateSubpoeanaGuards.spec.ts delete mode 100644 apps/judicial-system/web/src/utils/hooks/useSubpoena/getSubpoenaStatus.graphql create mode 100644 apps/judicial-system/web/src/utils/hooks/useSubpoena/subpoena.graphql diff --git a/apps/judicial-system/api/src/app/app.module.ts b/apps/judicial-system/api/src/app/app.module.ts index d73082ad8cd3..b9d12a0626b1 100644 --- a/apps/judicial-system/api/src/app/app.module.ts +++ b/apps/judicial-system/api/src/app/app.module.ts @@ -34,6 +34,7 @@ import { IndictmentCountModule, InstitutionModule, PoliceModule, + SubpoenaModule, UserModule, } from './modules' @@ -68,6 +69,7 @@ const autoSchemaFile = production CaseListModule, DefendantModule, DefenderModule, + SubpoenaModule, IndictmentCountModule, FileModule, InstitutionModule, diff --git a/apps/judicial-system/api/src/app/modules/backend/backend.service.ts b/apps/judicial-system/api/src/app/modules/backend/backend.service.ts index 806cb2c44ee2..8069fc52faac 100644 --- a/apps/judicial-system/api/src/app/modules/backend/backend.service.ts +++ b/apps/judicial-system/api/src/app/modules/backend/backend.service.ts @@ -41,9 +41,9 @@ import { Institution } from '../institution' import { PoliceCaseFile, PoliceCaseInfo, - SubpoenaStatus, UploadPoliceCaseFileResponse, } from '../police' +import { Subpoena } from '../subpoena' import { backendModuleConfig } from './backend.config' @Injectable() @@ -326,13 +326,6 @@ export class BackendService extends DataSource<{ req: Request }> { return this.get(`case/${caseId}/policeFiles`) } - getSubpoenaStatus( - caseId: string, - subpoenaId: string, - ): Promise { - return this.get(`case/${caseId}/subpoenaStatus/${subpoenaId}`) - } - getPoliceCaseInfo(caseId: string): Promise { return this.get(`case/${caseId}/policeCaseInfo`) } @@ -369,6 +362,16 @@ export class BackendService extends DataSource<{ req: Request }> { return this.delete(`case/${caseId}/defendant/${defendantId}`) } + getSubpoena( + caseId: string, + defendantId: string, + subpoenaId: string, + ): Promise { + return this.get( + `case/${caseId}/defendant/${defendantId}/subpoena/${subpoenaId}`, + ) + } + createCivilClaimant( caseId: string, createCivilClaimant: unknown, diff --git a/apps/judicial-system/api/src/app/modules/defendant/index.ts b/apps/judicial-system/api/src/app/modules/defendant/index.ts index 040ddad841e3..0811956a0ca8 100644 --- a/apps/judicial-system/api/src/app/modules/defendant/index.ts +++ b/apps/judicial-system/api/src/app/modules/defendant/index.ts @@ -2,4 +2,3 @@ export { Defendant } from './models/defendant.model' export { DeleteDefendantResponse } from './models/delete.response' export { CivilClaimant } from './models/civilClaimant.model' export { DeleteCivilClaimantResponse } from './models/deleteCivilClaimant.response' -export { Subpoena } from './models/subpoena.model' diff --git a/apps/judicial-system/api/src/app/modules/defendant/models/defendant.model.ts b/apps/judicial-system/api/src/app/modules/defendant/models/defendant.model.ts index b5276f9bc104..8a692f3a9ade 100644 --- a/apps/judicial-system/api/src/app/modules/defendant/models/defendant.model.ts +++ b/apps/judicial-system/api/src/app/modules/defendant/models/defendant.model.ts @@ -8,7 +8,7 @@ import { SubpoenaType, } from '@island.is/judicial-system/types' -import { Subpoena } from './subpoena.model' +import { Subpoena } from '../../subpoena' registerEnumType(Gender, { name: 'Gender' }) registerEnumType(DefendantPlea, { name: 'DefendantPlea' }) diff --git a/apps/judicial-system/api/src/app/modules/index.ts b/apps/judicial-system/api/src/app/modules/index.ts index de4bc6ecb048..6615aac49117 100644 --- a/apps/judicial-system/api/src/app/modules/index.ts +++ b/apps/judicial-system/api/src/app/modules/index.ts @@ -17,3 +17,4 @@ export { EventLogModule } from './event-log/eventLog.module' export { backendModuleConfig } from './backend/backend.config' export { BackendService } from './backend/backend.service' export { BackendModule } from './backend/backend.module' +export { SubpoenaModule } from './subpoena/subpoena.module' diff --git a/apps/judicial-system/api/src/app/modules/police/index.ts b/apps/judicial-system/api/src/app/modules/police/index.ts index a1fe72f38c8e..c2c2457a57b5 100644 --- a/apps/judicial-system/api/src/app/modules/police/index.ts +++ b/apps/judicial-system/api/src/app/modules/police/index.ts @@ -1,4 +1,3 @@ export { PoliceCaseInfo } from './models/policeCaseInfo.model' -export { SubpoenaStatus } from './models/subpoenaStatus.model' export { PoliceCaseFile } from './models/policeCaseFile.model' export { UploadPoliceCaseFileResponse } from './models/uploadPoliceCaseFile.response' diff --git a/apps/judicial-system/api/src/app/modules/police/models/subpoenaStatus.model.ts b/apps/judicial-system/api/src/app/modules/police/models/subpoenaStatus.model.ts deleted file mode 100644 index 6f7a85461e10..000000000000 --- a/apps/judicial-system/api/src/app/modules/police/models/subpoenaStatus.model.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Field, ObjectType } from '@nestjs/graphql' - -import { ServiceStatus } from '@island.is/judicial-system/types' - -@ObjectType() -export class SubpoenaStatus { - @Field(() => ServiceStatus, { nullable: true }) - readonly serviceStatus?: ServiceStatus - - @Field(() => String, { nullable: true }) - readonly servedBy?: string - - @Field(() => String, { nullable: true }) - readonly comment?: string - - @Field(() => String, { nullable: true }) - readonly serviceDate?: string - - @Field(() => String, { nullable: true }) - readonly defenderNationalId?: string -} diff --git a/apps/judicial-system/api/src/app/modules/police/police.resolver.ts b/apps/judicial-system/api/src/app/modules/police/police.resolver.ts index 1eef19336e76..8d4fbf9cd90a 100644 --- a/apps/judicial-system/api/src/app/modules/police/police.resolver.ts +++ b/apps/judicial-system/api/src/app/modules/police/police.resolver.ts @@ -17,11 +17,9 @@ import type { User } from '@island.is/judicial-system/types' import { BackendService } from '../backend' import { PoliceCaseFilesQueryInput } from './dto/policeCaseFiles.input' import { PoliceCaseInfoQueryInput } from './dto/policeCaseInfo.input' -import { SubpoenaStatusQueryInput } from './dto/subpoenaStatus.input' import { UploadPoliceCaseFileInput } from './dto/uploadPoliceCaseFile.input' import { PoliceCaseFile } from './models/policeCaseFile.model' import { PoliceCaseInfo } from './models/policeCaseInfo.model' -import { SubpoenaStatus } from './models/subpoenaStatus.model' import { UploadPoliceCaseFileResponse } from './models/uploadPoliceCaseFile.response' @UseGuards(JwtGraphQlAuthGuard) @@ -51,26 +49,6 @@ export class PoliceResolver { ) } - @Query(() => SubpoenaStatus, { nullable: true }) - subpoenaStatus( - @Args('input', { type: () => SubpoenaStatusQueryInput }) - input: SubpoenaStatusQueryInput, - @CurrentGraphQlUser() user: User, - @Context('dataSources') - { backendService }: { backendService: BackendService }, - ): Promise { - this.logger.debug( - `Getting subpoena status for subpoena ${input.subpoenaId} of case ${input.caseId}`, - ) - - return this.auditTrailService.audit( - user.id, - AuditedAction.GET_SUBPOENA_STATUS, - backendService.getSubpoenaStatus(input.caseId, input.subpoenaId), - input.caseId, - ) - } - @Query(() => [PoliceCaseInfo], { nullable: true }) policeCaseInfo( @Args('input', { type: () => PoliceCaseInfoQueryInput }) diff --git a/apps/judicial-system/api/src/app/modules/police/dto/subpoenaStatus.input.ts b/apps/judicial-system/api/src/app/modules/subpoena/dto/subpoena.input.ts similarity index 70% rename from apps/judicial-system/api/src/app/modules/police/dto/subpoenaStatus.input.ts rename to apps/judicial-system/api/src/app/modules/subpoena/dto/subpoena.input.ts index dcc11761c0be..4f0e3ca81c1f 100644 --- a/apps/judicial-system/api/src/app/modules/police/dto/subpoenaStatus.input.ts +++ b/apps/judicial-system/api/src/app/modules/subpoena/dto/subpoena.input.ts @@ -3,11 +3,15 @@ import { Allow } from 'class-validator' import { Field, ID, InputType } from '@nestjs/graphql' @InputType() -export class SubpoenaStatusQueryInput { +export class SubpoenaQueryInput { @Allow() @Field(() => ID) readonly caseId!: string + @Allow() + @Field(() => ID) + readonly defendantId!: string + @Allow() @Field(() => ID) readonly subpoenaId!: string diff --git a/apps/judicial-system/api/src/app/modules/subpoena/index.ts b/apps/judicial-system/api/src/app/modules/subpoena/index.ts new file mode 100644 index 000000000000..59471a541a5d --- /dev/null +++ b/apps/judicial-system/api/src/app/modules/subpoena/index.ts @@ -0,0 +1 @@ +export { Subpoena } from './models/subpoena.model' diff --git a/apps/judicial-system/api/src/app/modules/defendant/models/subpoena.model.ts b/apps/judicial-system/api/src/app/modules/subpoena/models/subpoena.model.ts similarity index 100% rename from apps/judicial-system/api/src/app/modules/defendant/models/subpoena.model.ts rename to apps/judicial-system/api/src/app/modules/subpoena/models/subpoena.model.ts diff --git a/apps/judicial-system/api/src/app/modules/subpoena/subpoena.module.ts b/apps/judicial-system/api/src/app/modules/subpoena/subpoena.module.ts new file mode 100644 index 000000000000..313600b022e9 --- /dev/null +++ b/apps/judicial-system/api/src/app/modules/subpoena/subpoena.module.ts @@ -0,0 +1,8 @@ +import { Module } from '@nestjs/common' + +import { SubpoenaResolver } from './subpoena.resolver' + +@Module({ + providers: [SubpoenaResolver], +}) +export class SubpoenaModule {} diff --git a/apps/judicial-system/api/src/app/modules/subpoena/subpoena.resolver.ts b/apps/judicial-system/api/src/app/modules/subpoena/subpoena.resolver.ts new file mode 100644 index 000000000000..51752826bec6 --- /dev/null +++ b/apps/judicial-system/api/src/app/modules/subpoena/subpoena.resolver.ts @@ -0,0 +1,53 @@ +import { Inject, UseGuards } from '@nestjs/common' +import { Args, Context, Query, Resolver } from '@nestjs/graphql' + +import type { Logger } from '@island.is/logging' +import { LOGGER_PROVIDER } from '@island.is/logging' + +import { + AuditedAction, + AuditTrailService, +} from '@island.is/judicial-system/audit-trail' +import { + CurrentGraphQlUser, + JwtGraphQlAuthGuard, +} from '@island.is/judicial-system/auth' +import type { User } from '@island.is/judicial-system/types' + +import { BackendService } from '../backend' +import { SubpoenaQueryInput } from './dto/subpoena.input' +import { Subpoena } from './models/subpoena.model' + +@UseGuards(JwtGraphQlAuthGuard) +@Resolver() +export class SubpoenaResolver { + constructor( + private readonly auditTrailService: AuditTrailService, + @Inject(LOGGER_PROVIDER) + private readonly logger: Logger, + ) {} + + @Query(() => Subpoena, { nullable: true }) + subpoena( + @Args('input', { type: () => SubpoenaQueryInput }) + input: SubpoenaQueryInput, + @CurrentGraphQlUser() user: User, + @Context('dataSources') + { backendService }: { backendService: BackendService }, + ): Promise { + this.logger.debug( + `Getting subpoena ${input.subpoenaId} for defendant ${input.defendantId} of case ${input.caseId}`, + ) + + return this.auditTrailService.audit( + user.id, + AuditedAction.GET_SUBPOENA, + backendService.getSubpoena( + input.caseId, + input.defendantId, + input.subpoenaId, + ), + input.caseId, + ) + } +} diff --git a/apps/judicial-system/backend/src/app/modules/case/case.controller.ts b/apps/judicial-system/backend/src/app/modules/case/case.controller.ts index 63b82483e3a9..9728dc420508 100644 --- a/apps/judicial-system/backend/src/app/modules/case/case.controller.ts +++ b/apps/judicial-system/backend/src/app/modules/case/case.controller.ts @@ -354,7 +354,7 @@ export class CaseController { ) @UseInterceptors(CompletedAppealAccessedInterceptor, CaseInterceptor) @Get('case/:caseId') - @ApiOkResponse({ type: Case, description: 'Gets an existing case' }) + @ApiOkResponse({ type: Case, description: 'Gets an existing case by id' }) getById(@Param('caseId') caseId: string, @CurrentCase() theCase: Case): Case { this.logger.debug(`Getting case ${caseId} by id`) diff --git a/apps/judicial-system/backend/src/app/modules/case/dto/internalCases.dto.ts b/apps/judicial-system/backend/src/app/modules/case/dto/internalCases.dto.ts deleted file mode 100644 index d9c3d5c34a08..000000000000 --- a/apps/judicial-system/backend/src/app/modules/case/dto/internalCases.dto.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Transform } from 'class-transformer' -import { IsNotEmpty, IsString, MaxLength, MinLength } from 'class-validator' - -import { ApiProperty } from '@nestjs/swagger' - -import { nationalIdTransformer } from '../../../transformers' - -export class InternalCasesDto { - @IsNotEmpty() - @IsString() - @MinLength(10) - @MaxLength(10) - @Transform(nationalIdTransformer) - @ApiProperty({ type: String }) - readonly nationalId!: string -} 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 bc5f2f9f3a5f..04c4e62bff2a 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 @@ -187,7 +187,6 @@ const getPrisonStaffUserCasesQueryFilter = (): WhereOptions => { return { [Op.and]: [ { is_archived: false }, - { state: CaseState.ACCEPTED }, { type: [ CaseType.CUSTODY, @@ -195,6 +194,7 @@ const getPrisonStaffUserCasesQueryFilter = (): WhereOptions => { CaseType.PAROLE_REVOCATION, ], }, + { state: CaseState.ACCEPTED }, { decision: [CaseDecision.ACCEPTING, CaseDecision.ACCEPTING_PARTIALLY] }, ], } @@ -205,8 +205,8 @@ const getPrisonAdminUserCasesQueryFilter = (): WhereOptions => { is_archived: false, [Op.or]: [ { - state: CaseState.ACCEPTED, type: [...restrictionCases, CaseType.PAROLE_REVOCATION], + state: CaseState.ACCEPTED, }, { type: indictmentCases, 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 4d6027e5d5b4..a67c842f797f 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 @@ -347,7 +347,6 @@ describe('getCasesQueryFilter', () => { expect(res).toStrictEqual({ [Op.and]: [ { is_archived: false }, - { state: CaseState.ACCEPTED }, { type: [ CaseType.CUSTODY, @@ -355,6 +354,7 @@ describe('getCasesQueryFilter', () => { CaseType.PAROLE_REVOCATION, ], }, + { state: CaseState.ACCEPTED }, { decision: [CaseDecision.ACCEPTING, CaseDecision.ACCEPTING_PARTIALLY], }, @@ -382,8 +382,8 @@ describe('getCasesQueryFilter', () => { [Op.or]: [ { - state: CaseState.ACCEPTED, type: [...restrictionCases, CaseType.PAROLE_REVOCATION], + state: CaseState.ACCEPTED, }, { type: indictmentCases, diff --git a/apps/judicial-system/backend/src/app/modules/case/guards/indictmentCaseExistsForDefendant.guard.ts b/apps/judicial-system/backend/src/app/modules/case/guards/indictmentCaseExistsForDefendant.guard.ts new file mode 100644 index 000000000000..f7165aeb9d29 --- /dev/null +++ b/apps/judicial-system/backend/src/app/modules/case/guards/indictmentCaseExistsForDefendant.guard.ts @@ -0,0 +1,37 @@ +import { + BadRequestException, + CanActivate, + ExecutionContext, + Injectable, +} from '@nestjs/common' + +import { InternalCaseService } from '../internalCase.service' + +@Injectable() +export class IndictmentCaseExistsForDefendantGuard implements CanActivate { + constructor(private readonly internalCaseService: InternalCaseService) {} + + async canActivate(context: ExecutionContext): Promise { + const request = context.switchToHttp().getRequest() + + const caseId = request.params.caseId + + if (!caseId) { + throw new BadRequestException('Missing case id') + } + + const defendantNationalId = request.params.defendantNationalId + + if (!defendantNationalId) { + throw new BadRequestException('Missing defendant national id') + } + + request.case = + await this.internalCaseService.findByIdAndDefendantNationalId( + caseId, + defendantNationalId, + ) + + return true + } +} diff --git a/apps/judicial-system/backend/src/app/modules/case/guards/mergedCaseExists.guard.ts b/apps/judicial-system/backend/src/app/modules/case/guards/mergedCaseExists.guard.ts index d3892e9036aa..7e983cf79987 100644 --- a/apps/judicial-system/backend/src/app/modules/case/guards/mergedCaseExists.guard.ts +++ b/apps/judicial-system/backend/src/app/modules/case/guards/mergedCaseExists.guard.ts @@ -34,7 +34,7 @@ export class MergedCaseExistsGuard implements CanActivate { ) if (!mergedCase) { - throw new BadRequestException('Merged case not found') + throw new BadRequestException(`Merged case ${mergedCaseId} not found`) } request.mergedCaseParent = theCase diff --git a/apps/judicial-system/backend/src/app/modules/case/guards/test/indictmentCaseExistsForDefendantGuard.spec.ts b/apps/judicial-system/backend/src/app/modules/case/guards/test/indictmentCaseExistsForDefendantGuard.spec.ts new file mode 100644 index 000000000000..076ac2c882c4 --- /dev/null +++ b/apps/judicial-system/backend/src/app/modules/case/guards/test/indictmentCaseExistsForDefendantGuard.spec.ts @@ -0,0 +1,161 @@ +import { Op } from 'sequelize' +import { uuid } from 'uuidv4' + +import { + BadRequestException, + ExecutionContext, + NotFoundException, +} from '@nestjs/common' + +import { normalizeAndFormatNationalId } from '@island.is/judicial-system/formatters' +import { CaseState, CaseType } from '@island.is/judicial-system/types' + +import { createTestingCaseModule } from '../../test/createTestingCaseModule' + +import { Defendant } from '../../../defendant' +import { Institution } from '../../../institution' +import { Subpoena } from '../../../subpoena' +import { User } from '../../../user' +import { Case } from '../../models/case.model' +import { DateLog } from '../../models/dateLog.model' +import { IndictmentCaseExistsForDefendantGuard } from '../indictmentCaseExistsForDefendant.guard' + +interface Then { + result: boolean + error: Error +} + +type GivenWhenThen = () => Promise + +describe('Indictment Case Exists For Defendant Guard', () => { + const mockRequest = jest.fn() + let mockCaseModel: typeof Case + let givenWhenThen: GivenWhenThen + + beforeEach(async () => { + const { caseModel, internalCaseService } = await createTestingCaseModule() + + mockCaseModel = caseModel + + givenWhenThen = async (): Promise => { + const guard = new IndictmentCaseExistsForDefendantGuard( + internalCaseService, + ) + const then = {} as Then + + try { + then.result = await guard.canActivate({ + switchToHttp: () => ({ getRequest: mockRequest }), + } as unknown as ExecutionContext) + } catch (error) { + then.error = error as Error + } + + return then + } + }) + + describe('case exists', () => { + const caseId = uuid() + const defendantNationalId = uuid() + const theCase = { id: caseId } + const request = { params: { caseId, defendantNationalId }, case: undefined } + let then: Then + + beforeEach(async () => { + mockRequest.mockReturnValueOnce(request) + const mockFindOne = mockCaseModel.findOne as jest.Mock + mockFindOne.mockResolvedValueOnce(theCase) + + then = await givenWhenThen() + }) + + it('should activate', () => { + expect(mockCaseModel.findOne).toHaveBeenCalledWith({ + include: [ + { + model: Defendant, + as: 'defendants', + include: [ + { + model: Subpoena, + as: 'subpoenas', + order: [['created', 'DESC']], + }, + ], + }, + { model: Institution, as: 'court' }, + { model: Institution, as: 'prosecutorsOffice' }, + { model: User, as: 'judge' }, + { + model: User, + as: 'prosecutor', + include: [{ model: Institution, as: 'institution' }], + }, + { model: DateLog, as: 'dateLogs' }, + ], + attributes: ['courtCaseNumber', 'id'], + where: { + type: CaseType.INDICTMENT, + id: caseId, + state: { [Op.not]: CaseState.DELETED }, + isArchived: false, + '$defendants.national_id$': + normalizeAndFormatNationalId(defendantNationalId), + }, + }) + expect(then.result).toBe(true) + expect(request.case).toBe(theCase) + }) + }) + + describe('case does not exist', () => { + const caseId = uuid() + const defendantNationalId = uuid() + let then: Then + + beforeEach(async () => { + mockRequest.mockReturnValueOnce({ + params: { caseId, defendantNationalId }, + }) + + then = await givenWhenThen() + }) + + it('should throw NotFoundException', () => { + expect(then.error).toBeInstanceOf(NotFoundException) + expect(then.error.message).toBe(`Case ${caseId} does not exist`) + }) + }) + + describe('missing case id', () => { + let then: Then + + beforeEach(async () => { + mockRequest.mockReturnValueOnce({ params: {} }) + + then = await givenWhenThen() + }) + + it('should throw BadRequestException', () => { + expect(then.error).toBeInstanceOf(BadRequestException) + expect(then.error.message).toBe('Missing case id') + }) + }) + + describe('missing defendant national id', () => { + const caseId = uuid() + let then: Then + + beforeEach(async () => { + mockRequest.mockReturnValueOnce({ params: { caseId } }) + + then = await givenWhenThen() + }) + + it('should throw BadRequestException', () => { + expect(then.error).toBeInstanceOf(BadRequestException) + expect(then.error.message).toBe('Missing defendant national id') + }) + }) +}) diff --git a/apps/judicial-system/backend/src/app/modules/case/guards/test/mergedCaseExistsGuard.spec.ts b/apps/judicial-system/backend/src/app/modules/case/guards/test/mergedCaseExistsGuard.spec.ts new file mode 100644 index 000000000000..9800abe3a1c0 --- /dev/null +++ b/apps/judicial-system/backend/src/app/modules/case/guards/test/mergedCaseExistsGuard.spec.ts @@ -0,0 +1,130 @@ +import { uuid } from 'uuidv4' + +import { + BadRequestException, + ExecutionContext, + InternalServerErrorException, +} from '@nestjs/common' + +import { createTestingCaseModule } from '../../test/createTestingCaseModule' + +import { MergedCaseExistsGuard } from '../mergedCaseExists.guard' + +interface Then { + result: boolean + error: Error +} + +type GivenWhenThen = () => Promise + +describe('Merged Case Exists Guard', () => { + const mockRequest = jest.fn() + let givenWhenThen: GivenWhenThen + + beforeEach(async () => { + const { caseService } = await createTestingCaseModule() + + givenWhenThen = async (): Promise => { + const guard = new MergedCaseExistsGuard(caseService) + const then = {} as Then + + try { + then.result = await guard.canActivate({ + switchToHttp: () => ({ getRequest: mockRequest }), + } as unknown as ExecutionContext) + } catch (error) { + then.error = error as Error + } + + return then + } + }) + + describe('merged case exists', () => { + const mergedCaseId = uuid() + const mergedCase = { id: mergedCaseId } + const caseId = uuid() + const theCase = { id: caseId, mergedCases: [mergedCase] } + const request = { + params: { caseId, mergedCaseId }, + mergedCaseParent: undefined, + case: theCase, + } + let then: Then + + beforeEach(async () => { + mockRequest.mockReturnValueOnce(request) + + then = await givenWhenThen() + }) + + it('should activate', () => { + expect(then.result).toBe(true) + expect(request.mergedCaseParent).toBe(theCase) + expect(request.params.caseId).toBe(mergedCaseId) + expect(request.case).toBe(mergedCase) + }) + }) + + describe('no merged case id', () => { + const caseId = uuid() + const theCase = { id: caseId, mergedCases: [] } + const request = { + params: { caseId }, + mergedCaseParent: undefined, + case: theCase, + } + let then: Then + + beforeEach(async () => { + mockRequest.mockReturnValueOnce(request) + + then = await givenWhenThen() + }) + + it('should activate', () => { + expect(then.result).toBe(true) + expect(request.mergedCaseParent).toBeUndefined() + expect(request.params.caseId).toBe(caseId) + expect(request.case).toBe(theCase) + }) + }) + + describe('merged case not found', () => { + const mergedCaseId = uuid() + const caseId = uuid() + const theCase = { id: caseId, mergedCases: [] } + const request = { + params: { caseId, mergedCaseId }, + mergedCaseParent: undefined, + case: theCase, + } + let then: Then + + beforeEach(async () => { + mockRequest.mockReturnValueOnce(request) + + then = await givenWhenThen() + }) + + it('should throw NotFoundException', () => { + expect(then.error).toBeInstanceOf(BadRequestException) + expect(then.error.message).toBe(`Merged case ${mergedCaseId} not found`) + }) + }) + + describe('missing case', () => { + let then: Then + + beforeEach(async () => { + mockRequest.mockReturnValueOnce({ params: { mergedCaseId: uuid() } }) + + then = await givenWhenThen() + }) + + it('should throw BadRequestException', () => { + expect(then.error).toBeInstanceOf(InternalServerErrorException) + expect(then.error.message).toBe('Missing case') + }) + }) +}) diff --git a/apps/judicial-system/backend/src/app/modules/case/internalCase.controller.ts b/apps/judicial-system/backend/src/app/modules/case/internalCase.controller.ts index 0a7dce8d89b7..60991a38b33f 100644 --- a/apps/judicial-system/backend/src/app/modules/case/internalCase.controller.ts +++ b/apps/judicial-system/backend/src/app/modules/case/internalCase.controller.ts @@ -2,6 +2,7 @@ import { BadRequestException, Body, Controller, + Get, Inject, Param, Post, @@ -27,12 +28,12 @@ import { import { EventService } from '../event' import { DeliverDto } from './dto/deliver.dto' import { DeliverCancellationNoticeDto } from './dto/deliverCancellationNotice.dto' -import { InternalCasesDto } from './dto/internalCases.dto' import { InternalCreateCaseDto } from './dto/internalCreateCase.dto' import { CurrentCase } from './guards/case.decorator' import { CaseCompletedGuard } from './guards/caseCompleted.guard' import { CaseExistsGuard } from './guards/caseExists.guard' import { CaseTypeGuard } from './guards/caseType.guard' +import { IndictmentCaseExistsForDefendantGuard } from './guards/indictmentCaseExistsForDefendant.guard' import { CaseInterceptor, CasesInterceptor, @@ -76,38 +77,42 @@ export class InternalCaseController { return this.internalCaseService.archive() } - @Post('cases/indictments') + @Get('cases/indictments/defendant/:defendantNationalId') @ApiOkResponse({ type: Case, isArray: true, - description: 'Gets all indictment cases for digital mailbox', + description: 'Gets all indictment cases for a given defendant', }) @UseInterceptors(CasesInterceptor) - getIndictmentCases( - @Body() internalCasesDto: InternalCasesDto, + getAllDefendantIndictmentCases( + @Param('defendantNationalId') defendantNationalId: string, ): Promise { - this.logger.debug('Getting all indictment cases') + this.logger.debug('Getting all indictment cases for a given defendant') - return this.internalCaseService.getIndictmentCases( - internalCasesDto.nationalId, + return this.internalCaseService.getAllDefendantIndictmentCases( + defendantNationalId, ) } - @Post('case/indictment/:caseId') + @UseGuards(IndictmentCaseExistsForDefendantGuard) + @Get('case/indictment/:caseId/defendant/:defendantNationalId') @ApiOkResponse({ type: Case, - description: 'Gets indictment case by id for digital mailbox', + description: 'Gets an existing indictment case by id for a given defendant', }) @UseInterceptors(CaseInterceptor) - getIndictmentCase( + getDefendantIndictmentCaseById( @Param('caseId') caseId: string, - @Body() internalCasesDto: InternalCasesDto, + @Param('defendantNationalId') defendantNationalId: string, + @CurrentCase() theCase: Case, ): Promise { - this.logger.debug(`Getting indictment case ${caseId}`) + this.logger.debug( + `Getting indictment case ${caseId} by id for a given defendant`, + ) - return this.internalCaseService.getIndictmentCase( - caseId, - internalCasesDto.nationalId, + return this.internalCaseService.getDefendantIndictmentCase( + theCase, + defendantNationalId, ) } diff --git a/apps/judicial-system/backend/src/app/modules/case/internalCase.service.ts b/apps/judicial-system/backend/src/app/modules/case/internalCase.service.ts index c47fd37ca093..a969cf40351b 100644 --- a/apps/judicial-system/backend/src/app/modules/case/internalCase.service.ts +++ b/apps/judicial-system/backend/src/app/modules/case/internalCase.service.ts @@ -1,6 +1,7 @@ import CryptoJS from 'crypto-js' import format from 'date-fns/format' import { Base64 } from 'js-base64' +import { Op } from 'sequelize' import { Sequelize } from 'sequelize-typescript' import { @@ -57,7 +58,7 @@ import { CaseFile, FileService } from '../file' import { IndictmentCount, IndictmentCountService } from '../indictment-count' import { Institution } from '../institution' import { PoliceDocument, PoliceDocumentType, PoliceService } from '../police' -import { Subpoena } from '../subpoena' +import { Subpoena, SubpoenaService } from '../subpoena' import { User, UserService } from '../user' import { InternalCreateCaseDto } from './dto/internalCreateCase.dto' import { archiveFilter } from './filters/case.archiveFilter' @@ -171,6 +172,8 @@ export class InternalCaseService { private readonly fileService: FileService, @Inject(forwardRef(() => DefendantService)) private readonly defendantService: DefendantService, + @Inject(forwardRef(() => SubpoenaService)) + private readonly subpoenaService: SubpoenaService, @Inject(forwardRef(() => PdfService)) private readonly pdfService: PdfService, @Inject(LOGGER_PROVIDER) private readonly logger: Logger, @@ -1180,7 +1183,7 @@ export class InternalCaseService { // As this is only currently used by the digital mailbox API // we will only return indictment cases that have a court date - async getIndictmentCases(nationalId: string): Promise { + async getAllDefendantIndictmentCases(nationalId: string): Promise { return this.caseModel.findAll({ include: [ { @@ -1213,8 +1216,11 @@ export class InternalCaseService { }) } - async getIndictmentCase(caseId: string, nationalId: string): Promise { - const caseById = await this.caseModel.findOne({ + async findByIdAndDefendantNationalId( + caseId: string, + defendantNationalId: string, + ): Promise { + const theCase = await this.caseModel.findOne({ include: [ { model: Defendant, @@ -1241,17 +1247,39 @@ export class InternalCaseService { where: { type: CaseType.INDICTMENT, id: caseId, - // The national id could be without a hyphen or with a hyphen so we need to - // search for both - '$defendants.national_id$': normalizeAndFormatNationalId(nationalId), + state: { [Op.not]: CaseState.DELETED }, + isArchived: false, + // This select only defendants with the given national id, other defendants are not included + '$defendants.national_id$': + normalizeAndFormatNationalId(defendantNationalId), }, }) - if (!caseById) { - throw new NotFoundException(`Case ${caseId} not found`) + if (!theCase) { + throw new NotFoundException(`Case ${caseId} does not exist`) + } + + return theCase + } + + async getDefendantIndictmentCase( + theCase: Case, + defendantNationalId: string, + ): Promise { + const subpoena = theCase.defendants?.[0].subpoenas?.[0] + + if (!subpoena) { + return theCase + } + + const latestSubpoena = await this.subpoenaService.getSubpoena(subpoena) + + if (latestSubpoena === subpoena) { + // The subpoena was up to date + return theCase } - return caseById + return this.findByIdAndDefendantNationalId(theCase.id, defendantNationalId) } countIndictmentsWaitingForConfirmation(prosecutorsOfficeId: string) { diff --git a/apps/judicial-system/backend/src/app/modules/case/test/createTestingCaseModule.ts b/apps/judicial-system/backend/src/app/modules/case/test/createTestingCaseModule.ts index 358e3028963a..3ecf8b656acc 100644 --- a/apps/judicial-system/backend/src/app/modules/case/test/createTestingCaseModule.ts +++ b/apps/judicial-system/backend/src/app/modules/case/test/createTestingCaseModule.ts @@ -180,6 +180,9 @@ export const createTestingCaseModule = async () => { const caseService = caseModule.get(CaseService) + const internalCaseService = + caseModule.get(InternalCaseService) + const limitedAccessCaseService = caseModule.get( LimitedAccessCaseService, ) @@ -213,6 +216,7 @@ export const createTestingCaseModule = async () => { caseStringModel, caseConfig, caseService, + internalCaseService, limitedAccessCaseService, caseController, internalCaseController, diff --git a/apps/judicial-system/backend/src/app/modules/case/test/internalCaseController/getIndictmentCaseByIdGuards.spec.ts b/apps/judicial-system/backend/src/app/modules/case/test/internalCaseController/getIndictmentCaseByIdGuards.spec.ts new file mode 100644 index 000000000000..5854213f6614 --- /dev/null +++ b/apps/judicial-system/backend/src/app/modules/case/test/internalCaseController/getIndictmentCaseByIdGuards.spec.ts @@ -0,0 +1,21 @@ +import { IndictmentCaseExistsForDefendantGuard } from '../../guards/indictmentCaseExistsForDefendant.guard' +import { InternalCaseController } from '../../internalCase.controller' + +describe('InternalCaseController - Get defendant indictment case by id guards', () => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let guards: any[] + + beforeEach(() => { + guards = Reflect.getMetadata( + '__guards__', + InternalCaseController.prototype.getDefendantIndictmentCaseById, + ) + }) + + it('should have the right guard configuration', () => { + expect(guards).toHaveLength(1) + expect(new guards[0]()).toBeInstanceOf( + IndictmentCaseExistsForDefendantGuard, + ) + }) +}) diff --git a/apps/judicial-system/backend/src/app/modules/defendant/civilClaimant.controller.ts b/apps/judicial-system/backend/src/app/modules/defendant/civilClaimant.controller.ts index 439904735d4e..bd19b67c84f7 100644 --- a/apps/judicial-system/backend/src/app/modules/defendant/civilClaimant.controller.ts +++ b/apps/judicial-system/backend/src/app/modules/defendant/civilClaimant.controller.ts @@ -28,13 +28,15 @@ import { } from '../../guards' import { Case, CaseExistsGuard, CaseWriteGuard, CurrentCase } from '../case' import { UpdateCivilClaimantDto } from './dto/updateCivilClaimant.dto' +import { CurrentCivilClaimant } from './guards/civilClaimaint.decorator' +import { CivilClaimantExistsGuard } from './guards/civilClaimantExists.guard' import { CivilClaimant } from './models/civilClaimant.model' import { DeleteCivilClaimantResponse } from './models/deleteCivilClaimant.response' import { CivilClaimantService } from './civilClaimant.service' -@UseGuards(JwtAuthGuard, RolesGuard) @Controller('api/case/:caseId/civilClaimant') @ApiTags('civilClaimants') +@UseGuards(JwtAuthGuard, RolesGuard) export class CivilClaimantController { constructor( private readonly civilClaimantService: CivilClaimantService, @@ -63,7 +65,7 @@ export class CivilClaimantController { return this.civilClaimantService.create(theCase) } - @UseGuards(CaseExistsGuard, CaseWriteGuard) + @UseGuards(CaseExistsGuard, CaseWriteGuard, CivilClaimantExistsGuard) @RolesRules( prosecutorRule, prosecutorRepresentativeRule, @@ -79,19 +81,20 @@ export class CivilClaimantController { async update( @Param('caseId') caseId: string, @Param('civilClaimantId') civilClaimantId: string, + @CurrentCivilClaimant() civilClaimant: CivilClaimant, @Body() updateCivilClaimantDto: UpdateCivilClaimantDto, ): Promise { this.logger.debug( - `Updating civil claimant ${civilClaimantId} in case ${caseId}`, + `Updating civil claimant ${civilClaimantId} of case ${caseId}`, ) return this.civilClaimantService.update( caseId, - civilClaimantId, + civilClaimant, updateCivilClaimantDto, ) } - @UseGuards(CaseExistsGuard, CaseWriteGuard) + @UseGuards(CaseExistsGuard, CaseWriteGuard, CivilClaimantExistsGuard) @RolesRules(prosecutorRule, prosecutorRepresentativeRule) @Delete(':civilClaimantId') @ApiOkResponse({ @@ -102,7 +105,9 @@ export class CivilClaimantController { @Param('caseId') caseId: string, @Param('civilClaimantId') civilClaimantId: string, ): Promise { - this.logger.debug(`Deleting civil claimant ${civilClaimantId}`) + this.logger.debug( + `Deleting civil claimant ${civilClaimantId} of case ${caseId}`, + ) const deleted = await this.civilClaimantService.delete( caseId, diff --git a/apps/judicial-system/backend/src/app/modules/defendant/civilClaimant.service.ts b/apps/judicial-system/backend/src/app/modules/defendant/civilClaimant.service.ts index 285092718ba5..c0d676283258 100644 --- a/apps/judicial-system/backend/src/app/modules/defendant/civilClaimant.service.ts +++ b/apps/judicial-system/backend/src/app/modules/defendant/civilClaimant.service.ts @@ -1,6 +1,10 @@ import { Op } from 'sequelize' -import { Inject, Injectable } from '@nestjs/common' +import { + Inject, + Injectable, + InternalServerErrorException, +} from '@nestjs/common' import { InjectModel } from '@nestjs/sequelize' import type { Logger } from '@island.is/logging' @@ -33,17 +37,18 @@ export class CivilClaimantService { } private async sendUpdateCivilClaimantMessages( - update: UpdateCivilClaimantDto, + oldCivilClaimant: CivilClaimant, updatedCivilClaimant: CivilClaimant, ): Promise { - if (update.isSpokespersonConfirmed === true) { + if ( + updatedCivilClaimant.isSpokespersonConfirmed && + !oldCivilClaimant.isSpokespersonConfirmed + ) { return this.messageService.sendMessagesToQueue([ { type: MessageType.CIVIL_CLAIMANT_NOTIFICATION, caseId: updatedCivilClaimant.caseId, - body: { - type: CivilClaimantNotificationType.SPOKESPERSON_ASSIGNED, - }, + body: { type: CivilClaimantNotificationType.SPOKESPERSON_ASSIGNED }, elementId: updatedCivilClaimant.id, }, ]) @@ -52,13 +57,13 @@ export class CivilClaimantService { async update( caseId: string, - civilClaimantId: string, + civilClaimant: CivilClaimant, update: UpdateCivilClaimantDto, ): Promise { const [numberOfAffectedRows, civilClaimants] = await this.civilClaimantModel.update(update, { where: { - id: civilClaimantId, + id: civilClaimant.id, caseId: caseId, }, returning: true, @@ -66,15 +71,22 @@ export class CivilClaimantService { if (numberOfAffectedRows > 1) { this.logger.error( - `Unexpected number of rows (${numberOfAffectedRows}) affected when updating civil claimant ${civilClaimantId} of case ${caseId}`, + `Unexpected number of rows (${numberOfAffectedRows}) affected when updating civil claimant ${civilClaimant.id} of case ${caseId}`, ) } else if (numberOfAffectedRows < 1) { - throw new Error(`Could not update civil claimant ${civilClaimantId}`) + throw new InternalServerErrorException( + `Could not update civil claimant ${civilClaimant.id} of case ${caseId}`, + ) } - await this.sendUpdateCivilClaimantMessages(update, civilClaimants[0]) + const updatedCivilClaimant = civilClaimants[0] + + await this.sendUpdateCivilClaimantMessages( + civilClaimant, + updatedCivilClaimant, + ) - return civilClaimants[0] + return updatedCivilClaimant } async delete(caseId: string, civilClaimantId: string): Promise { @@ -91,7 +103,9 @@ export class CivilClaimantService { `Unexpected number of rows (${numberOfAffectedRows}) affected when deleting civil claimant ${civilClaimantId} of case ${caseId}`, ) } else if (numberOfAffectedRows < 1) { - throw new Error(`Could not delete civil claimant ${civilClaimantId}`) + throw new InternalServerErrorException( + `Could not delete civil claimant ${civilClaimantId}`, + ) } return true diff --git a/apps/judicial-system/backend/src/app/modules/defendant/defendant.controller.ts b/apps/judicial-system/backend/src/app/modules/defendant/defendant.controller.ts index 83edd4cd8b1e..de64f7538c41 100644 --- a/apps/judicial-system/backend/src/app/modules/defendant/defendant.controller.ts +++ b/apps/judicial-system/backend/src/app/modules/defendant/defendant.controller.ts @@ -38,9 +38,9 @@ import { Defendant } from './models/defendant.model' import { DeleteDefendantResponse } from './models/delete.response' import { DefendantService } from './defendant.service' -@UseGuards(JwtAuthGuard, RolesGuard) @Controller('api/case/:caseId/defendant') @ApiTags('defendants') +@UseGuards(JwtAuthGuard, RolesGuard) export class DefendantController { constructor( private readonly defendantService: DefendantService, diff --git a/apps/judicial-system/backend/src/app/modules/defendant/defendant.service.ts b/apps/judicial-system/backend/src/app/modules/defendant/defendant.service.ts index fca293e95ffe..6276f0634115 100644 --- a/apps/judicial-system/backend/src/app/modules/defendant/defendant.service.ts +++ b/apps/judicial-system/backend/src/app/modules/defendant/defendant.service.ts @@ -95,26 +95,6 @@ export class DefendantService { return message } - private getUpdatedDefendant( - numberOfAffectedRows: number, - defendants: Defendant[], - defendantId: string, - caseId: string, - ): Defendant { - if (numberOfAffectedRows > 1) { - // Tolerate failure, but log error - this.logger.error( - `Unexpected number of rows (${numberOfAffectedRows}) affected when updating defendant ${defendantId} of case ${caseId}`, - ) - } else if (numberOfAffectedRows < 1) { - throw new InternalServerErrorException( - `Could not update defendant ${defendantId} of case ${caseId}`, - ) - } - - return defendants[0] - } - private async sendRequestCaseUpdateDefendantMessages( theCase: Case, updatedDefendant: Defendant, @@ -255,12 +235,18 @@ export class DefendantService { { where: { id: defendantId, caseId }, returning: true, transaction }, ) - return this.getUpdatedDefendant( - numberOfAffectedRows, - defendants, - defendantId, - caseId, - ) + if (numberOfAffectedRows > 1) { + // Tolerate failure, but log error + this.logger.error( + `Unexpected number of rows (${numberOfAffectedRows}) affected when updating defendant ${defendantId} of case ${caseId}`, + ) + } else if (numberOfAffectedRows < 1) { + throw new InternalServerErrorException( + `Could not update defendant ${defendantId} of case ${caseId}`, + ) + } + + return defendants[0] } async updateRequestCaseDefendant( diff --git a/apps/judicial-system/backend/src/app/modules/defendant/test/civilClaimantController/civilClaimantControllerGuards.spec.ts b/apps/judicial-system/backend/src/app/modules/defendant/test/civilClaimantController/civilClaimantControllerGuards.spec.ts new file mode 100644 index 000000000000..a6029164ba58 --- /dev/null +++ b/apps/judicial-system/backend/src/app/modules/defendant/test/civilClaimantController/civilClaimantControllerGuards.spec.ts @@ -0,0 +1,18 @@ +import { JwtAuthGuard, RolesGuard } from '@island.is/judicial-system/auth' + +import { CivilClaimantController } from '../../civilClaimant.controller' + +describe('CivilClaimantController - guards', () => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let guards: any[] + + beforeEach(() => { + guards = Reflect.getMetadata('__guards__', CivilClaimantController) + }) + + it('should have the right guard configuration', () => { + expect(guards).toHaveLength(2) + expect(new guards[0]()).toBeInstanceOf(JwtAuthGuard) + expect(new guards[1]()).toBeInstanceOf(RolesGuard) + }) +}) diff --git a/apps/judicial-system/backend/src/app/modules/defendant/test/civilClaimantController/deleteGuards.spec.ts b/apps/judicial-system/backend/src/app/modules/defendant/test/civilClaimantController/deleteGuards.spec.ts index 20e8ef89e2f6..522d7fa661a7 100644 --- a/apps/judicial-system/backend/src/app/modules/defendant/test/civilClaimantController/deleteGuards.spec.ts +++ b/apps/judicial-system/backend/src/app/modules/defendant/test/civilClaimantController/deleteGuards.spec.ts @@ -2,10 +2,15 @@ import { CanActivate } from '@nestjs/common' import { CaseExistsGuard, CaseWriteGuard } from '../../../case' import { CivilClaimantController } from '../../civilClaimant.controller' +import { CivilClaimantExistsGuard } from '../../guards/civilClaimantExists.guard' describe('CivilClaimantController - Delete guards', () => { let guards: Array CanActivate> - const expectedGuards = [CaseExistsGuard, CaseWriteGuard] + const expectedGuards = [ + CaseExistsGuard, + CaseWriteGuard, + CivilClaimantExistsGuard, + ] beforeEach(() => { guards = Reflect.getMetadata( diff --git a/apps/judicial-system/backend/src/app/modules/defendant/test/civilClaimantController/update.spec.ts b/apps/judicial-system/backend/src/app/modules/defendant/test/civilClaimantController/update.spec.ts index 10eba40190cc..391d99e3eb0a 100644 --- a/apps/judicial-system/backend/src/app/modules/defendant/test/civilClaimantController/update.spec.ts +++ b/apps/judicial-system/backend/src/app/modules/defendant/test/civilClaimantController/update.spec.ts @@ -42,7 +42,12 @@ describe('CivilClaimantController - Update', () => { const then = {} as Then await civilClaimantController - .update(caseId, civilClaimantId, updateData) + .update( + caseId, + civilClaimantId, + { id: civilClaimantId } as CivilClaimant, + updateData, + ) .then((result) => (then.result = result)) .catch((error) => (then.error = error)) diff --git a/apps/judicial-system/backend/src/app/modules/defendant/test/civilClaimantController/updateGuards.spec.ts b/apps/judicial-system/backend/src/app/modules/defendant/test/civilClaimantController/updateGuards.spec.ts index d333af01f86f..cc106a2fce27 100644 --- a/apps/judicial-system/backend/src/app/modules/defendant/test/civilClaimantController/updateGuards.spec.ts +++ b/apps/judicial-system/backend/src/app/modules/defendant/test/civilClaimantController/updateGuards.spec.ts @@ -2,10 +2,15 @@ import { CanActivate } from '@nestjs/common' import { CaseExistsGuard, CaseWriteGuard } from '../../../case' import { CivilClaimantController } from '../../civilClaimant.controller' +import { CivilClaimantExistsGuard } from '../../guards/civilClaimantExists.guard' describe('CivilClaimantController - Update guards', () => { let guards: Array CanActivate> - const expectedGuards = [CaseExistsGuard, CaseWriteGuard] + const expectedGuards = [ + CaseExistsGuard, + CaseWriteGuard, + CivilClaimantExistsGuard, + ] beforeEach(() => { guards = Reflect.getMetadata( diff --git a/apps/judicial-system/backend/src/app/modules/event-log/eventLog.controller.ts b/apps/judicial-system/backend/src/app/modules/event-log/eventLog.controller.ts index 30af6da23667..853fe1fe0ef3 100644 --- a/apps/judicial-system/backend/src/app/modules/event-log/eventLog.controller.ts +++ b/apps/judicial-system/backend/src/app/modules/event-log/eventLog.controller.ts @@ -1,5 +1,5 @@ import { Body, Controller, Post, UseGuards } from '@nestjs/common' -import { ApiCreatedResponse } from '@nestjs/swagger' +import { ApiCreatedResponse, ApiTags } from '@nestjs/swagger' import { TokenGuard } from '@island.is/judicial-system/auth' @@ -7,6 +7,7 @@ import { CreateEventLogDto } from './dto/createEventLog.dto' import { EventLogService } from './eventLog.service' @Controller('api/eventLog') +@ApiTags('eventLogs') export class EventLogController { constructor(private readonly eventLogService: EventLogService) {} diff --git a/apps/judicial-system/backend/src/app/modules/file/file.controller.ts b/apps/judicial-system/backend/src/app/modules/file/file.controller.ts index ca960a15f389..70a0ffe302a9 100644 --- a/apps/judicial-system/backend/src/app/modules/file/file.controller.ts +++ b/apps/judicial-system/backend/src/app/modules/file/file.controller.ts @@ -65,9 +65,9 @@ import { SignedUrl } from './models/signedUrl.model' import { UploadFileToCourtResponse } from './models/uploadFileToCourt.response' import { FileService } from './file.service' -@UseGuards(JwtAuthGuard) @Controller('api/case/:caseId') @ApiTags('files') +@UseGuards(JwtAuthGuard) export class FileController { constructor( private readonly fileService: FileService, diff --git a/apps/judicial-system/backend/src/app/modules/file/internalFile.controller.ts b/apps/judicial-system/backend/src/app/modules/file/internalFile.controller.ts index 6c6f5ddba847..2241f9db7bf7 100644 --- a/apps/judicial-system/backend/src/app/modules/file/internalFile.controller.ts +++ b/apps/judicial-system/backend/src/app/modules/file/internalFile.controller.ts @@ -25,9 +25,9 @@ import { DeliverResponse } from './models/deliver.response' import { CaseFile } from './models/file.model' import { FileService } from './file.service' -@UseGuards(TokenGuard) @Controller('api/internal/case/:caseId') @ApiTags('internal files') +@UseGuards(TokenGuard) export class InternalFileController { constructor( private readonly fileService: FileService, diff --git a/apps/judicial-system/backend/src/app/modules/file/limitedAccessFile.controller.ts b/apps/judicial-system/backend/src/app/modules/file/limitedAccessFile.controller.ts index d6399c0a36a4..5285a7aa0a70 100644 --- a/apps/judicial-system/backend/src/app/modules/file/limitedAccessFile.controller.ts +++ b/apps/judicial-system/backend/src/app/modules/file/limitedAccessFile.controller.ts @@ -55,9 +55,9 @@ import { PresignedPost } from './models/presignedPost.model' import { SignedUrl } from './models/signedUrl.model' import { FileService } from './file.service' -@UseGuards(JwtAuthGuard, RolesGuard, LimitedAccessCaseExistsGuard) @Controller('api/case/:caseId/limitedAccess') @ApiTags('files') +@UseGuards(JwtAuthGuard, RolesGuard, LimitedAccessCaseExistsGuard) export class LimitedAccessFileController { constructor( private readonly fileService: FileService, diff --git a/apps/judicial-system/backend/src/app/modules/indictment-count/indictmentCount.controller.ts b/apps/judicial-system/backend/src/app/modules/indictment-count/indictmentCount.controller.ts index 1c32f7a87303..3303ff096517 100644 --- a/apps/judicial-system/backend/src/app/modules/indictment-count/indictmentCount.controller.ts +++ b/apps/judicial-system/backend/src/app/modules/indictment-count/indictmentCount.controller.ts @@ -27,9 +27,9 @@ import { DeleteIndictmentCountResponse } from './models/delete.response' import { IndictmentCount } from './models/indictmentCount.model' import { IndictmentCountService } from './indictmentCount.service' -@UseGuards(JwtAuthGuard, RolesGuard) @Controller('api/case/:caseId/indictmentCount') @ApiTags('indictment-counts') +@UseGuards(JwtAuthGuard, RolesGuard) export class IndictmentCountController { constructor( private readonly indictmentCountService: IndictmentCountService, diff --git a/apps/judicial-system/backend/src/app/modules/notification/internalNotification.controller.ts b/apps/judicial-system/backend/src/app/modules/notification/internalNotification.controller.ts index 5e03c0f784b3..68131739f280 100644 --- a/apps/judicial-system/backend/src/app/modules/notification/internalNotification.controller.ts +++ b/apps/judicial-system/backend/src/app/modules/notification/internalNotification.controller.ts @@ -41,9 +41,9 @@ import { InstitutionNotificationService } from './services/institutionNotificati import { SubpoenaNotificationService } from './services/subpoenaNotification/subpoenaNotification.service' import { NotificationDispatchService } from './notificationDispatch.service' -@UseGuards(TokenGuard) @Controller('api/internal') @ApiTags('internal notifications') +@UseGuards(TokenGuard) export class InternalNotificationController { constructor( private readonly caseNotificationService: CaseNotificationService, diff --git a/apps/judicial-system/backend/src/app/modules/notification/notification.controller.ts b/apps/judicial-system/backend/src/app/modules/notification/notification.controller.ts index 4b8ce92cb0c2..60f979a3ff9f 100644 --- a/apps/judicial-system/backend/src/app/modules/notification/notification.controller.ts +++ b/apps/judicial-system/backend/src/app/modules/notification/notification.controller.ts @@ -34,9 +34,9 @@ import { import { SendNotificationResponse } from './models/sendNotification.response' import { NotificationService } from './notification.service' -@UseGuards(JwtAuthGuard, RolesGuard, CaseExistsGuard) @Controller('api/case/:caseId/notification') @ApiTags('notifications') +@UseGuards(JwtAuthGuard, RolesGuard, CaseExistsGuard) export class NotificationController { constructor( private readonly notificationService: NotificationService, diff --git a/apps/judicial-system/backend/src/app/modules/police/index.ts b/apps/judicial-system/backend/src/app/modules/police/index.ts index 6f0613c334bc..f269e4fca945 100644 --- a/apps/judicial-system/backend/src/app/modules/police/index.ts +++ b/apps/judicial-system/backend/src/app/modules/police/index.ts @@ -1,5 +1,6 @@ export { PoliceDocumentType, PoliceDocument, + SubpoenaInfo, PoliceService, } from './police.service' diff --git a/apps/judicial-system/backend/src/app/modules/police/internalPolice.controller.ts b/apps/judicial-system/backend/src/app/modules/police/internalPolice.controller.ts deleted file mode 100644 index 4680100a5d85..000000000000 --- a/apps/judicial-system/backend/src/app/modules/police/internalPolice.controller.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { Controller, Get, Inject, Param, UseGuards } from '@nestjs/common' -import { ApiOkResponse, ApiTags } from '@nestjs/swagger' - -import { type Logger, LOGGER_PROVIDER } from '@island.is/logging' - -import { TokenGuard } from '@island.is/judicial-system/auth' - -import { Case, CaseExistsGuard, CurrentCase } from '../case' -import { Subpoena } from '../subpoena' -import { PoliceService } from './police.service' - -@UseGuards(TokenGuard, CaseExistsGuard) -@Controller('api/internal/case/:caseId') -@ApiTags('internal police') -export class InternalPoliceController { - constructor( - private readonly policeService: PoliceService, - @Inject(LOGGER_PROVIDER) private readonly logger: Logger, - ) {} - - @Get('subpoenaStatus/:subpoenaId') - @ApiOkResponse({ - type: Subpoena, - description: 'Gets subpoena status', - }) - getSubpoenaStatus( - @Param('subpoenaId') subpoenaId: string, - @CurrentCase() theCase: Case, - ): Promise { - this.logger.debug(`Gets subpoena status in case ${theCase.id}`) - - return this.policeService.getSubpoenaStatus(subpoenaId) - } -} diff --git a/apps/judicial-system/backend/src/app/modules/police/models/subpoenaInfo.model.ts b/apps/judicial-system/backend/src/app/modules/police/models/subpoenaInfo.model.ts new file mode 100644 index 000000000000..2bd84b54109a --- /dev/null +++ b/apps/judicial-system/backend/src/app/modules/police/models/subpoenaInfo.model.ts @@ -0,0 +1,49 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger' + +import { ServiceStatus } from '@island.is/judicial-system/types' + +interface SubpoenaUpdate + extends Pick< + SubpoenaInfo, + | 'serviceStatus' + | 'comment' + | 'servedBy' + | 'defenderNationalId' + | 'serviceDate' + > {} + +const subpoenaUpdateKeys: Array = [ + 'serviceStatus', + 'comment', + 'servedBy', + 'defenderNationalId', + 'serviceDate', +] + +export class SubpoenaInfo { + private isNewValueSetAndDifferent = ( + newValue: unknown, + oldValue: unknown, + ): boolean => Boolean(newValue) && newValue !== oldValue + + isSubpoenaInfoChanged(oldSubpoenaInfo: SubpoenaUpdate) { + return subpoenaUpdateKeys.some((key) => + this.isNewValueSetAndDifferent(this[key], oldSubpoenaInfo[key]), + ) + } + + @ApiProperty({ type: ServiceStatus }) + serviceStatus?: ServiceStatus + + @ApiPropertyOptional({ type: String }) + comment?: string + + @ApiPropertyOptional({ type: String }) + servedBy?: string + + @ApiPropertyOptional({ type: String }) + defenderNationalId?: string + + @ApiPropertyOptional({ type: Date }) + serviceDate?: Date +} diff --git a/apps/judicial-system/backend/src/app/modules/police/police.controller.ts b/apps/judicial-system/backend/src/app/modules/police/police.controller.ts index 40c5f1829985..c80db84e51db 100644 --- a/apps/judicial-system/backend/src/app/modules/police/police.controller.ts +++ b/apps/judicial-system/backend/src/app/modules/police/police.controller.ts @@ -21,13 +21,7 @@ import { } from '@island.is/judicial-system/auth' import type { User } from '@island.is/judicial-system/types' -import { - districtCourtAssistantRule, - districtCourtJudgeRule, - districtCourtRegistrarRule, - prosecutorRepresentativeRule, - prosecutorRule, -} from '../../guards' +import { prosecutorRepresentativeRule, prosecutorRule } from '../../guards' import { Case, CaseExistsGuard, @@ -36,13 +30,14 @@ import { CaseReadGuard, CurrentCase, } from '../case' -import { Subpoena } from '../subpoena' import { UploadPoliceCaseFileDto } from './dto/uploadPoliceCaseFile.dto' import { PoliceCaseFile } from './models/policeCaseFile.model' import { PoliceCaseInfo } from './models/policeCaseInfo.model' import { UploadPoliceCaseFileResponse } from './models/uploadPoliceCaseFile.response' import { PoliceService } from './police.service' +@Controller('api/case/:caseId') +@ApiTags('police files') @UseGuards( JwtAuthGuard, RolesGuard, @@ -50,8 +45,6 @@ import { PoliceService } from './police.service' CaseReadGuard, CaseNotCompletedGuard, ) -@Controller('api/case/:caseId') -@ApiTags('police files') export class PoliceController { constructor( private readonly policeService: PoliceService, @@ -76,28 +69,6 @@ export class PoliceController { return this.policeService.getAllPoliceCaseFiles(theCase.id, user) } - @RolesRules( - prosecutorRule, - prosecutorRepresentativeRule, - districtCourtJudgeRule, - districtCourtAssistantRule, - districtCourtRegistrarRule, - ) - @Get('subpoenaStatus/:subpoenaId') - @ApiOkResponse({ - type: Subpoena, - description: 'Gets subpoena status', - }) - getSubpoenaStatus( - @Param('subpoenaId') subpoenaId: string, - @CurrentCase() theCase: Case, - @CurrentHttpUser() user: User, - ): Promise { - this.logger.debug(`Gets subpoena status in case ${theCase.id}`) - - return this.policeService.getSubpoenaStatus(subpoenaId, user) - } - @RolesRules(prosecutorRule, prosecutorRepresentativeRule) @UseInterceptors(CaseOriginalAncestorInterceptor) @Get('policeCaseInfo') diff --git a/apps/judicial-system/backend/src/app/modules/police/police.module.ts b/apps/judicial-system/backend/src/app/modules/police/police.module.ts index c6868f89b050..1df72c2b8794 100644 --- a/apps/judicial-system/backend/src/app/modules/police/police.module.ts +++ b/apps/judicial-system/backend/src/app/modules/police/police.module.ts @@ -1,7 +1,6 @@ import { forwardRef, Module } from '@nestjs/common' import { AwsS3Module, CaseModule, EventModule, SubpoenaModule } from '../index' -import { InternalPoliceController } from './internalPolice.controller' import { PoliceController } from './police.controller' import { PoliceService } from './police.service' @@ -14,6 +13,6 @@ import { PoliceService } from './police.service' ], providers: [PoliceService], exports: [PoliceService], - controllers: [PoliceController, InternalPoliceController], + controllers: [PoliceController], }) export class PoliceModule {} diff --git a/apps/judicial-system/backend/src/app/modules/police/police.service.ts b/apps/judicial-system/backend/src/app/modules/police/police.service.ts index c753e4e4db5e..bdb539eb73cd 100644 --- a/apps/judicial-system/backend/src/app/modules/police/police.service.ts +++ b/apps/judicial-system/backend/src/app/modules/police/police.service.ts @@ -34,8 +34,7 @@ import { AwsS3Service } from '../aws-s3' import { Case } from '../case' import { Defendant } from '../defendant/models/defendant.model' import { EventService } from '../event' -import { Subpoena, SubpoenaService } from '../subpoena' -import { UpdateSubpoenaDto } from '../subpoena/dto/updateSubpoena.dto' +import { SubpoenaService } from '../subpoena' import { UploadPoliceCaseFileDto } from './dto/uploadPoliceCaseFile.dto' import { CreateSubpoenaResponse } from './models/createSubpoena.response' import { PoliceCaseFile } from './models/policeCaseFile.model' @@ -59,6 +58,14 @@ export interface PoliceDocument { courtDocument: string } +export interface SubpoenaInfo { + serviceStatus?: ServiceStatus + comment?: string + servedBy?: string + defenderNationalId?: string + serviceDate?: Date +} + const getChapter = (category?: string): number | undefined => { if (!category) { return undefined @@ -336,7 +343,10 @@ export class PoliceService { }) } - async getSubpoenaStatus(subpoenaId: string, user?: User): Promise { + async getSubpoenaStatus( + subpoenaId: string, + user?: User, + ): Promise { return this.fetchPoliceDocumentApi( `${this.xRoadPath}/GetSubpoenaStatus?id=${subpoenaId}`, ) @@ -347,37 +357,24 @@ export class PoliceService { this.subpoenaStructure.parse(response) - const subpoenaToUpdate = await this.subpoenaService.findBySubpoenaId( - subpoenaId, - ) - - const serviceStatus = response.deliveredToLawyer - ? ServiceStatus.DEFENDER - : response.prosecutedConfirmedSubpoenaThroughIslandis - ? ServiceStatus.ELECTRONICALLY - : response.deliveredOnPaper || response.delivered === true - ? ServiceStatus.IN_PERSON - : response.acknowledged === false && response.delivered === false - ? ServiceStatus.FAILED - : // TODO: handle expired - undefined - - if (serviceStatus === undefined) { - return subpoenaToUpdate + return { + serviceStatus: response.deliveredToLawyer + ? ServiceStatus.DEFENDER + : response.prosecutedConfirmedSubpoenaThroughIslandis + ? ServiceStatus.ELECTRONICALLY + : response.deliveredOnPaper || response.delivered === true + ? ServiceStatus.IN_PERSON + : response.acknowledged === false && response.delivered === false + ? ServiceStatus.FAILED + : // TODO: handle expired + undefined, + comment: response.comment ?? undefined, + servedBy: response.servedBy ?? undefined, + defenderNationalId: response.defenderNationalId ?? undefined, + serviceDate: response.servedAt + ? new Date(response.servedAt) + : undefined, } - - const updatedSubpoena = await this.subpoenaService.update( - subpoenaToUpdate, - { - comment: response.comment ?? undefined, - servedBy: response.servedBy ?? undefined, - defenderNationalId: response.defenderNationalId ?? undefined, - serviceDate: response.servedAt ?? undefined, - serviceStatus, - } as UpdateSubpoenaDto, - ) - - return updatedSubpoena } const reason = await res.text() @@ -395,7 +392,7 @@ export class PoliceService { } if (reason instanceof ServiceUnavailableException) { - // Act as if the case does not exist + // Act as if the subpoena does not exist throw new NotFoundException({ ...reason, message: `Subpoena ${subpoenaId} does not exist`, diff --git a/apps/judicial-system/backend/src/app/modules/subpoena/dto/updateSubpoena.dto.ts b/apps/judicial-system/backend/src/app/modules/subpoena/dto/updateSubpoena.dto.ts index 2394ff1249f2..dc1e815371e0 100644 --- a/apps/judicial-system/backend/src/app/modules/subpoena/dto/updateSubpoena.dto.ts +++ b/apps/judicial-system/backend/src/app/modules/subpoena/dto/updateSubpoena.dto.ts @@ -1,5 +1,6 @@ -import { Transform } from 'class-transformer' +import { Transform, Type } from 'class-transformer' import { + IsDate, IsEnum, IsOptional, IsString, @@ -26,9 +27,10 @@ export class UpdateSubpoenaDto { readonly servedBy?: string @IsOptional() - @IsString() - @ApiPropertyOptional({ type: String }) - readonly serviceDate?: string + @Type(() => Date) + @IsDate() + @ApiPropertyOptional({ type: Date }) + readonly serviceDate?: Date @IsOptional() @IsString() diff --git a/apps/judicial-system/backend/src/app/modules/subpoena/guards/policeSubpoenaExists.guard.ts b/apps/judicial-system/backend/src/app/modules/subpoena/guards/policeSubpoenaExists.guard.ts new file mode 100644 index 000000000000..e351570b9714 --- /dev/null +++ b/apps/judicial-system/backend/src/app/modules/subpoena/guards/policeSubpoenaExists.guard.ts @@ -0,0 +1,28 @@ +import { + BadRequestException, + CanActivate, + ExecutionContext, + Injectable, +} from '@nestjs/common' + +import { SubpoenaService } from '../subpoena.service' + +@Injectable() +export class PoliceSubpoenaExistsGuard implements CanActivate { + constructor(private readonly subpoenaService: SubpoenaService) {} + + async canActivate(context: ExecutionContext): Promise { + const request = context.switchToHttp().getRequest() + + const subpoenaId = request.params.subpoenaId + + if (!subpoenaId) { + throw new BadRequestException('Missing police subpoena id') + } + + // subpoenaId is the external police document id + request.subpoena = await this.subpoenaService.findBySubpoenaId(subpoenaId) + + return true + } +} diff --git a/apps/judicial-system/backend/src/app/modules/subpoena/guards/subpoenaExists.guard.ts b/apps/judicial-system/backend/src/app/modules/subpoena/guards/subpoenaExists.guard.ts index f5dc6c406cb1..e4c02b4dec2f 100644 --- a/apps/judicial-system/backend/src/app/modules/subpoena/guards/subpoenaExists.guard.ts +++ b/apps/judicial-system/backend/src/app/modules/subpoena/guards/subpoenaExists.guard.ts @@ -3,34 +3,28 @@ import { CanActivate, ExecutionContext, Injectable, + InternalServerErrorException, } from '@nestjs/common' import { Defendant } from '../../defendant' -import { SubpoenaService } from '../subpoena.service' @Injectable() export class SubpoenaExistsGuard implements CanActivate { - constructor(private readonly subpoenaService: SubpoenaService) {} - async canActivate(context: ExecutionContext): Promise { const request = context.switchToHttp().getRequest() - const subpoenaId = request.params.subpoenaId - - if (!subpoenaId) { - throw new BadRequestException('Missing subpoena id') - } - const defendant: Defendant = request.defendant if (!defendant) { - // subpoenaId is the external police document id - request.subpoena = await this.subpoenaService.findBySubpoenaId(subpoenaId) + throw new InternalServerErrorException('Missing defendant') + } - return true + const subpoenaId = request.params.subpoenaId + + if (!subpoenaId) { + throw new BadRequestException('Missing subpoena id') } - // subpoenaId is the internal subpoena id const subpoena = defendant.subpoenas?.find( (subpoena) => subpoena.id === subpoenaId, ) diff --git a/apps/judicial-system/backend/src/app/modules/subpoena/guards/test/policeSubpoenaExistsGuard.spec.ts b/apps/judicial-system/backend/src/app/modules/subpoena/guards/test/policeSubpoenaExistsGuard.spec.ts new file mode 100644 index 000000000000..c924bb6a938f --- /dev/null +++ b/apps/judicial-system/backend/src/app/modules/subpoena/guards/test/policeSubpoenaExistsGuard.spec.ts @@ -0,0 +1,110 @@ +import { uuid } from 'uuidv4' + +import { + BadRequestException, + ExecutionContext, + NotFoundException, +} from '@nestjs/common' + +import { createTestingSubpoenaModule } from '../../test/createTestingSubpoenaModule' + +import { Subpoena } from '../../models/subpoena.model' +import { include } from '../../subpoena.service' +import { PoliceSubpoenaExistsGuard } from '../policeSubpoenaExists.guard' + +interface Then { + result: boolean + error: Error +} + +type GivenWhenThen = () => Promise + +describe('Police Subpoena Exists Guard', () => { + const mockRequest = jest.fn() + let mockSubpoenaModel: typeof Subpoena + let givenWhenThen: GivenWhenThen + + beforeEach(async () => { + const { subpoenaModel, subpoenaService } = + await createTestingSubpoenaModule() + + mockSubpoenaModel = subpoenaModel + + givenWhenThen = async (): Promise => { + const guard = new PoliceSubpoenaExistsGuard(subpoenaService) + const then = {} as Then + + try { + then.result = await guard.canActivate({ + switchToHttp: () => ({ getRequest: mockRequest }), + } as unknown as ExecutionContext) + } catch (error) { + then.error = error as Error + } + + return then + } + }) + + describe('subpoena exists', () => { + const policeSubpoenaId = uuid() + const subpoena = { id: uuid(), subpoenaId: policeSubpoenaId } + const request = { + params: { subpoenaId: policeSubpoenaId }, + subpoena: undefined, + } + let then: Then + + beforeEach(async () => { + mockRequest.mockReturnValueOnce(request) + const mockFindOne = mockSubpoenaModel.findOne as jest.Mock + mockFindOne.mockResolvedValueOnce(subpoena) + + then = await givenWhenThen() + }) + + it('should activate', () => { + expect(mockSubpoenaModel.findOne).toHaveBeenCalledWith({ + include, + where: { subpoenaId: policeSubpoenaId }, + }) + expect(then.result).toBe(true) + expect(request.subpoena).toBe(subpoena) + }) + }) + + describe('subpoena does not exist', () => { + const policeSubpoenaId = uuid() + let then: Then + + beforeEach(async () => { + mockRequest.mockReturnValueOnce({ + params: { subpoenaId: policeSubpoenaId }, + }) + + then = await givenWhenThen() + }) + + it('should throw NotFoundException', () => { + expect(then.error).toBeInstanceOf(NotFoundException) + expect(then.error.message).toBe( + `Subpoena with police subpoena id ${policeSubpoenaId} does not exist`, + ) + }) + }) + + describe('missing subpoena id', () => { + let then: Then + + beforeEach(async () => { + mockRequest.mockReturnValueOnce({ params: {} }) + + then = await givenWhenThen() + }) + + it('should throw BadRequestException', () => { + expect(then.error).toBeInstanceOf(BadRequestException) + expect(then.error.message).toBe('Missing police subpoena id') + }) + }) +}) diff --git a/apps/judicial-system/backend/src/app/modules/subpoena/guards/test/subpoenaExistsGuard.spec.ts b/apps/judicial-system/backend/src/app/modules/subpoena/guards/test/subpoenaExistsGuard.spec.ts new file mode 100644 index 000000000000..504b56c01e61 --- /dev/null +++ b/apps/judicial-system/backend/src/app/modules/subpoena/guards/test/subpoenaExistsGuard.spec.ts @@ -0,0 +1,109 @@ +import { uuid } from 'uuidv4' + +import { + BadRequestException, + ExecutionContext, + InternalServerErrorException, +} from '@nestjs/common' + +import { SubpoenaExistsGuard } from '../subpoenaExists.guard' + +interface Then { + result: boolean + error: Error +} + +type GivenWhenThen = () => Promise + +describe('Subpoena Exists Guard', () => { + const mockRequest = jest.fn() + let givenWhenThen: GivenWhenThen + + beforeEach(async () => { + givenWhenThen = async (): Promise => { + const guard = new SubpoenaExistsGuard() + const then = {} as Then + + try { + then.result = await guard.canActivate({ + switchToHttp: () => ({ getRequest: mockRequest }), + } as unknown as ExecutionContext) + } catch (error) { + then.error = error as Error + } + + return then + } + }) + + describe('subpoena exists', () => { + const subpoenaId = uuid() + const subpoena = { id: subpoenaId } + const defendantId = uuid() + const defendant = { id: defendantId, subpoenas: [subpoena] } + const request = { params: { subpoenaId }, defendant, subpoena: undefined } + let then: Then + + beforeEach(async () => { + mockRequest.mockReturnValueOnce(request) + + then = await givenWhenThen() + }) + + it('should activate', () => { + expect(then.result).toBe(true) + expect(request.subpoena).toBe(subpoena) + }) + }) + + describe('subpoena does not exist', () => { + const subpoenaId = uuid() + const defendantId = uuid() + const defendant = { id: defendantId, subpoenas: [] } + const request = { params: { subpoenaId }, defendant, subpoena: undefined } + let then: Then + + beforeEach(async () => { + mockRequest.mockReturnValueOnce(request) + + then = await givenWhenThen() + }) + + it('should throw BadRequestException', () => { + expect(then.error).toBeInstanceOf(BadRequestException) + expect(then.error.message).toBe( + `Subpoena ${subpoenaId} of defendant ${defendantId} does not exist`, + ) + }) + }) + + describe('missing defendant', () => { + let then: Then + + beforeEach(async () => { + mockRequest.mockReturnValueOnce({ params: {} }) + + then = await givenWhenThen() + }) + + it('should throw InternalServerErrorException', () => { + expect(then.error).toBeInstanceOf(InternalServerErrorException) + expect(then.error.message).toBe('Missing defendant') + }) + }) + + describe('missing subpoena id', () => { + let then: Then + + beforeEach(async () => { + mockRequest.mockReturnValueOnce({ params: {}, defendant: { id: uuid() } }) + + then = await givenWhenThen() + }) + + it('should throw BadRequestException', () => { + expect(then.error).toBeInstanceOf(BadRequestException) + expect(then.error.message).toBe('Missing subpoena id') + }) + }) +}) diff --git a/apps/judicial-system/backend/src/app/modules/subpoena/internalSubpoena.controller.ts b/apps/judicial-system/backend/src/app/modules/subpoena/internalSubpoena.controller.ts index 968440871a92..87402f7b3ada 100644 --- a/apps/judicial-system/backend/src/app/modules/subpoena/internalSubpoena.controller.ts +++ b/apps/judicial-system/backend/src/app/modules/subpoena/internalSubpoena.controller.ts @@ -1,7 +1,6 @@ import { Body, Controller, - Get, Inject, Param, Patch, @@ -27,6 +26,7 @@ import { DefendantExistsGuard } from '../defendant/guards/defendantExists.guard' import { Defendant } from '../defendant/models/defendant.model' import { DeliverDto } from './dto/deliver.dto' import { UpdateSubpoenaDto } from './dto/updateSubpoena.dto' +import { PoliceSubpoenaExistsGuard } from './guards/policeSubpoenaExists.guard' import { CurrentSubpoena } from './guards/subpoena.decorator' import { SubpoenaExistsGuard } from './guards/subpoenaExists.guard' import { DeliverResponse } from './models/deliver.response' @@ -42,20 +42,9 @@ export class InternalSubpoenaController { @Inject(LOGGER_PROVIDER) private readonly logger: Logger, ) {} - @UseGuards(SubpoenaExistsGuard) - @Get('subpoena/:subpoenaId') - async getSubpoena( - @Param('subpoenaId') subpoenaId: string, - @CurrentSubpoena() subpoena: Subpoena, - ): Promise { - this.logger.debug(`Getting subpoena by subpoena id ${subpoenaId}`) - - return subpoena - } - - @UseGuards(SubpoenaExistsGuard) + @UseGuards(PoliceSubpoenaExistsGuard) @Patch('subpoena/:subpoenaId') - async updateSubpoena( + updateSubpoena( @Param('subpoenaId') subpoenaId: string, @CurrentSubpoena() subpoena: Subpoena, @Body() update: UpdateSubpoenaDto, @@ -80,7 +69,7 @@ export class InternalSubpoenaController { type: DeliverResponse, description: 'Delivers a subpoena to police', }) - async deliverSubpoenaToPolice( + deliverSubpoenaToPolice( @Param('caseId') caseId: string, @Param('defendantId') defendantId: string, @Param('subpoenaId') subpoenaId: string, @@ -93,7 +82,7 @@ export class InternalSubpoenaController { `Delivering subpoena ${subpoenaId} to police for defendant ${defendantId} of case ${caseId}`, ) - return await this.subpoenaService.deliverSubpoenaToPolice( + return this.subpoenaService.deliverSubpoenaToPolice( theCase, defendant, subpoena, diff --git a/apps/judicial-system/backend/src/app/modules/subpoena/limitedAccessSubpoena.controller.ts b/apps/judicial-system/backend/src/app/modules/subpoena/limitedAccessSubpoena.controller.ts index 36e2676df68b..54bfcdacd19a 100644 --- a/apps/judicial-system/backend/src/app/modules/subpoena/limitedAccessSubpoena.controller.ts +++ b/apps/judicial-system/backend/src/app/modules/subpoena/limitedAccessSubpoena.controller.ts @@ -32,12 +32,13 @@ import { } from '../case' import { CurrentDefendant, Defendant, DefendantExistsGuard } from '../defendant' import { CurrentSubpoena } from './guards/subpoena.decorator' -import { SubpoenaExistsOptionalGuard } from './guards/subpoenaExists.guard' +import { SubpoenaExistsGuard } from './guards/subpoenaExists.guard' import { Subpoena } from './models/subpoena.model' @Controller([ 'api/case/:caseId/limitedAccess/defendant/:defendantId/subpoena/:subpoenaId', ]) +@ApiTags('limited access subpoenas') @UseGuards( JwtAuthGuard, CaseExistsGuard, @@ -46,14 +47,13 @@ import { Subpoena } from './models/subpoena.model' CaseReadGuard, DefendantExistsGuard, ) -@ApiTags('limited access subpoenas') export class LimitedAccessSubpoenaController { constructor( private readonly pdfService: PdfService, @Inject(LOGGER_PROVIDER) private readonly logger: Logger, ) {} - @UseGuards(SubpoenaExistsOptionalGuard) + @UseGuards(SubpoenaExistsGuard) @RolesRules(defenderGeneratedPdfRule) @Get() @Header('Content-Type', 'application/pdf') diff --git a/apps/judicial-system/backend/src/app/modules/subpoena/subpoena.controller.ts b/apps/judicial-system/backend/src/app/modules/subpoena/subpoena.controller.ts index da139a356906..8242d7867970 100644 --- a/apps/judicial-system/backend/src/app/modules/subpoena/subpoena.controller.ts +++ b/apps/judicial-system/backend/src/app/modules/subpoena/subpoena.controller.ts @@ -15,11 +15,16 @@ import { ApiOkResponse, ApiTags } from '@nestjs/swagger' import { type Logger, LOGGER_PROVIDER } from '@island.is/logging' import { + CurrentHttpUser, JwtAuthGuard, RolesGuard, RolesRules, } from '@island.is/judicial-system/auth' -import { indictmentCases, SubpoenaType } from '@island.is/judicial-system/types' +import { + indictmentCases, + SubpoenaType, + type User, +} from '@island.is/judicial-system/types' import { districtCourtAssistantRule, @@ -46,7 +51,10 @@ import { SubpoenaExistsOptionalGuard, } from './guards/subpoenaExists.guard' import { Subpoena } from './models/subpoena.model' +import { SubpoenaService } from './subpoena.service' +@Controller('api/case/:caseId/defendant/:defendantId/subpoena') +@ApiTags('subpoenas') @UseGuards( JwtAuthGuard, RolesGuard, @@ -55,14 +63,40 @@ import { Subpoena } from './models/subpoena.model' CaseReadGuard, DefendantExistsGuard, ) -@Controller('api/case/:caseId/defendant/:defendantId/subpoena') -@ApiTags('subpoenas') export class SubpoenaController { constructor( private readonly pdfService: PdfService, + private readonly subpoenaService: SubpoenaService, @Inject(LOGGER_PROVIDER) private readonly logger: Logger, ) {} + @RolesRules( + prosecutorRule, + prosecutorRepresentativeRule, + districtCourtJudgeRule, + districtCourtAssistantRule, + districtCourtRegistrarRule, + ) + @Get(':subpoenaId') + @UseGuards(SubpoenaExistsGuard) + @ApiOkResponse({ + type: Subpoena, + description: 'Gets the subpoena for a given defendant', + }) + getSubpoena( + @Param('caseId') caseId: string, + @Param('defendantId') defendantId: string, + @Param('subpoenaId') subpoenaId: string, + @CurrentSubpoena() subpoena: Subpoena, + @CurrentHttpUser() user: User, + ): Promise { + this.logger.debug( + `Gets subpoena ${subpoenaId} for defendant ${defendantId} of case ${caseId}`, + ) + + return this.subpoenaService.getSubpoena(subpoena, user) + } + @RolesRules( prosecutorRule, prosecutorRepresentativeRule, diff --git a/apps/judicial-system/backend/src/app/modules/subpoena/subpoena.service.ts b/apps/judicial-system/backend/src/app/modules/subpoena/subpoena.service.ts index 5c9bea446004..e2d3116d4aa5 100644 --- a/apps/judicial-system/backend/src/app/modules/subpoena/subpoena.service.ts +++ b/apps/judicial-system/backend/src/app/modules/subpoena/subpoena.service.ts @@ -1,5 +1,5 @@ import { Base64 } from 'js-base64' -import { Includeable } from 'sequelize' +import { Includeable, Sequelize } from 'sequelize' import { Transaction } from 'sequelize/types' import { @@ -7,8 +7,9 @@ import { Inject, Injectable, InternalServerErrorException, + NotFoundException, } from '@nestjs/common' -import { InjectModel } from '@nestjs/sequelize' +import { InjectConnection, InjectModel } from '@nestjs/sequelize' import type { Logger } from '@island.is/logging' import { LOGGER_PROVIDER } from '@island.is/logging' @@ -34,7 +35,7 @@ import { DefendantService } from '../defendant/defendant.service' import { Defendant } from '../defendant/models/defendant.model' import { EventService } from '../event' import { FileService } from '../file' -import { PoliceService } from '../police' +import { PoliceService, SubpoenaInfo } from '../police' import { User } from '../user' import { UpdateSubpoenaDto } from './dto/updateSubpoena.dto' import { DeliverResponse } from './models/deliver.response' @@ -58,9 +59,31 @@ export const include: Includeable[] = [ { model: Defendant, as: 'defendant' }, ] +const subpoenaInfoKeys: Array = [ + 'serviceStatus', + 'comment', + 'servedBy', + 'defenderNationalId', + 'serviceDate', +] + +const isNewValueSetAndDifferent = ( + newValue: unknown, + oldValue: unknown, +): boolean => Boolean(newValue) && newValue !== oldValue + +export const isSubpoenaInfoChanged = ( + newSubpoenaInfo: SubpoenaInfo, + oldSubpoenaInfo: SubpoenaInfo, +) => + subpoenaInfoKeys.some((key) => + isNewValueSetAndDifferent(newSubpoenaInfo[key], oldSubpoenaInfo[key]), + ) + @Injectable() export class SubpoenaService { constructor( + @InjectConnection() private readonly sequelize: Sequelize, @InjectModel(Subpoena) private readonly subpoenaModel: typeof Subpoena, private readonly pdfService: PdfService, private readonly messageService: MessageService, @@ -145,7 +168,6 @@ export class SubpoenaService { async update( subpoena: Subpoena, update: UpdateSubpoenaDto, - transaction?: Transaction, ): Promise { const { defenderChoice, @@ -159,61 +181,63 @@ export class SubpoenaService { requestedDefenderName, } = update - const [numberOfAffectedRows] = await this.subpoenaModel.update(update, { - where: { subpoenaId: subpoena.subpoenaId }, - returning: true, - transaction, - }) + await this.sequelize.transaction(async (transaction) => { + const [numberOfAffectedRows] = await this.subpoenaModel.update(update, { + where: { id: subpoena.id }, + returning: true, + transaction, + }) - if (numberOfAffectedRows > 1) { - // Tolerate failure, but log error - this.logger.error( - `Unexpected number of rows ${numberOfAffectedRows} affected when updating subpoena`, - ) - } + if (numberOfAffectedRows > 1) { + // Tolerate failure, but log error + this.logger.error( + `Unexpected number of rows ${numberOfAffectedRows} affected when updating subpoena`, + ) + } - if ( - subpoena.case && - subpoena.defendant && - (defenderChoice || - defenderNationalId || - defenderName || - defenderEmail || - defenderPhoneNumber || - requestedDefenderChoice || - requestedDefenderNationalId || - requestedDefenderName) - ) { - // If there is a change in the defender choice after the judge has confirmed the choice, - // we need to set the isDefenderChoiceConfirmed to false - const resetDefenderChoiceConfirmed = - subpoena.defendant?.isDefenderChoiceConfirmed && - ((defenderChoice && - subpoena.defendant?.defenderChoice !== defenderChoice) || - (defenderNationalId && - subpoena.defendant?.defenderNationalId !== defenderNationalId)) - - // Færa message handling í defendant service - await this.defendantService.updateRestricted( - subpoena.case, - subpoena.defendant, - { - defenderChoice, - defenderNationalId, - defenderName, - defenderEmail, - defenderPhoneNumber, - requestedDefenderChoice, - requestedDefenderNationalId, - requestedDefenderName, - }, - resetDefenderChoiceConfirmed ? false : undefined, - transaction, - ) - } + if ( + subpoena.case && + subpoena.defendant && + (defenderChoice || + defenderNationalId || + defenderName || + defenderEmail || + defenderPhoneNumber || + requestedDefenderChoice || + requestedDefenderNationalId || + requestedDefenderName) + ) { + // If there is a change in the defender choice after the judge has confirmed the choice, + // we need to set the isDefenderChoiceConfirmed to false + const resetDefenderChoiceConfirmed = + subpoena.defendant?.isDefenderChoiceConfirmed && + ((defenderChoice && + subpoena.defendant?.defenderChoice !== defenderChoice) || + (defenderNationalId && + subpoena.defendant?.defenderNationalId !== defenderNationalId)) + + // Færa message handling í defendant service + await this.defendantService.updateRestricted( + subpoena.case, + subpoena.defendant, + { + defenderChoice, + defenderNationalId, + defenderName, + defenderEmail, + defenderPhoneNumber, + requestedDefenderChoice, + requestedDefenderNationalId, + requestedDefenderName, + }, + resetDefenderChoiceConfirmed ? false : undefined, + transaction, + ) + } + }) // No need to wait for this to finish - this.addMessagesForSubpoenaUpdateToQueue(subpoena, serviceStatus) + await this.addMessagesForSubpoenaUpdateToQueue(subpoena, serviceStatus) if ( update.serviceStatus && @@ -228,21 +252,32 @@ export class SubpoenaService { ) } - return this.findBySubpoenaId(subpoena.subpoenaId) + return this.findById(subpoena.id) } - async findBySubpoenaId(subpoenaId?: string): Promise { - if (!subpoenaId) { - throw new Error('Missing subpoena id') + async findById(subpoenaId: string): Promise { + const subpoena = await this.subpoenaModel.findOne({ + include, + where: { id: subpoenaId }, + }) + + if (!subpoena) { + throw new NotFoundException(`Subpoena ${subpoenaId} does not exist`) } + return subpoena + } + + async findBySubpoenaId(policeSubpoenaId?: string): Promise { const subpoena = await this.subpoenaModel.findOne({ include, - where: { subpoenaId }, + where: { subpoenaId: policeSubpoenaId }, }) if (!subpoena) { - throw new Error(`Subpoena with subpoena id ${subpoenaId} not found`) + throw new NotFoundException( + `Subpoena with police subpoena id ${policeSubpoenaId} does not exist`, + ) } return subpoena @@ -320,4 +355,30 @@ export class SubpoenaService { return { delivered: false } } } + + async getSubpoena(subpoena: Subpoena, user?: TUser): Promise { + if (!subpoena.subpoenaId) { + // The subpoena has not been delivered to the police + return subpoena + } + + if (subpoena.serviceStatus) { + // The subpoena has already been served to the defendant + return subpoena + } + + // We don't know if the subpoena has been served to the defendant + // so we need to check the police service + const subpoenaInfo = await this.policeService.getSubpoenaStatus( + subpoena.subpoenaId, + user, + ) + + if (!isSubpoenaInfoChanged(subpoenaInfo, subpoena)) { + // The subpoena has not changed + return subpoena + } + + return this.update(subpoena, subpoenaInfo) + } } diff --git a/apps/judicial-system/backend/src/app/modules/subpoena/test/internalSubpoenaController/internalSubpoenaControllerGuards.spec.ts b/apps/judicial-system/backend/src/app/modules/subpoena/test/internalSubpoenaController/internalSubpoenaControllerGuards.spec.ts new file mode 100644 index 000000000000..09ed4d41ebe3 --- /dev/null +++ b/apps/judicial-system/backend/src/app/modules/subpoena/test/internalSubpoenaController/internalSubpoenaControllerGuards.spec.ts @@ -0,0 +1,17 @@ +import { TokenGuard } from '@island.is/judicial-system/auth' + +import { InternalSubpoenaController } from '../../internalSubpoena.controller' + +describe('InternalSubpoenaController - guards', () => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let guards: any[] + + beforeEach(() => { + guards = Reflect.getMetadata('__guards__', InternalSubpoenaController) + }) + + it('should have the right guard configuration', () => { + expect(guards).toHaveLength(1) + expect(new guards[0]()).toBeInstanceOf(TokenGuard) + }) +}) diff --git a/apps/judicial-system/backend/src/app/modules/subpoena/test/internalSubpoenaController/updateSubpoeanaGuards.spec.ts b/apps/judicial-system/backend/src/app/modules/subpoena/test/internalSubpoenaController/updateSubpoeanaGuards.spec.ts new file mode 100644 index 000000000000..1cad7bd28cdb --- /dev/null +++ b/apps/judicial-system/backend/src/app/modules/subpoena/test/internalSubpoenaController/updateSubpoeanaGuards.spec.ts @@ -0,0 +1,19 @@ +import { PoliceSubpoenaExistsGuard } from '../../guards/policeSubpoenaExists.guard' +import { InternalSubpoenaController } from '../../internalSubpoena.controller' + +describe('InternalSubpoenaController - Update subpoena guards', () => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let guards: any[] + + beforeEach(() => { + guards = Reflect.getMetadata( + '__guards__', + InternalSubpoenaController.prototype.updateSubpoena, + ) + }) + + it('should have the right guard configuration', () => { + expect(guards).toHaveLength(1) + expect(new guards[0]()).toBeInstanceOf(PoliceSubpoenaExistsGuard) + }) +}) diff --git a/apps/judicial-system/backend/src/app/modules/subpoena/test/subpoenaController/getSubpoenaPdfGuards.spec.ts b/apps/judicial-system/backend/src/app/modules/subpoena/test/subpoenaController/getSubpoenaPdfGuards.spec.ts index 085cc4d55b71..9ed23d7309b2 100644 --- a/apps/judicial-system/backend/src/app/modules/subpoena/test/subpoenaController/getSubpoenaPdfGuards.spec.ts +++ b/apps/judicial-system/backend/src/app/modules/subpoena/test/subpoenaController/getSubpoenaPdfGuards.spec.ts @@ -1,4 +1,4 @@ -import { SubpoenaExistsOptionalGuard } from '../../guards/subpoenaExists.guard' +import { SubpoenaExistsGuard } from '../../guards/subpoenaExists.guard' import { SubpoenaController } from '../../subpoena.controller' describe('SubpoenaController - Get subpoena pdf guards', () => { @@ -14,6 +14,6 @@ describe('SubpoenaController - Get subpoena pdf guards', () => { it('should have the right guard configuration', () => { expect(guards).toHaveLength(1) - expect(new guards[0]()).toBeInstanceOf(SubpoenaExistsOptionalGuard) + expect(new guards[0]()).toBeInstanceOf(SubpoenaExistsGuard) }) }) diff --git a/apps/judicial-system/digital-mailbox-api/src/app/modules/cases/case.controller.ts b/apps/judicial-system/digital-mailbox-api/src/app/modules/cases/case.controller.ts index 3088b8a144f1..6663f8cd5c21 100644 --- a/apps/judicial-system/digital-mailbox-api/src/app/modules/cases/case.controller.ts +++ b/apps/judicial-system/digital-mailbox-api/src/app/modules/cases/case.controller.ts @@ -65,7 +65,7 @@ export class CaseController { }) @CommonApiResponses @ApiLocaleQuery - async getAllCases( + getAllCases( @CurrentUser() user: User, @Query() query?: { locale: string }, ): Promise { @@ -85,7 +85,7 @@ export class CaseController { description: 'Case for given case id and authenticated user not found', }) @ApiLocaleQuery - async getCase( + getCase( @Param('caseId', new ParseUUIDPipe()) caseId: string, @CurrentUser() user: User, @Query() query?: { locale: string }, @@ -106,7 +106,7 @@ export class CaseController { description: 'Subpoena for given case id and authenticated user not found', }) @ApiLocaleQuery - async getSubpoena( + getSubpoena( @Param('caseId', new ParseUUIDPipe()) caseId: string, @CurrentUser() user: User, @Query() query?: { locale: string }, @@ -131,7 +131,7 @@ export class CaseController { description: 'User is not allowed to update subpoena', }) @ApiLocaleQuery - async updateSubpoena( + updateSubpoena( @CurrentUser() user: User, @Param('caseId', new ParseUUIDPipe()) caseId: string, @Body() defenderAssignment: UpdateSubpoenaDto, diff --git a/apps/judicial-system/digital-mailbox-api/src/app/modules/cases/case.service.ts b/apps/judicial-system/digital-mailbox-api/src/app/modules/cases/case.service.ts index 80fe904f3ff2..6f4274fcbe56 100644 --- a/apps/judicial-system/digital-mailbox-api/src/app/modules/cases/case.service.ts +++ b/apps/judicial-system/digital-mailbox-api/src/app/modules/cases/case.service.ts @@ -11,7 +11,6 @@ import { AuditedAction, AuditTrailService, } from '@island.is/judicial-system/audit-trail' -import { normalizeAndFormatNationalId } from '@island.is/judicial-system/formatters' import { LawyersService } from '@island.is/judicial-system/lawyers' import { DefenderChoice } from '@island.is/judicial-system/types' @@ -43,15 +42,15 @@ export class CaseService { } async getCase( - id: string, + caseId: string, nationalId: string, lang?: string, ): Promise { return this.auditTrailService.audit( 'digital-mailbox-api', AuditedAction.GET_INDICTMENT, - this.getCaseInfo(id, nationalId, lang), - () => id, + this.getCaseInfo(caseId, nationalId, lang), + () => caseId, ) } @@ -92,27 +91,11 @@ export class CaseService { } private async getCaseInfo( - id: string, + caseId: string, nationalId: string, lang?: string, ): Promise { - const response = await this.fetchCase(id, nationalId) - const defendantInfo = response.defendants.find( - (defendant) => - defendant.nationalId && - normalizeAndFormatNationalId(nationalId).includes(defendant.nationalId), - ) - - if (!defendantInfo) { - throw new NotFoundException('Defendant not found') - } - - if ( - defendantInfo.subpoenas?.[0]?.subpoenaId && - !defendantInfo.subpoenas[0].serviceStatus - ) { - await this.fetchServiceStatus(id, defendantInfo.subpoenas[0].subpoenaId) - } + const response = await this.fetchCase(caseId, nationalId) return CaseResponse.fromInternalCaseResponse(response, lang) } @@ -168,6 +151,7 @@ export class CaseService { requestedDefenderName: chosenLawyer?.Name, } await this.patchDefenseInfo(defendantNationalId, caseId, defenderChoice) + const updatedCase = await this.fetchCase(caseId, defendantNationalId) return SubpoenaResponse.fromInternalCaseResponse( @@ -182,9 +166,9 @@ export class CaseService { ): Promise { try { const res = await fetch( - `${this.config.backendUrl}/api/internal/cases/indictments`, + `${this.config.backendUrl}/api/internal/cases/indictments/defendant/${nationalId}`, { - method: 'POST', + method: 'GET', headers: { 'Content-Type': 'application/json', authorization: `Bearer ${this.config.secretToken}`, @@ -206,58 +190,12 @@ export class CaseService { } private async fetchCase( - id: string, - nationalId: string, - ): Promise { - try { - const res = await fetch( - `${this.config.backendUrl}/api/internal/case/indictment/${id}`, - { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - authorization: `Bearer ${this.config.secretToken}`, - }, - body: JSON.stringify({ nationalId }), - }, - ) - - if (!res.ok) { - if (res.status === 404) { - throw new NotFoundException(`Case ${id} not found`) - } - - const reason = await res.text() - - throw new BadGatewayException( - reason || 'Unexpected error occurred while fetching case by ID', - ) - } - - const caseData = await res.json() - - return caseData - } catch (reason) { - if ( - reason instanceof BadGatewayException || - reason instanceof NotFoundException - ) { - throw reason - } - - throw new BadGatewayException( - `Failed to fetch case by id: ${reason.message}`, - ) - } - } - - private async fetchServiceStatus( caseId: string, - subpoenaId: string, + nationalId: string, ): Promise { try { const res = await fetch( - `${this.config.backendUrl}/api/internal/case/${caseId}/subpoenaStatus/${subpoenaId}`, + `${this.config.backendUrl}/api/internal/case/indictment/${caseId}/defendant/${nationalId}`, { method: 'GET', headers: { @@ -275,8 +213,7 @@ export class CaseService { const reason = await res.text() throw new BadGatewayException( - reason || - 'Unexpected error occurred while fetching serviceStatus by subpoenaID', + reason || 'Unexpected error occurred while fetching case by ID', ) } @@ -292,7 +229,7 @@ export class CaseService { } throw new BadGatewayException( - `Failed to fetch serviceStatus by subpoenaId: ${reason.message}`, + `Failed to fetch case by id: ${reason.message}`, ) } } diff --git a/apps/judicial-system/digital-mailbox-api/src/app/modules/defenders/defender.controller.ts b/apps/judicial-system/digital-mailbox-api/src/app/modules/defenders/defender.controller.ts index 24162164dc56..3e9b45a2b3ac 100644 --- a/apps/judicial-system/digital-mailbox-api/src/app/modules/defenders/defender.controller.ts +++ b/apps/judicial-system/digital-mailbox-api/src/app/modules/defenders/defender.controller.ts @@ -1,9 +1,10 @@ import { CacheInterceptor } from '@nestjs/cache-manager' import { + BadGatewayException, Controller, Get, Inject, - InternalServerErrorException, + NotFoundException, Param, UseInterceptors, } from '@nestjs/common' @@ -29,7 +30,7 @@ export class DefenderController { type: [Defender], description: 'Returns a list of defenders', }) - @ApiResponse({ status: 500, description: 'Failed to retrieve defenders' }) + @ApiResponse({ status: 502, description: 'Failed to retrieve defenders' }) async getLawyers(): Promise { try { this.logger.debug('Retrieving litigators from lawyer registry') @@ -45,7 +46,7 @@ export class DefenderController { })) } catch (error) { this.logger.error('Failed to retrieve lawyers', error) - throw new InternalServerErrorException('Failed to retrieve lawyers') + throw new BadGatewayException('Failed to retrieve lawyers') } } @@ -54,6 +55,8 @@ export class DefenderController { type: Defender, description: 'Retrieves a defender by national id', }) + @ApiResponse({ status: 404, description: 'Defender not found' }) + @ApiResponse({ status: 502, description: 'Failed to retrieve defender' }) async getLawyer(@Param('nationalId') nationalId: string): Promise { try { this.logger.debug(`Retrieving lawyer by national id ${nationalId}`) @@ -65,8 +68,12 @@ export class DefenderController { practice: lawyer.Practice, } } catch (error) { + if (error instanceof NotFoundException) { + throw error + } + this.logger.error('Failed to retrieve lawyer', error) - throw new InternalServerErrorException('Failed to retrieve lawyer') + throw new BadGatewayException('Failed to retrieve lawyer') } } } diff --git a/apps/judicial-system/web/src/components/FormProvider/case.graphql b/apps/judicial-system/web/src/components/FormProvider/case.graphql index f2a5e28a0aa0..341c18c355fc 100644 --- a/apps/judicial-system/web/src/components/FormProvider/case.graphql +++ b/apps/judicial-system/web/src/components/FormProvider/case.graphql @@ -44,6 +44,7 @@ query Case($input: CaseQueryInput!) { comment defenderNationalId caseId + defendantId subpoenaId } } diff --git a/apps/judicial-system/web/src/components/FormProvider/limitedAccessCase.graphql b/apps/judicial-system/web/src/components/FormProvider/limitedAccessCase.graphql index df8b33ad5c86..b61738d433a9 100644 --- a/apps/judicial-system/web/src/components/FormProvider/limitedAccessCase.graphql +++ b/apps/judicial-system/web/src/components/FormProvider/limitedAccessCase.graphql @@ -56,6 +56,7 @@ query LimitedAccessCase($input: CaseQueryInput!) { comment defenderNationalId caseId + defendantId subpoenaId } } diff --git a/apps/judicial-system/web/src/components/ServiceAnnouncement/ServiceAnnouncement.tsx b/apps/judicial-system/web/src/components/ServiceAnnouncement/ServiceAnnouncement.tsx index 2c2b09df085b..8dac19a8c448 100644 --- a/apps/judicial-system/web/src/components/ServiceAnnouncement/ServiceAnnouncement.tsx +++ b/apps/judicial-system/web/src/components/ServiceAnnouncement/ServiceAnnouncement.tsx @@ -94,10 +94,7 @@ interface ServiceAnnouncementProps { const ServiceAnnouncement: FC = (props) => { const { subpoena: localSubpoena, defendantName } = props - const [subpoena, setSubpoena] = useState() - - const { subpoenaStatus, subpoenaStatusLoading, subpoenaStatusError } = - useSubpoena(localSubpoena.caseId, localSubpoena.subpoenaId) + const { subpoena, subpoenaLoading } = useSubpoena(localSubpoena) const { formatMessage } = useIntl() @@ -111,38 +108,22 @@ const ServiceAnnouncement: FC = (props) => { ? mapServiceStatusMessages(subpoena, formatMessage, lawyer) : [] - // Use data from RLS but fallback to local data - useEffect(() => { - if (subpoenaStatusError || localSubpoena.serviceStatus) { - setSubpoena(localSubpoena) - } else { - setSubpoena({ - ...localSubpoena, - servedBy: subpoenaStatus?.subpoenaStatus?.servedBy, - serviceStatus: subpoenaStatus?.subpoenaStatus?.serviceStatus, - serviceDate: subpoenaStatus?.subpoenaStatus?.serviceDate, - comment: subpoenaStatus?.subpoenaStatus?.comment, - defenderNationalId: subpoenaStatus?.subpoenaStatus?.defenderNationalId, - }) - } - }, [localSubpoena, subpoenaStatus, subpoenaStatusError]) - - return !subpoena && !subpoenaStatusLoading ? ( - {renderError(formatMessage)} - ) : subpoenaStatusLoading && !localSubpoena.serviceStatus ? ( + return subpoenaLoading ? ( + ) : !subpoena ? ( + {renderError(formatMessage)} ) : ( {messages.map((msg) => ( - + {msg} ))} diff --git a/apps/judicial-system/web/src/routes/Court/Indictments/Overview/Overview.tsx b/apps/judicial-system/web/src/routes/Court/Indictments/Overview/Overview.tsx index f22e56f7832d..d8528fc3bef7 100644 --- a/apps/judicial-system/web/src/routes/Court/Indictments/Overview/Overview.tsx +++ b/apps/judicial-system/web/src/routes/Court/Indictments/Overview/Overview.tsx @@ -79,16 +79,13 @@ const IndictmentOverview = () => { {formatMessage(strings.inProgressTitle)} {workingCase.defendants?.map((defendant) => - defendant.subpoenas?.map( - (subpoena) => - subpoena.subpoenaId && ( - - ), - ), + defendant.subpoenas?.map((subpoena) => ( + + )), )} {workingCase.court && latestDate?.date && diff --git a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Overview/Overview.tsx b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Overview/Overview.tsx index 3f1c8110e6b9..2640b512c74d 100644 --- a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Overview/Overview.tsx +++ b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Overview/Overview.tsx @@ -191,16 +191,13 @@ const Overview: FC = () => { {formatMessage(strings.heading)} {workingCase.defendants?.map((defendant) => - defendant.subpoenas?.map( - (subpoena) => - subpoena.subpoenaId && ( - - ), - ), + defendant.subpoenas?.map((subpoena) => ( + + )), )} {workingCase.court && latestDate?.date && diff --git a/apps/judicial-system/web/src/utils/hooks/useSubpoena/getSubpoenaStatus.graphql b/apps/judicial-system/web/src/utils/hooks/useSubpoena/getSubpoenaStatus.graphql deleted file mode 100644 index a87df3e15366..000000000000 --- a/apps/judicial-system/web/src/utils/hooks/useSubpoena/getSubpoenaStatus.graphql +++ /dev/null @@ -1,9 +0,0 @@ -query SubpoenaStatus($input: SubpoenaStatusQueryInput!) { - subpoenaStatus(input: $input) { - serviceStatus - servedBy - comment - serviceDate - defenderNationalId - } -} diff --git a/apps/judicial-system/web/src/utils/hooks/useSubpoena/index.ts b/apps/judicial-system/web/src/utils/hooks/useSubpoena/index.ts index 3d2ab133f847..de5fc94af08d 100644 --- a/apps/judicial-system/web/src/utils/hooks/useSubpoena/index.ts +++ b/apps/judicial-system/web/src/utils/hooks/useSubpoena/index.ts @@ -1,20 +1,29 @@ -import { useSubpoenaStatusQuery } from './getSubpoenaStatus.generated' +import { Subpoena } from '@island.is/judicial-system-web/src/graphql/schema' -const useSubpoena = (caseId?: string | null, subpoenaId?: string | null) => { - const { - data: subpoenaStatus, - loading: subpoenaStatusLoading, - error: subpoenaStatusError, - } = useSubpoenaStatusQuery({ +import { useSubpoenaQuery } from './subpoena.generated' + +const useSubpoena = (subpoena: Subpoena) => { + // Skip if the subpoena has not been sent to the police + // or if the subpoena already has a service status + const skip = !subpoena.subpoenaId || Boolean(subpoena.serviceStatus) + + const { data, loading, error } = useSubpoenaQuery({ + skip, variables: { input: { - caseId: caseId ?? '', - subpoenaId: subpoenaId ?? '', + caseId: subpoena?.caseId ?? '', + defendantId: subpoena?.defendantId ?? '', + subpoenaId: subpoena?.id ?? '', }, }, + fetchPolicy: 'no-cache', + errorPolicy: 'all', }) - return { subpoenaStatus, subpoenaStatusLoading, subpoenaStatusError } + return { + subpoena: skip || error ? subpoena : data?.subpoena, + subpoenaLoading: skip ? false : loading, + } } export default useSubpoena diff --git a/apps/judicial-system/web/src/utils/hooks/useSubpoena/subpoena.graphql b/apps/judicial-system/web/src/utils/hooks/useSubpoena/subpoena.graphql new file mode 100644 index 000000000000..745293ed61eb --- /dev/null +++ b/apps/judicial-system/web/src/utils/hooks/useSubpoena/subpoena.graphql @@ -0,0 +1,14 @@ +query Subpoena($input: SubpoenaQueryInput!) { + subpoena(input: $input) { + id + created + serviceStatus + serviceDate + servedBy + comment + defenderNationalId + caseId + defendantId + subpoenaId + } +} diff --git a/apps/judicial-system/xrd-api/src/app/app.controller.ts b/apps/judicial-system/xrd-api/src/app/app.controller.ts index 5e49256d0d48..639611f2242f 100644 --- a/apps/judicial-system/xrd-api/src/app/app.controller.ts +++ b/apps/judicial-system/xrd-api/src/app/app.controller.ts @@ -1,8 +1,8 @@ import { + BadGatewayException, Body, Controller, Inject, - InternalServerErrorException, Param, ParseUUIDPipe, Patch, @@ -10,7 +10,7 @@ import { UseInterceptors, } from '@nestjs/common' import { Get } from '@nestjs/common' -import { ApiCreatedResponse, ApiResponse } from '@nestjs/swagger' +import { ApiCreatedResponse, ApiOkResponse, ApiResponse } from '@nestjs/swagger' import type { Logger } from '@island.is/logging' import { LOGGER_PROVIDER } from '@island.is/logging' @@ -45,7 +45,11 @@ export class AppController { } @Get('defenders') - @ApiResponse({ status: 500, description: 'Failed to retrieve defenders' }) + @ApiOkResponse({ + type: [Defender], + description: 'Returns a list of defenders', + }) + @ApiResponse({ status: 502, description: 'Failed to retrieve defenders' }) async getLawyers(): Promise { try { this.logger.debug('Retrieving litigators from lawyer registry') @@ -61,12 +65,13 @@ export class AppController { })) } catch (error) { this.logger.error('Failed to retrieve lawyers', error) - throw new InternalServerErrorException('Failed to retrieve lawyers') + throw new BadGatewayException('Failed to retrieve lawyers') } } @Patch('subpoena/:subpoenaId') - @ApiResponse({ status: 500, description: 'Failed to update subpoena' }) + @ApiResponse({ status: 400, description: 'Invalid input' }) + @ApiResponse({ status: 502, description: 'Failed to update subpoena' }) async updateSubpoena( @Param('subpoenaId', new ParseUUIDPipe()) subpoenaId: string, @Body() updateSubpoena: UpdateSubpoenaDto, diff --git a/apps/judicial-system/xrd-api/src/app/app.service.ts b/apps/judicial-system/xrd-api/src/app/app.service.ts index 57648e9254f7..2cdd54556f9b 100644 --- a/apps/judicial-system/xrd-api/src/app/app.service.ts +++ b/apps/judicial-system/xrd-api/src/app/app.service.ts @@ -132,6 +132,7 @@ export class AppService { `Failed to retrieve lawyer with national id ${updateSubpoena.defenderNationalId}`, reason, ) + throw new BadRequestException('Lawyer not found') } } diff --git a/libs/judicial-system/audit-trail/src/lib/auditTrail.service.ts b/libs/judicial-system/audit-trail/src/lib/auditTrail.service.ts index d20af4e3c6c7..70b82ad7d3ef 100644 --- a/libs/judicial-system/audit-trail/src/lib/auditTrail.service.ts +++ b/libs/judicial-system/audit-trail/src/lib/auditTrail.service.ts @@ -41,7 +41,6 @@ export enum AuditedAction { GET_SERVICE_CERTIFICATE_PDF = 'GET_SERVICE_CERTIFICATE', GET_ALL_FILES_ZIP = 'GET_ALL_FILES_ZIP', GET_INSTITUTIONS = 'GET_INSTITUTIONS', - GET_SUBPOENA_STATUS = 'GET_SUBPOENA_STATUS', CREATE_PRESIGNED_POST = 'CREATE_PRESIGNED_POST', CREATE_FILE = 'CREATE_FILE', UPDATE_FILES = 'UPDATE_FILES', diff --git a/libs/judicial-system/lawyers/src/lib/lawyers.service.ts b/libs/judicial-system/lawyers/src/lib/lawyers.service.ts index 4c89ce69bb67..251de7a67721 100644 --- a/libs/judicial-system/lawyers/src/lib/lawyers.service.ts +++ b/libs/judicial-system/lawyers/src/lib/lawyers.service.ts @@ -1,4 +1,4 @@ -import { Inject, Injectable } from '@nestjs/common' +import { Inject, Injectable, NotFoundException } from '@nestjs/common' import type { Logger } from '@island.is/logging' import { LOGGER_PROVIDER } from '@island.is/logging' @@ -31,7 +31,7 @@ export class LawyersService { ) if (response.ok) { - return await response.json() + return response.json() } const reason = await response.text() @@ -51,11 +51,16 @@ export class LawyersService { ) if (response.ok) { - return await response.json() + return response.json() } const reason = await response.text() this.logger.info('Failed to get lawyer from lawyer registry:', reason) + + if (response.status === 404) { + throw new NotFoundException('Lawyer not found') + } + throw new Error(reason) } } From 14b2142ca50081758a68fcd7a3c95a546b530ba2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9Eorkell=20M=C3=A1ni=20=C3=9Eorkelsson?= Date: Wed, 4 Dec 2024 09:45:29 +0000 Subject: [PATCH 12/14] chore(web): switch line and text around (#17120) Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../web/screens/Grants/Grant/GrantSidebar.tsx | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/apps/web/screens/Grants/Grant/GrantSidebar.tsx b/apps/web/screens/Grants/Grant/GrantSidebar.tsx index e923ccbb7395..c785d595a2b1 100644 --- a/apps/web/screens/Grants/Grant/GrantSidebar.tsx +++ b/apps/web/screens/Grants/Grant/GrantSidebar.tsx @@ -40,19 +40,19 @@ export const GrantSidebar = ({ grant, locale }: Props) => { generateLine( formatMessage(m.single.fund), grant?.fund?.link?.slug ? ( - - + + {grant.fund.title} - - + + ) : undefined, ), generateLine( From 4dec76c3b462ab2e78191c380276c081c06c4a8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9Eorkell=20M=C3=A1ni=20=C3=9Eorkelsson?= Date: Wed, 4 Dec 2024 10:33:06 +0000 Subject: [PATCH 13/14] fix(web): order filter (#17121) * chore: switch line and text around * fix: init --- .../SearchResults/SearchResultsFilter.tsx | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/apps/web/screens/Grants/SearchResults/SearchResultsFilter.tsx b/apps/web/screens/Grants/SearchResults/SearchResultsFilter.tsx index e8045f00d55b..058fa195b758 100644 --- a/apps/web/screens/Grants/SearchResults/SearchResultsFilter.tsx +++ b/apps/web/screens/Grants/SearchResults/SearchResultsFilter.tsx @@ -1,4 +1,5 @@ import { useIntl } from 'react-intl' +import sortBy from 'lodash/sortBy' import { Box, @@ -30,13 +31,17 @@ export const GrantsSearchResultsFilter = ({ variant = 'default', }: Props) => { const { formatMessage } = useIntl() - const categoryFilters = tags?.filter( - (t) => t.genericTagGroup?.slug === 'grant-category', - ) - const typeFilters = tags?.filter( - (t) => t.genericTagGroup?.slug === 'grant-type', - ) + const sortedFilters = { + categories: sortBy( + tags?.filter((t) => t.genericTagGroup?.slug === 'grant-category'), + 'title', + ), + types: sortBy( + tags?.filter((t) => t.genericTagGroup?.slug === 'grant-type'), + 'title', + ), + } return ( ({ + filters: sortedFilters.categories.map((t) => ({ value: t.slug, label: t.title, })), } : undefined, - typeFilters + sortedFilters.types ? { id: 'type', label: formatMessage(m.search.type), selected: searchState?.['type'] ?? [], - filters: typeFilters.map((t) => ({ + filters: sortedFilters.types.map((t) => ({ value: t.slug, label: t.title, })), From 4a229a48d71bac8032a268e5d072982bbe07cef4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A6ring=20Gunnar=20Stein=C3=BE=C3=B3rsson?= Date: Wed, 4 Dec 2024 11:27:10 +0000 Subject: [PATCH 14/14] feat(cms): Be able to query organization by national id (#17122) * Be able to query organization by national id * Refactor * Be able to conditionally require field value to match code before refactor --------- Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- libs/cms/src/lib/cms.contentful.service.ts | 54 +++++++++------------- 1 file changed, 23 insertions(+), 31 deletions(-) diff --git a/libs/cms/src/lib/cms.contentful.service.ts b/libs/cms/src/lib/cms.contentful.service.ts index f1e40230d6d8..15ba9425ed37 100644 --- a/libs/cms/src/lib/cms.contentful.service.ts +++ b/libs/cms/src/lib/cms.contentful.service.ts @@ -348,18 +348,20 @@ export class CmsContentfulService { ) } - async getOrganization( - slug: string, + private async getOrganizationBy( + fieldName: string, + fieldValue: string, lang: string, + requireFieldValue = false, ): Promise { - if (!slug) { + if (requireFieldValue && !fieldValue) { return null } const params = { ['content_type']: 'organization', include: 10, - 'fields.slug': slug, + [`fields.${fieldName}`]: fieldValue, limit: 1, } @@ -372,42 +374,32 @@ export class CmsContentfulService { ) } + async getOrganization( + slug: string, + lang: string, + ): Promise { + return this.getOrganizationBy('slug', slug, lang, true) + } + async getOrganizationByTitle( title: string, lang: string, - ): Promise { - const params = { - ['content_type']: 'organization', - include: 10, - 'fields.title[match]': title, - } - - const result = await this.contentfulRepository - .getLocalizedEntries(lang, params) - .catch(errorHandler('getOrganization')) - - return ( - (result.items as types.IOrganization[]).map(mapOrganization)[0] ?? null - ) + ): Promise { + return this.getOrganizationBy('title[match]', title, lang) } async getOrganizationByReferenceId( referenceId: string, lang: string, - ): Promise { - const params = { - ['content_type']: 'organization', - include: 10, - 'fields.referenceIdentifier': referenceId, - } - - const result = await this.contentfulRepository - .getLocalizedEntries(lang, params) - .catch(errorHandler('getOrganization')) + ): Promise { + return this.getOrganizationBy('referenceIdentifier', referenceId, lang) + } - return ( - (result.items as types.IOrganization[]).map(mapOrganization)[0] ?? null - ) + async getOrganizationByNationalId( + nationalId: string, + lang: string, + ): Promise { + return this.getOrganizationBy('kennitala', nationalId, lang, true) } async getOrganizationPage(