diff --git a/.gitignore b/.gitignore index 9ad6dbd3146e..46c71ffd1796 100644 --- a/.gitignore +++ b/.gitignore @@ -102,4 +102,70 @@ apps/**/index.html # E2E outputs test-results/ playwright-report/ -tmp-sessions/ \ No newline at end of file +tmp-sessions/ + +# React Native + +# Xcode +# +build/ +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +xcuserdata +*.xccheckout +*.moved-aside +DerivedData +*.hmap +*.ipa +*.dSYM.zip +*.xcuserstate +**/.xcode.env.local +ios/*.cer +ios/*.certSigningRequest +ios/*.mobileprovision +ios/*.p12 + +# Android/IntelliJ +# +build/ +.idea +.gradle +local.properties +*.iml +*.hprof +.cxx/ +*.keystore +!debug.keystore +google-services.json +service-account.json + +# fastlane +# +# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the +# screenshots whenever they are needed. +# For more information about the recommended setup visit: +# https://docs.fastlane.tools/best-practices/source-control/ + +**/fastlane/report.xml +**/fastlane/Preview.html +**/fastlane/screenshots +**/fastlane/test_output + +# Bundle artifact +*.jsbundle + +# Ruby / CocoaPods +**/Pods/ +**/vendor/bundle/ + +# Temporary files created by Metro to check the health of the file watcher +.metro-health-check* + +# testing +/coverage diff --git a/.yarn/patches/react-native-npm-0.71.1-f5d237f240.patch b/.yarn/patches/react-native-npm-0.71.1-f5d237f240.patch deleted file mode 100644 index 0c49e9c60fe6..000000000000 Binary files a/.yarn/patches/react-native-npm-0.71.1-f5d237f240.patch and /dev/null differ diff --git a/apps/air-discount-scheme/backend/src/app/modules/discount/discount.service.ts b/apps/air-discount-scheme/backend/src/app/modules/discount/discount.service.ts index 993a82013473..9ccc135e8265 100644 --- a/apps/air-discount-scheme/backend/src/app/modules/discount/discount.service.ts +++ b/apps/air-discount-scheme/backend/src/app/modules/discount/discount.service.ts @@ -17,6 +17,8 @@ import { UserService } from '../user/user.service' import type { User as AuthUser } from '@island.is/auth-nest-tools' import { ExplicitFlight } from './dto/ExplicitFlight.dto' import { CreateSuperExplicitDiscountCodeParams } from './dto' +import type { Logger } from '@island.is/logging' +import { LOGGER_PROVIDER } from '@island.is/logging' interface CachedDiscount { user: User @@ -47,6 +49,8 @@ export class DiscountService { @InjectModel(ExplicitCode) private explicitModel: typeof ExplicitCode, + @Inject(LOGGER_PROVIDER) + private readonly logger: Logger, private readonly userService: UserService, ) {} @@ -197,13 +201,19 @@ export class DiscountService { unConnectedFlights: Flight[] | ExplicitFlight[], isExplicit: boolean, flightLegs = 1, + isManual?: boolean, ): Promise<Array<Discount> | null> { const user = await this.userService.getUserInfoByNationalId( nationalId, auth, + isExplicit, + isManual, ) if (!user) { + this.logger.warn('User by national id not found for explicit discount.', { + category: 'ads-backend', + }) return null } // overwrite credit since validation may return 0 depending on what the problem is @@ -212,10 +222,19 @@ export class DiscountService { user.fund.credit = 2 //making sure we can get flight from and to user.fund.total = 2 } else { + this.logger.warn( + `User fund used requirements not met: ${user.fund.used}.`, + { + category: 'ads-backend', + }, + ) return null } } if (user.fund.credit === 0 && user.fund.total !== undefined) { + this.logger.warn(`User fund no credit, has total: ${user.fund.total}.`, { + category: 'ads-backend', + }) return null } @@ -477,6 +496,7 @@ export class DiscountService { ], } + const isManual = true const discount = await this.createExplicitDiscountCode( auth, body.nationalId, @@ -487,6 +507,7 @@ export class DiscountService { body.needsConnectionFlight ? [flight] : [], isExplicit, 1, + isManual, ) if (!discount) { throw new Error(`Could not create explicit discount`) diff --git a/apps/air-discount-scheme/backend/src/app/modules/discount/test/unit/discount.service.spec.ts b/apps/air-discount-scheme/backend/src/app/modules/discount/test/unit/discount.service.spec.ts index a5b95b9ca4b4..46c9da2b5f8f 100644 --- a/apps/air-discount-scheme/backend/src/app/modules/discount/test/unit/discount.service.spec.ts +++ b/apps/air-discount-scheme/backend/src/app/modules/discount/test/unit/discount.service.spec.ts @@ -64,7 +64,9 @@ describe('DiscountService', () => { { provide: LOGGER_PROVIDER, useClass: jest.fn(() => ({ - error: () => ({}), + error: jest.fn(), + info: jest.fn(), + warn: jest.fn(), })), }, { diff --git a/apps/air-discount-scheme/backend/src/app/modules/user/user.service.ts b/apps/air-discount-scheme/backend/src/app/modules/user/user.service.ts index 0761e5880450..034f80c06872 100644 --- a/apps/air-discount-scheme/backend/src/app/modules/user/user.service.ts +++ b/apps/air-discount-scheme/backend/src/app/modules/user/user.service.ts @@ -17,6 +17,12 @@ const ONE_WEEK = 604800 // seconds const CACHE_KEY = 'userService' const MAX_AGE_LIMIT = 18 +const DEFAULT_FUND: Fund = { + credit: 2, + total: 2, + used: 0, +} + interface CustodianCache { custodians: Array<NationalRegistryUser | null> } @@ -42,6 +48,7 @@ export class UserService { private async getFund( user: NationalRegistryUser, auth?: AuthUser, + isManual?: boolean, ): Promise<Fund> { const { used, unused, total } = await this.flightService.countThisYearsFlightLegsByNationalId( @@ -49,7 +56,7 @@ export class UserService { ) let meetsADSRequirements = false - if (this.flightService.isADSPostalCode(user.postalcode)) { + if (this.flightService.isADSPostalCode(user.postalcode) || isManual) { meetsADSRequirements = true } else if (info(user.nationalId).age < MAX_AGE_LIMIT) { // NationalId is a minor and doesn't live in ADS postal codes. @@ -95,20 +102,33 @@ export class UserService { nationalId: string, model: new (user: NationalRegistryUser, fund: Fund) => T, auth: AuthUser, + isExplicit?: boolean, + isManual?: boolean, ): Promise<T | null> { const user = await this.nationalRegistryService.getUser(nationalId, auth) if (!user) { return null } - const fund = await this.getFund(user, auth) + if (isExplicit) { + return new model(user, DEFAULT_FUND) + } + const fund = await this.getFund(user, auth, isManual) return new model(user, fund) } async getUserInfoByNationalId( nationalId: string, auth: AuthUser, + isExplicit?: boolean, + isManual?: boolean, ): Promise<User | null> { - return this.getUserByNationalId<User>(nationalId, User, auth) + return this.getUserByNationalId<User>( + nationalId, + User, + auth, + isExplicit, + isManual, + ) } async getMultipleUsersByNationalIdArray( diff --git a/apps/application-system/api/src/app/modules/application/lifecycle/application-lifecycle.service.ts b/apps/application-system/api/src/app/modules/application/lifecycle/application-lifecycle.service.ts index 332c163b2555..ab2dbab36e7d 100644 --- a/apps/application-system/api/src/app/modules/application/lifecycle/application-lifecycle.service.ts +++ b/apps/application-system/api/src/app/modules/application/lifecycle/application-lifecycle.service.ts @@ -151,39 +151,52 @@ export class ApplicationLifeCycleService { private async preparePrunedNotification( application: PruningApplication, ): Promise<CreateHnippNotificationDto | null> { - const template = await getApplicationTemplateByTypeId(application.typeId) - const stateConfig = template.stateMachineConfig.states[application.state] - const lifeCycle = stateConfig.meta?.lifecycle - if (lifeCycle && lifeCycle.shouldBePruned && lifeCycle.pruneMessage) { - try { - const pruneMessage = - typeof lifeCycle.pruneMessage === 'function' - ? lifeCycle.pruneMessage(application as ApplicationWithAttachments) - : lifeCycle.pruneMessage - const notification = { - recipient: application.applicant, - templateId: pruneMessage.notificationTemplateId, - args: [ - { - key: 'externalBody', - value: pruneMessage.externalBody || '', - }, - { - key: 'internalBody', - value: pruneMessage.internalBody || '', - }, - ], - } - return notification - } catch (error) { - this.logger.error( - `Failed to prepare pruning notification for application ${application.id}`, - error, - ) + try { + const template = await getApplicationTemplateByTypeId(application.typeId) + if (!template) { return null } + const stateConfig = template.stateMachineConfig.states[application.state] + const lifeCycle = stateConfig.meta?.lifecycle + if (lifeCycle && lifeCycle.shouldBePruned && lifeCycle.pruneMessage) { + try { + const pruneMessage = + typeof lifeCycle.pruneMessage === 'function' + ? lifeCycle.pruneMessage( + application as ApplicationWithAttachments, + ) + : lifeCycle.pruneMessage + const notification = { + recipient: application.applicant, + templateId: pruneMessage.notificationTemplateId, + args: [ + { + key: 'externalBody', + value: pruneMessage.externalBody || '', + }, + { + key: 'internalBody', + value: pruneMessage.internalBody || '', + }, + ], + } + return notification + } catch (error) { + this.logger.error( + `Failed to prepare pruning notification for application ${application.id}`, + error, + ) + return null + } + } + return null + } catch (error) { + this.logger.error( + `Failed to get application template for application typeId ${application.typeId}`, + error, + ) + return null } - return null } private async sendPrunedNotification( diff --git a/apps/judicial-system/api/infra/judicial-system-api.ts b/apps/judicial-system/api/infra/judicial-system-api.ts index efe736fa005b..8f5cabe11e2c 100644 --- a/apps/judicial-system/api/infra/judicial-system-api.ts +++ b/apps/judicial-system/api/infra/judicial-system-api.ts @@ -47,8 +47,8 @@ export const serviceSetup = (services: { }, HIDDEN_FEATURES: { dev: '', - staging: '', - prod: '', + staging: 'MULTIPLE_INDICTMENT_SUBTYPES', + prod: 'MULTIPLE_INDICTMENT_SUBTYPES', }, }) .secrets({ diff --git a/apps/judicial-system/api/src/app/modules/indictment-count/dto/updateIndictmentCount.input.ts b/apps/judicial-system/api/src/app/modules/indictment-count/dto/updateIndictmentCount.input.ts index d4d05cedb578..7e26e223a6b6 100644 --- a/apps/judicial-system/api/src/app/modules/indictment-count/dto/updateIndictmentCount.input.ts +++ b/apps/judicial-system/api/src/app/modules/indictment-count/dto/updateIndictmentCount.input.ts @@ -4,7 +4,10 @@ import { GraphQLJSONObject } from 'graphql-type-json' import { Field, ID, InputType } from '@nestjs/graphql' import type { SubstanceMap } from '@island.is/judicial-system/types' -import { IndictmentCountOffense } from '@island.is/judicial-system/types' +import { + IndictmentCountOffense, + IndictmentSubtype, +} from '@island.is/judicial-system/types' @InputType() export class UpdateIndictmentCountInput { @@ -53,4 +56,11 @@ export class UpdateIndictmentCountInput { @IsOptional() @Field(() => String, { nullable: true }) readonly legalArguments?: string + + @Allow() + @IsOptional() + @IsArray() + @IsEnum(IndictmentSubtype, { each: true }) + @Field(() => [IndictmentSubtype], { nullable: true }) + readonly indictmentCountSubtypes?: IndictmentSubtype[] } diff --git a/apps/judicial-system/api/src/app/modules/indictment-count/models/indictmentCount.model.ts b/apps/judicial-system/api/src/app/modules/indictment-count/models/indictmentCount.model.ts index 3ea6a132945c..f3ef5aa954c6 100644 --- a/apps/judicial-system/api/src/app/modules/indictment-count/models/indictmentCount.model.ts +++ b/apps/judicial-system/api/src/app/modules/indictment-count/models/indictmentCount.model.ts @@ -3,9 +3,13 @@ import { GraphQLJSONObject } from 'graphql-type-json' import { Field, ID, ObjectType, registerEnumType } from '@nestjs/graphql' import type { SubstanceMap } from '@island.is/judicial-system/types' -import { IndictmentCountOffense } from '@island.is/judicial-system/types' +import { + IndictmentCountOffense, + IndictmentSubtype, +} from '@island.is/judicial-system/types' registerEnumType(IndictmentCountOffense, { name: 'IndictmentCountOffense' }) +registerEnumType(IndictmentSubtype, { name: 'IndictmentSubtype' }) @ObjectType() export class IndictmentCount { @@ -41,4 +45,7 @@ export class IndictmentCount { @Field(() => String, { nullable: true }) readonly legalArguments?: string + + @Field(() => [IndictmentSubtype], { nullable: true }) + readonly indictmentCountSubtypes?: IndictmentSubtype[] } diff --git a/apps/judicial-system/backend/infra/judicial-system-backend.ts b/apps/judicial-system/backend/infra/judicial-system-backend.ts index bab01682da23..8d8fb88fcd95 100644 --- a/apps/judicial-system/backend/infra/judicial-system-backend.ts +++ b/apps/judicial-system/backend/infra/judicial-system-backend.ts @@ -68,6 +68,8 @@ export const serviceSetup = (): ServiceBuilder<'judicial-system-backend'> => EMAIL_FROM_NAME: '/k8s/judicial-system/EMAIL_FROM_NAME', EMAIL_REPLY_TO: '/k8s/judicial-system/EMAIL_REPLY_TO', EMAIL_REPLY_TO_NAME: '/k8s/judicial-system/EMAIL_REPLY_TO_NAME', + POLICE_INSTITUTIONS_EMAILS: + '/k8s/judicial-system/POLICE_INSTITUTIONS_EMAILS', PRISON_EMAIL: '/k8s/judicial-system/PRISON_EMAIL', PRISON_ADMIN_EMAIL: '/k8s/judicial-system/PRISON_ADMIN_EMAIL', PRISON_ADMIN_INDICTMENT_EMAILS: diff --git a/apps/judicial-system/backend/migrations/20241204124025-update-indictment-count.js b/apps/judicial-system/backend/migrations/20241204124025-update-indictment-count.js new file mode 100644 index 000000000000..523e416ba23e --- /dev/null +++ b/apps/judicial-system/backend/migrations/20241204124025-update-indictment-count.js @@ -0,0 +1,25 @@ +'use strict' + +module.exports = { + async up(queryInterface, Sequelize) { + await queryInterface.sequelize.transaction((transaction) => + queryInterface.addColumn( + 'indictment_count', + 'indictment_count_subtypes', + { + type: Sequelize.ARRAY(Sequelize.STRING), + allowNull: true, + defaultValue: [], + }, + { transaction }, + ), + ) + }, + + async down(queryInterface) { + await queryInterface.removeColumn( + 'indictment_count', + 'indictment_count_subtypes', + ) + }, +} diff --git a/apps/judicial-system/backend/src/app/messages/notifications.ts b/apps/judicial-system/backend/src/app/messages/notifications.ts index a7bf8f898acf..43bda5af9fb3 100644 --- a/apps/judicial-system/backend/src/app/messages/notifications.ts +++ b/apps/judicial-system/backend/src/app/messages/notifications.ts @@ -52,7 +52,7 @@ export const notifications = { }), emailWhitelistDomains: defineMessage({ id: 'judicial.system.backend:notifications.email_whitelist_domains', - defaultMessage: 'omnitrix.is,kolibri.is', + defaultMessage: 'omnitrix.is,kolibri.is,dummy.dd', description: 'Notað til að tilgreina hvort póstfang sé í hvítlista', }), readyForCourt: defineMessages({ diff --git a/apps/judicial-system/backend/src/app/modules/case/case.controller.ts b/apps/judicial-system/backend/src/app/modules/case/case.controller.ts index 9728dc420508..3f11857ac9c5 100644 --- a/apps/judicial-system/backend/src/app/modules/case/case.controller.ts +++ b/apps/judicial-system/backend/src/app/modules/case/case.controller.ts @@ -70,6 +70,7 @@ import { CurrentCase } from './guards/case.decorator' import { CaseCompletedGuard } from './guards/caseCompleted.guard' import { CaseExistsGuard } from './guards/caseExists.guard' import { CaseReadGuard } from './guards/caseRead.guard' +import { CaseTransitionGuard } from './guards/caseTransition.guard' import { CaseTypeGuard } from './guards/caseType.guard' import { CaseWriteGuard } from './guards/caseWrite.guard' import { MergedCaseExistsGuard } from './guards/mergedCaseExists.guard' @@ -273,7 +274,13 @@ export class CaseController { return this.caseService.update(theCase, update, user) as Promise<Case> // Never returns undefined } - @UseGuards(JwtAuthGuard, CaseExistsGuard, RolesGuard, CaseWriteGuard) + @UseGuards( + JwtAuthGuard, + CaseExistsGuard, + RolesGuard, + CaseWriteGuard, + CaseTransitionGuard, + ) @RolesRules( prosecutorTransitionRule, prosecutorRepresentativeTransitionRule, diff --git a/apps/judicial-system/backend/src/app/modules/case/guards/caseTransition.guard.ts b/apps/judicial-system/backend/src/app/modules/case/guards/caseTransition.guard.ts new file mode 100644 index 000000000000..80898c0fc5f2 --- /dev/null +++ b/apps/judicial-system/backend/src/app/modules/case/guards/caseTransition.guard.ts @@ -0,0 +1,37 @@ +import { + CanActivate, + ExecutionContext, + ForbiddenException, + Injectable, + InternalServerErrorException, +} from '@nestjs/common' + +import { User } from '@island.is/judicial-system/types' + +import { getTransitionRule } from './caseTransitionRules' + +@Injectable() +// Used for more complex cases than just whether a role can perform a +// transition overall, which is handled in the transition roles rules +export class CaseTransitionGuard implements CanActivate { + canActivate(context: ExecutionContext): boolean { + const request = context.switchToHttp().getRequest() + + const { transition } = request.body + const theCase = request.case + const user: User = request.user + + // This shouldn't happen + if (!theCase || !user) { + throw new InternalServerErrorException('Missing case or user') + } + + const transitionRule = getTransitionRule(transition) + + if (!transitionRule(theCase, user)) { + throw new ForbiddenException('Forbidden transition') + } + + return true + } +} diff --git a/apps/judicial-system/backend/src/app/modules/case/guards/caseTransitionRules.ts b/apps/judicial-system/backend/src/app/modules/case/guards/caseTransitionRules.ts new file mode 100644 index 000000000000..605e877e3791 --- /dev/null +++ b/apps/judicial-system/backend/src/app/modules/case/guards/caseTransitionRules.ts @@ -0,0 +1,41 @@ +import { ForbiddenException } from '@nestjs/common' + +import { + CaseIndictmentRulingDecision, + CaseTransition, + CaseType, + User, +} from '@island.is/judicial-system/types' + +import { Case } from '..' + +type TransitionRule = (theCase: Case, user: User) => boolean + +const defaultTransitionRule: TransitionRule = () => true + +const completeTransitionRule: TransitionRule = (theCase, user) => { + if (theCase.type !== CaseType.INDICTMENT) { + throw new ForbiddenException( + `Forbidden transition for ${theCase.type} cases`, + ) + } + + if ( + theCase.indictmentRulingDecision === CaseIndictmentRulingDecision.RULING || + theCase.indictmentRulingDecision === CaseIndictmentRulingDecision.DISMISSAL + ) { + return user.id === theCase.judgeId + } + + return true +} + +const transitionRuleMap: Partial<Record<CaseTransition, TransitionRule>> = { + [CaseTransition.COMPLETE]: completeTransitionRule, +} + +export const getTransitionRule = ( + transition: CaseTransition, +): TransitionRule => { + return transitionRuleMap[transition] || defaultTransitionRule +} diff --git a/apps/judicial-system/backend/src/app/modules/case/guards/rolesRules.ts b/apps/judicial-system/backend/src/app/modules/case/guards/rolesRules.ts index 2b95c2f46a57..1e4b10ef019f 100644 --- a/apps/judicial-system/backend/src/app/modules/case/guards/rolesRules.ts +++ b/apps/judicial-system/backend/src/app/modules/case/guards/rolesRules.ts @@ -211,7 +211,7 @@ export const prosecutorTransitionRule: RolesRule = { const dto: TransitionCaseDto = request.body const theCase: Case = request.case - // Deny if something is missing - shuould never happen + // Deny if something is missing - should never happen if (!user || !dto || !theCase) { return false } @@ -258,7 +258,7 @@ export const defenderTransitionRule: RolesRule = { const dto: TransitionCaseDto = request.body const theCase: Case = request.case - // Deny if something is missing - shuould never happen + // Deny if something is missing - should never happen if (!dto || !theCase) { return false } @@ -393,7 +393,7 @@ export const districtCourtJudgeSignRulingRule: RolesRule = { const user: User = request.user const theCase: Case = request.case - // Deny if something is missing - shuould never happen + // Deny if something is missing - should never happen if (!user || !theCase) { return false } diff --git a/apps/judicial-system/backend/src/app/modules/case/guards/test/caseTransitionGuard.spec.ts b/apps/judicial-system/backend/src/app/modules/case/guards/test/caseTransitionGuard.spec.ts new file mode 100644 index 000000000000..6c88ea5e5053 --- /dev/null +++ b/apps/judicial-system/backend/src/app/modules/case/guards/test/caseTransitionGuard.spec.ts @@ -0,0 +1,98 @@ +import { + ExecutionContext, + ForbiddenException, + InternalServerErrorException, +} from '@nestjs/common' + +import { + CaseIndictmentRulingDecision, + CaseTransition, + CaseType, +} from '@island.is/judicial-system/types' + +import { CaseTransitionGuard } from '../caseTransition.guard' + +describe('CaseTransitionGuard', () => { + let guard: CaseTransitionGuard + // eslint-disable-next-line @typescript-eslint/no-unused-vars + let mockRequest: jest.Mock + + beforeEach(() => { + guard = new CaseTransitionGuard() + mockRequest = jest.fn() + }) + + const mockExecutionContext = (requestMock: unknown): ExecutionContext => + ({ + switchToHttp: () => ({ + getRequest: () => requestMock, + }), + } as unknown as ExecutionContext) + + const createMockCase = ( + type: CaseType, + rulingDecision: unknown, + judgeId: string, + ) => ({ + type, + indictmentRulingDecision: rulingDecision, + judgeId, + }) + + it('should activate when the judge is the assigned judge', () => { + const mockCase = createMockCase( + CaseType.INDICTMENT, + CaseIndictmentRulingDecision.RULING, + 'judgeId', + ) + const context = mockExecutionContext({ + body: { transition: CaseTransition.COMPLETE }, + case: mockCase, + user: { id: 'judgeId' }, + }) + + const result = guard.canActivate(context) + + expect(result).toBe(true) + }) + + it('should not activate when the user is not the assigned judge', () => { + const mockCase = createMockCase( + CaseType.INDICTMENT, + CaseIndictmentRulingDecision.RULING, + 'judgeId', + ) + const context = mockExecutionContext({ + body: { transition: CaseTransition.COMPLETE }, + case: mockCase, + user: { id: 'differentJudgeId' }, + }) + + expect(() => guard.canActivate(context)).toThrow(ForbiddenException) + }) + + it('should activate using the default rule for transitions not in the rule map', () => { + const mockCase = createMockCase(CaseType.CUSTODY, null, 'someId') + const context = mockExecutionContext({ + body: { transition: CaseTransition.SUBMIT }, + case: mockCase, + user: { id: 'someId' }, + }) + + const result = guard.canActivate(context) + + expect(result).toBe(true) + }) + + it('should throw InternalServerErrorException when case or user is missing', () => { + const context = mockExecutionContext({ + body: { transition: CaseTransition.COMPLETE }, + case: null, + user: null, + }) + + expect(() => guard.canActivate(context)).toThrow( + InternalServerErrorException, + ) + }) +}) diff --git a/apps/judicial-system/backend/src/app/modules/case/test/caseController/transitionGuards.spec.ts b/apps/judicial-system/backend/src/app/modules/case/test/caseController/transitionGuards.spec.ts index 2e7775081e91..1e275969093b 100644 --- a/apps/judicial-system/backend/src/app/modules/case/test/caseController/transitionGuards.spec.ts +++ b/apps/judicial-system/backend/src/app/modules/case/test/caseController/transitionGuards.spec.ts @@ -4,6 +4,7 @@ import { JwtAuthGuard, RolesGuard } from '@island.is/judicial-system/auth' import { CaseController } from '../../case.controller' import { CaseExistsGuard } from '../../guards/caseExists.guard' +import { CaseTransitionGuard } from '../../guards/caseTransition.guard' import { CaseWriteGuard } from '../../guards/caseWrite.guard' describe('CaseController - Transition guards', () => { @@ -17,8 +18,8 @@ describe('CaseController - Transition guards', () => { ) }) - it('should have four guards', () => { - expect(guards).toHaveLength(4) + it('should have five guards', () => { + expect(guards).toHaveLength(5) }) describe('JwtAuthGuard', () => { @@ -68,4 +69,15 @@ describe('CaseController - Transition guards', () => { expect(guard).toBeInstanceOf(CaseWriteGuard) }) }) + describe('CaseTransitionGuard', () => { + let guard: CanActivate + + beforeEach(() => { + guard = new guards[4]() + }) + + it('should have CaseTransitionGuard as guard 5', () => { + expect(guard).toBeInstanceOf(CaseTransitionGuard) + }) + }) }) diff --git a/apps/judicial-system/backend/src/app/modules/event-log/eventLog.module.ts b/apps/judicial-system/backend/src/app/modules/event-log/eventLog.module.ts index d208d6b47f62..2e23e6aacfa5 100644 --- a/apps/judicial-system/backend/src/app/modules/event-log/eventLog.module.ts +++ b/apps/judicial-system/backend/src/app/modules/event-log/eventLog.module.ts @@ -1,12 +1,17 @@ -import { Module } from '@nestjs/common' +import { forwardRef, Module } from '@nestjs/common' import { SequelizeModule } from '@nestjs/sequelize' +import { MessageModule } from '@island.is/judicial-system/message' + import { EventLog } from './models/eventLog.model' import { EventLogController } from './eventLog.controller' import { EventLogService } from './eventLog.service' @Module({ - imports: [SequelizeModule.forFeature([EventLog])], + imports: [ + forwardRef(() => MessageModule), + SequelizeModule.forFeature([EventLog]), + ], providers: [EventLogService], exports: [EventLogService], controllers: [EventLogController], diff --git a/apps/judicial-system/backend/src/app/modules/event-log/eventLog.service.ts b/apps/judicial-system/backend/src/app/modules/event-log/eventLog.service.ts index c1ee4450f943..52806927363b 100644 --- a/apps/judicial-system/backend/src/app/modules/event-log/eventLog.service.ts +++ b/apps/judicial-system/backend/src/app/modules/event-log/eventLog.service.ts @@ -7,7 +7,11 @@ import { InjectModel } from '@nestjs/sequelize' import type { Logger } from '@island.is/logging' import { LOGGER_PROVIDER } from '@island.is/logging' -import { EventType } from '@island.is/judicial-system/types' +import { MessageService, MessageType } from '@island.is/judicial-system/message' +import { + EventNotificationType, + EventType, +} from '@island.is/judicial-system/types' import { CreateEventLogDto } from './dto/createEventLog.dto' import { EventLog } from './models/eventLog.model' @@ -20,11 +24,19 @@ const allowMultiple: EventType[] = [ EventType.INDICTMENT_CONFIRMED, ] +const eventToNotificationMap: Partial< + Record<EventType, EventNotificationType> +> = { + INDICTMENT_SENT_TO_PUBLIC_PROSECUTOR: + EventNotificationType.INDICTMENT_SENT_TO_PUBLIC_PROSECUTOR, +} + @Injectable() export class EventLogService { constructor( @InjectModel(EventLog) private readonly eventLogModel: typeof EventLog, + private readonly messageService: MessageService, @Inject(LOGGER_PROVIDER) private readonly logger: Logger, ) {} @@ -58,6 +70,10 @@ export class EventLogService { // Tolerate failure but log error this.logger.error('Failed to create event log', error) } + + if (caseId) { + this.addEventNotificationToQueue(eventType, caseId) + } } async loginMap( @@ -86,4 +102,23 @@ export class EventLogService { ), ) } + + // Sends events to queue for notification dispatch + private addEventNotificationToQueue(eventType: EventType, caseId: string) { + const notificationType = eventToNotificationMap[eventType] + + if (notificationType) { + try { + this.messageService.sendMessagesToQueue([ + { + type: MessageType.EVENT_NOTIFICATION_DISPATCH, + caseId: caseId, + body: { type: notificationType }, + }, + ]) + } catch (error) { + this.logger.error('Failed to send event notification to queue', error) + } + } + } } diff --git a/apps/judicial-system/backend/src/app/modules/indictment-count/dto/updateIndictmentCount.dto.ts b/apps/judicial-system/backend/src/app/modules/indictment-count/dto/updateIndictmentCount.dto.ts index 0559ffa35277..3b12997e7fed 100644 --- a/apps/judicial-system/backend/src/app/modules/indictment-count/dto/updateIndictmentCount.dto.ts +++ b/apps/judicial-system/backend/src/app/modules/indictment-count/dto/updateIndictmentCount.dto.ts @@ -10,7 +10,10 @@ import { import { ApiPropertyOptional } from '@nestjs/swagger' import type { SubstanceMap } from '@island.is/judicial-system/types' -import { IndictmentCountOffense } from '@island.is/judicial-system/types' +import { + IndictmentCountOffense, + IndictmentSubtype, +} from '@island.is/judicial-system/types' export class UpdateIndictmentCountDto { @IsOptional() @@ -50,4 +53,10 @@ export class UpdateIndictmentCountDto { @IsString() @ApiPropertyOptional({ type: String }) readonly legalArguments?: string + + @IsOptional() + @IsArray() + @IsEnum(IndictmentSubtype, { each: true }) + @ApiPropertyOptional({ enum: IndictmentSubtype, isArray: true }) + readonly indictmentCountSubtypes?: IndictmentSubtype[] } diff --git a/apps/judicial-system/backend/src/app/modules/indictment-count/models/indictmentCount.model.ts b/apps/judicial-system/backend/src/app/modules/indictment-count/models/indictmentCount.model.ts index ae7ec5c59316..03b9d511f2d7 100644 --- a/apps/judicial-system/backend/src/app/modules/indictment-count/models/indictmentCount.model.ts +++ b/apps/judicial-system/backend/src/app/modules/indictment-count/models/indictmentCount.model.ts @@ -11,7 +11,10 @@ import { import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger' import type { SubstanceMap } from '@island.is/judicial-system/types' -import { IndictmentCountOffense } from '@island.is/judicial-system/types' +import { + IndictmentCountOffense, + IndictmentSubtype, +} from '@island.is/judicial-system/types' import { Case } from '../../case/models/case.model' @@ -69,4 +72,12 @@ export class IndictmentCount extends Model { @Column({ type: DataType.TEXT, allowNull: true }) @ApiPropertyOptional({ type: String }) legalArguments?: string + + @Column({ + type: DataType.ARRAY(DataType.ENUM), + allowNull: true, + values: Object.values(IndictmentSubtype), + }) + @ApiPropertyOptional({ enum: IndictmentSubtype, isArray: true }) + indictmentCountSubtypes?: IndictmentSubtype[] } diff --git a/apps/judicial-system/backend/src/app/modules/notification/dto/indictmentCaseNotification.dto.ts b/apps/judicial-system/backend/src/app/modules/notification/dto/indictmentCaseNotification.dto.ts new file mode 100644 index 000000000000..201f04c208b5 --- /dev/null +++ b/apps/judicial-system/backend/src/app/modules/notification/dto/indictmentCaseNotification.dto.ts @@ -0,0 +1,12 @@ +import { IsEnum, IsNotEmpty } from 'class-validator' + +import { ApiProperty } from '@nestjs/swagger' + +import { IndictmentCaseNotificationType } from '@island.is/judicial-system/types' + +export class IndictmentCaseNotificationDto { + @IsNotEmpty() + @IsEnum(IndictmentCaseNotificationType) + @ApiProperty({ enum: IndictmentCaseNotificationType }) + readonly type!: IndictmentCaseNotificationType +} diff --git a/apps/judicial-system/backend/src/app/modules/notification/dto/notificationDispatch.dto.ts b/apps/judicial-system/backend/src/app/modules/notification/dto/notificationDispatch.dto.ts index 017f7a7fde0d..984e356a627a 100644 --- a/apps/judicial-system/backend/src/app/modules/notification/dto/notificationDispatch.dto.ts +++ b/apps/judicial-system/backend/src/app/modules/notification/dto/notificationDispatch.dto.ts @@ -2,7 +2,10 @@ import { IsEnum, IsNotEmpty } from 'class-validator' import { ApiProperty } from '@nestjs/swagger' -import { NotificationDispatchType } from '@island.is/judicial-system/types' +import { + EventNotificationType, + NotificationDispatchType, +} from '@island.is/judicial-system/types' export class NotificationDispatchDto { @IsNotEmpty() @@ -10,3 +13,10 @@ export class NotificationDispatchDto { @ApiProperty({ enum: NotificationDispatchType }) readonly type!: NotificationDispatchType } + +export class EventNotificationDispatchDto { + @IsNotEmpty() + @IsEnum(EventNotificationType) + @ApiProperty({ enum: EventNotificationType }) + readonly type!: EventNotificationType +} diff --git a/apps/judicial-system/backend/src/app/modules/notification/internalNotification.controller.ts b/apps/judicial-system/backend/src/app/modules/notification/internalNotification.controller.ts index 68131739f280..af4c5a235c61 100644 --- a/apps/judicial-system/backend/src/app/modules/notification/internalNotification.controller.ts +++ b/apps/judicial-system/backend/src/app/modules/notification/internalNotification.controller.ts @@ -16,8 +16,9 @@ import { messageEndpoint, MessageType, } from '@island.is/judicial-system/message' +import { indictmentCases } from '@island.is/judicial-system/types' -import { Case, CaseHasExistedGuard, CurrentCase } from '../case' +import { Case, CaseHasExistedGuard, CaseTypeGuard, CurrentCase } from '../case' import { CivilClaimant, CivilClaimantExistsGuard, @@ -30,13 +31,18 @@ import { SubpoenaExistsGuard } from '../subpoena' import { CaseNotificationDto } from './dto/caseNotification.dto' import { CivilClaimantNotificationDto } from './dto/civilClaimantNotification.dto' import { DefendantNotificationDto } from './dto/defendantNotification.dto' +import { IndictmentCaseNotificationDto } from './dto/indictmentCaseNotification.dto' import { InstitutionNotificationDto } from './dto/institutionNotification.dto' -import { NotificationDispatchDto } from './dto/notificationDispatch.dto' +import { + EventNotificationDispatchDto, + NotificationDispatchDto, +} from './dto/notificationDispatch.dto' import { SubpoenaNotificationDto } from './dto/subpoenaNotification.dto' import { DeliverResponse } from './models/deliver.response' import { CaseNotificationService } from './services/caseNotification/caseNotification.service' import { CivilClaimantNotificationService } from './services/civilClaimantNotification/civilClaimantNotification.service' import { DefendantNotificationService } from './services/defendantNotification/defendantNotification.service' +import { IndictmentCaseNotificationService } from './services/indictmentCaseNotification/indictmentCaseNotification.service' import { InstitutionNotificationService } from './services/institutionNotification/institutionNotification.service' import { SubpoenaNotificationService } from './services/subpoenaNotification/subpoenaNotification.service' import { NotificationDispatchService } from './notificationDispatch.service' @@ -52,6 +58,7 @@ export class InternalNotificationController { private readonly subpoenaNotificationService: SubpoenaNotificationService, private readonly defendantNotificationService: DefendantNotificationService, private readonly civilClaimantNotificationService: CivilClaimantNotificationService, + private readonly indictmentCaseNotificationService: IndictmentCaseNotificationService, @Inject(LOGGER_PROVIDER) private readonly logger: Logger, ) {} @@ -77,6 +84,29 @@ export class InternalNotificationController { ) } + @Post( + `case/:caseId/${messageEndpoint[MessageType.INDICTMENT_CASE_NOTIFICATION]}`, + ) + @UseGuards(CaseHasExistedGuard, new CaseTypeGuard(indictmentCases)) + @ApiCreatedResponse({ + type: DeliverResponse, + description: 'Sends a case notification for an existing indictment case', + }) + sendIndictmentCaseNotification( + @Param('caseId') caseId: string, + @CurrentCase() theCase: Case, + @Body() notificationDto: IndictmentCaseNotificationDto, + ): Promise<DeliverResponse> { + this.logger.debug( + `Sending ${notificationDto.type} indictment case notification for case ${caseId}`, + ) + + return this.indictmentCaseNotificationService.sendIndictmentCaseNotification( + notificationDto.type, + theCase, + ) + } + @Post( `case/:caseId/${ messageEndpoint[MessageType.SUBPOENA_NOTIFICATION] @@ -161,34 +191,58 @@ export class InternalNotificationController { ) } - @Post(messageEndpoint[MessageType.NOTIFICATION_DISPATCH]) + @Post(messageEndpoint[MessageType.INSTITUTION_NOTIFICATION]) @ApiCreatedResponse({ type: DeliverResponse, - description: 'Dispatches notifications', + description: 'Sends an institution notification', }) - dispatchNotification( - @Body() notificationDto: NotificationDispatchDto, + sendInstitutionNotification( + @Body() notificationDto: InstitutionNotificationDto, ): Promise<DeliverResponse> { - this.logger.debug(`Dispatching ${notificationDto.type} notification`) + this.logger.debug(`Sending ${notificationDto.type} notification`) - return this.notificationDispatchService.dispatchNotification( + return this.institutionNotificationService.sendNotification( notificationDto.type, + notificationDto.prosecutorsOfficeId, ) } - @Post(messageEndpoint[MessageType.INSTITUTION_NOTIFICATION]) + @Post( + `case/:caseId/${messageEndpoint[MessageType.EVENT_NOTIFICATION_DISPATCH]}`, + ) + @UseGuards(CaseHasExistedGuard) @ApiCreatedResponse({ type: DeliverResponse, - description: 'Sends an institution notification', + description: + 'Dispatches notifications in response to events logged in event log', }) - sendNotification( - @Body() notificationDto: InstitutionNotificationDto, + dispatchEventNotification( + @Param('caseId') caseId: string, + @CurrentCase() theCase: Case, + @Body() notificationDto: EventNotificationDispatchDto, ): Promise<DeliverResponse> { - this.logger.debug(`Sending ${notificationDto.type} notification`) + this.logger.debug( + `Dispatching ${notificationDto.type} event notification for case ${caseId}`, + ) - return this.institutionNotificationService.sendNotification( + return this.notificationDispatchService.dispatchEventNotification( + notificationDto.type, + theCase, + ) + } + + @Post(messageEndpoint[MessageType.NOTIFICATION_DISPATCH]) + @ApiCreatedResponse({ + type: DeliverResponse, + description: 'Dispatches notifications', + }) + dispatchNotification( + @Body() notificationDto: NotificationDispatchDto, + ): Promise<DeliverResponse> { + this.logger.debug(`Dispatching ${notificationDto.type} notification`) + + return this.notificationDispatchService.dispatchNotification( notificationDto.type, - notificationDto.prosecutorsOfficeId, ) } } diff --git a/apps/judicial-system/backend/src/app/modules/notification/notification.config.ts b/apps/judicial-system/backend/src/app/modules/notification/notification.config.ts index aecd5dabb6c9..e91cc08bd354 100644 --- a/apps/judicial-system/backend/src/app/modules/notification/notification.config.ts +++ b/apps/judicial-system/backend/src/app/modules/notification/notification.config.ts @@ -21,6 +21,12 @@ export const notificationModuleConfig = defineConfig({ courtsEmails: env.requiredJSON('COURTS_EMAILS', {}) as { [key: string]: string }, + policeInstitutionEmails: env.requiredJSON( + 'POLICE_INSTITUTIONS_EMAILS', + {}, + ) as { + [key: string]: string + }, }, sms: { courtsMobileNumbers: env.requiredJSON('COURTS_MOBILE_NUMBERS', {}) as { diff --git a/apps/judicial-system/backend/src/app/modules/notification/notification.module.ts b/apps/judicial-system/backend/src/app/modules/notification/notification.module.ts index 101978e3d3ba..47331de9c1c8 100644 --- a/apps/judicial-system/backend/src/app/modules/notification/notification.module.ts +++ b/apps/judicial-system/backend/src/app/modules/notification/notification.module.ts @@ -20,6 +20,7 @@ import { Notification } from './models/notification.model' import { CaseNotificationService } from './services/caseNotification/caseNotification.service' import { CivilClaimantNotificationService } from './services/civilClaimantNotification/civilClaimantNotification.service' import { DefendantNotificationService } from './services/defendantNotification/defendantNotification.service' +import { IndictmentCaseNotificationService } from './services/indictmentCaseNotification/indictmentCaseNotification.service' import { InstitutionNotificationService } from './services/institutionNotification/institutionNotification.service' import { SubpoenaNotificationService } from './services/subpoenaNotification/subpoenaNotification.service' import { InternalNotificationController } from './internalNotification.controller' @@ -47,6 +48,7 @@ import { NotificationDispatchService } from './notificationDispatch.service' CaseNotificationService, CivilClaimantNotificationService, DefendantNotificationService, + IndictmentCaseNotificationService, InstitutionNotificationService, NotificationService, NotificationDispatchService, diff --git a/apps/judicial-system/backend/src/app/modules/notification/notificationDispatch.service.ts b/apps/judicial-system/backend/src/app/modules/notification/notificationDispatch.service.ts index 13884be1e9e0..dbaff1fd065d 100644 --- a/apps/judicial-system/backend/src/app/modules/notification/notificationDispatch.service.ts +++ b/apps/judicial-system/backend/src/app/modules/notification/notificationDispatch.service.ts @@ -3,16 +3,20 @@ import { Injectable, InternalServerErrorException, } from '@nestjs/common' +import { ConfigType } from '@nestjs/config' import { type Logger, LOGGER_PROVIDER } from '@island.is/logging' import { MessageService, MessageType } from '@island.is/judicial-system/message' import { + EventNotificationType, + IndictmentCaseNotificationType, InstitutionNotificationType, InstitutionType, NotificationDispatchType, } from '@island.is/judicial-system/types' +import { Case } from '../case' import { Institution, InstitutionService } from '../institution' import { DeliverResponse } from './models/deliver.response' @@ -63,4 +67,43 @@ export class NotificationDispatchService { return { delivered: true } } + + private async dispatchIndictmentSentToPublicProsecutorNotifications( + theCase: Case, + ): Promise<void> { + return this.messageService.sendMessagesToQueue([ + { + type: MessageType.INDICTMENT_CASE_NOTIFICATION, + caseId: theCase.id, + body: { + type: IndictmentCaseNotificationType.INDICTMENT_VERDICT_INFO, + }, + }, + ]) + } + + async dispatchEventNotification( + type: EventNotificationType, + theCase: Case, + ): Promise<DeliverResponse> { + try { + switch (type) { + case EventNotificationType.INDICTMENT_SENT_TO_PUBLIC_PROSECUTOR: + await this.dispatchIndictmentSentToPublicProsecutorNotifications( + theCase, + ) + break + default: + throw new InternalServerErrorException( + `Invalid notification type ${type}`, + ) + } + } catch (error) { + this.logger.error('Failed to dispatch event notification', error) + + return { delivered: false } + } + + return { delivered: true } + } } diff --git a/apps/judicial-system/backend/src/app/modules/notification/services/indictmentCaseNotification/indictmentCaseNotification.service.ts b/apps/judicial-system/backend/src/app/modules/notification/services/indictmentCaseNotification/indictmentCaseNotification.service.ts new file mode 100644 index 000000000000..821662fa0ca0 --- /dev/null +++ b/apps/judicial-system/backend/src/app/modules/notification/services/indictmentCaseNotification/indictmentCaseNotification.service.ts @@ -0,0 +1,155 @@ +import { + Inject, + Injectable, + InternalServerErrorException, +} from '@nestjs/common' +import { InjectModel } from '@nestjs/sequelize' + +import { IntlService } from '@island.is/cms-translations' +import { EmailService } from '@island.is/email-service' +import { type Logger, LOGGER_PROVIDER } from '@island.is/logging' +import { type ConfigType } from '@island.is/nest/config' + +import { + CaseIndictmentRulingDecision, + IndictmentCaseNotificationType, + IndictmentDecision, +} from '@island.is/judicial-system/types' + +import { Case } from '../../../case' +import { EventService } from '../../../event' +import { BaseNotificationService } from '../../baseNotification.service' +import { DeliverResponse } from '../../models/deliver.response' +import { Notification, Recipient } from '../../models/notification.model' +import { notificationModuleConfig } from '../../notification.config' +import { strings } from './indictmentCaseNotification.strings' + +@Injectable() +export class IndictmentCaseNotificationService extends BaseNotificationService { + constructor( + @InjectModel(Notification) + notificationModel: typeof Notification, + @Inject(notificationModuleConfig.KEY) + config: ConfigType<typeof notificationModuleConfig>, + @Inject(LOGGER_PROVIDER) logger: Logger, + intlService: IntlService, + emailService: EmailService, + eventService: EventService, + ) { + super( + notificationModel, + emailService, + intlService, + config, + eventService, + logger, + ) + } + + private async sendEmails( + theCase: Case, + notificationType: IndictmentCaseNotificationType, + subject: string, + body: string, + to: { name?: string; email?: string }[], + ) { + const promises: Promise<Recipient>[] = [] + + for (const recipient of to) { + if (recipient.email && recipient.name) { + promises.push( + this.sendEmail( + subject, + body, + recipient.name, + recipient.email, + undefined, + true, + ), + ) + } + } + + const recipients = await Promise.all(promises) + + return this.recordNotification(theCase.id, notificationType, recipients) + } + + private async sendVerdictInfoNotification( + theCase: Case, + ): Promise<DeliverResponse> { + const institutionId = theCase.prosecutor?.institution?.id + const institutionEmail = + (institutionId && + this.config.email.policeInstitutionEmails[institutionId]) ?? + undefined + + const hasRuling = + theCase.indictmentRulingDecision === CaseIndictmentRulingDecision.RULING + + if (!institutionEmail || !hasRuling) { + // institution does not want to receive these emails or the case does not have a ruling + return { delivered: true } + } + + const formattedSubject = this.formatMessage( + strings.indictmentCompletedWithRuling.subject, + { + courtCaseNumber: theCase.courtCaseNumber, + }, + ) + + const formattedBody = this.formatMessage( + strings.indictmentCompletedWithRuling.body, + { + courtCaseNumber: theCase.courtCaseNumber, + courtName: theCase.court?.name, + serviceRequirement: + theCase.defendants && theCase.defendants[0].serviceRequirement, + caseOrigin: theCase.origin, + }, + ) + + return this.sendEmails( + theCase, + IndictmentCaseNotificationType.INDICTMENT_VERDICT_INFO, + formattedSubject, + formattedBody, + [ + { + name: theCase.prosecutor?.institution?.name, + email: institutionEmail, + }, + ], + ) + } + + private sendNotification( + notificationType: IndictmentCaseNotificationType, + theCase: Case, + ): Promise<DeliverResponse> { + switch (notificationType) { + case IndictmentCaseNotificationType.INDICTMENT_VERDICT_INFO: + return this.sendVerdictInfoNotification(theCase) + + default: + throw new InternalServerErrorException( + `Invalid indictment notification type: ${notificationType}`, + ) + } + } + + async sendIndictmentCaseNotification( + type: IndictmentCaseNotificationType, + theCase: Case, + ): Promise<DeliverResponse> { + await this.refreshFormatMessage() + try { + return await this.sendNotification(type, theCase) + } catch (error) { + this.logger.error('Failed to send indictment case notification', error) + + return { delivered: false } + } + } +} diff --git a/apps/judicial-system/backend/src/app/modules/notification/services/indictmentCaseNotification/indictmentCaseNotification.strings.ts b/apps/judicial-system/backend/src/app/modules/notification/services/indictmentCaseNotification/indictmentCaseNotification.strings.ts new file mode 100644 index 000000000000..c9ed5ec52513 --- /dev/null +++ b/apps/judicial-system/backend/src/app/modules/notification/services/indictmentCaseNotification/indictmentCaseNotification.strings.ts @@ -0,0 +1,19 @@ +import { defineMessages } from '@formatjs/intl' + +export const strings = { + indictmentCompletedWithRuling: defineMessages({ + subject: { + id: 'judicial.system.backend:indictment_case_notifications.verdict_service.subject', + defaultMessage: 'Máli lokið {courtCaseNumber}', + description: + 'Notað sem titill í tilkynningu um stöðu birtingar dóms í lokinni ákæru', + }, + body: { + id: 'judicial.system.backend:indictment_case_notifications.verdict_service.body', + defaultMessage: + 'Máli {courtCaseNumber} hjá {courtName} hefur verið lokið.\n\nNiðurstaða: Dómur\n\n{serviceRequirement, select, REQUIRED {Birta skal dómfellda dóminn} NOT_REQUIRED {Birting dóms ekki þörf} NOT_APPLICABLE {Dómfelldi var viðstaddur dómsuppkvaðningu} other {}}\n\n{caseOrigin, select, LOKE {Dómur er aðgengilegur í LÖKE.} other {}}', + description: + 'Notað sem body í tilkynningu um stöðu birtingar dóms í lokinni ákæru', + }, + }), +} diff --git a/apps/judicial-system/backend/src/app/modules/notification/test/createTestingNotificationModule.ts b/apps/judicial-system/backend/src/app/modules/notification/test/createTestingNotificationModule.ts index c5ea4ac34521..fa41ef517272 100644 --- a/apps/judicial-system/backend/src/app/modules/notification/test/createTestingNotificationModule.ts +++ b/apps/judicial-system/backend/src/app/modules/notification/test/createTestingNotificationModule.ts @@ -33,6 +33,7 @@ import { NotificationDispatchService } from '../notificationDispatch.service' import { CaseNotificationService } from '../services/caseNotification/caseNotification.service' import { CivilClaimantNotificationService } from '../services/civilClaimantNotification/civilClaimantNotification.service' import { DefendantNotificationService } from '../services/defendantNotification/defendantNotification.service' +import { IndictmentCaseNotificationService } from '../services/indictmentCaseNotification/indictmentCaseNotification.service' import { InstitutionNotificationService } from '../services/institutionNotification/institutionNotification.service' jest.mock('@island.is/judicial-system/message') @@ -130,6 +131,7 @@ export const createTestingNotificationModule = async () => { InstitutionNotificationService, DefendantNotificationService, CivilClaimantNotificationService, + IndictmentCaseNotificationService, ], }) .useMocker((token) => { @@ -158,6 +160,9 @@ export const createTestingNotificationModule = async () => { internalNotificationController: notificationModule.get( InternalNotificationController, ), + indictmentCaseNotificationService: notificationModule.get( + IndictmentCaseNotificationService, + ), } notificationModule.close() diff --git a/apps/judicial-system/backend/src/app/modules/notification/test/internalNotificationController/eventNotificationDispatch/eventNotificationDispatch.spec.ts b/apps/judicial-system/backend/src/app/modules/notification/test/internalNotificationController/eventNotificationDispatch/eventNotificationDispatch.spec.ts new file mode 100644 index 000000000000..490b135e8429 --- /dev/null +++ b/apps/judicial-system/backend/src/app/modules/notification/test/internalNotificationController/eventNotificationDispatch/eventNotificationDispatch.spec.ts @@ -0,0 +1,77 @@ +import { uuid } from 'uuidv4' + +import { MessageService, MessageType } from '@island.is/judicial-system/message' +import { + EventNotificationType, + IndictmentCaseNotificationType, +} from '@island.is/judicial-system/types' + +import { createTestingNotificationModule } from '../../createTestingNotificationModule' + +import { Case } from '../../../../case' +import { InternalNotificationController } from '../../../internalNotification.controller' + +describe('InternalNotificationController - Dispatch event notifications', () => { + const theCase = { id: uuid() } as Case + let mockMessageService: MessageService + let internalNotificationController: InternalNotificationController + + beforeEach(async () => { + const { messageService, internalNotificationController: controller } = + await createTestingNotificationModule() + + mockMessageService = messageService + internalNotificationController = controller + + const mockSendMessagesToQueue = + messageService.sendMessagesToQueue as jest.Mock + mockSendMessagesToQueue.mockResolvedValueOnce(undefined) + }) + + const notificationScenarios = [ + { + notificationType: + EventNotificationType.INDICTMENT_SENT_TO_PUBLIC_PROSECUTOR, + expectedMessage: { + type: MessageType.INDICTMENT_CASE_NOTIFICATION, + caseId: theCase.id, + body: { + type: IndictmentCaseNotificationType.INDICTMENT_VERDICT_INFO, + }, + }, + }, + ] + + it.each( + notificationScenarios.map(({ notificationType, expectedMessage }) => ({ + notificationType, + expectedMessage, + description: `should send message to queue for notification type ${notificationType}`, + })), + )('$description', async ({ notificationType, expectedMessage }) => { + const result = + await internalNotificationController.dispatchEventNotification( + theCase.id, + theCase, + { type: notificationType }, + ) + + expect(mockMessageService.sendMessagesToQueue).toHaveBeenCalledWith([ + expectedMessage, + ]) + expect(result).toEqual({ delivered: true }) + }) + + it('will fail if a new EventNotificationType is missing from the tests', () => { + const allNotificationTypes = Object.values(EventNotificationType) + const testedNotificationTypes = notificationScenarios.map( + (scenario) => scenario.notificationType, + ) + + const missingNotificationTypes = allNotificationTypes.filter( + (type) => !testedNotificationTypes.includes(type), + ) + + expect(missingNotificationTypes).toEqual([]) + }) +}) diff --git a/apps/judicial-system/backend/src/app/modules/notification/test/internalNotificationController/indictmentCaseNotification/sendIndictmentVerdictInfoNotification.spec.ts b/apps/judicial-system/backend/src/app/modules/notification/test/internalNotificationController/indictmentCaseNotification/sendIndictmentVerdictInfoNotification.spec.ts new file mode 100644 index 000000000000..3c4af29a137b --- /dev/null +++ b/apps/judicial-system/backend/src/app/modules/notification/test/internalNotificationController/indictmentCaseNotification/sendIndictmentVerdictInfoNotification.spec.ts @@ -0,0 +1,146 @@ +import { uuid } from 'uuidv4' + +import { EmailService } from '@island.is/email-service' + +import { + CaseIndictmentRulingDecision, + CaseOrigin, + IndictmentCaseNotificationType, + ServiceRequirement, +} from '@island.is/judicial-system/types' + +import { + createTestingNotificationModule, + createTestUsers, +} from '../../createTestingNotificationModule' + +import { Case } from '../../../../case' +import { DeliverResponse } from '../../../models/deliver.response' + +interface Then { + result: DeliverResponse + error: Error +} + +type GivenWhenThen = ( + theCase: Case, + notificationType: IndictmentCaseNotificationType, +) => Promise<Then> + +describe('IndictmentCaseService', () => { + const { prosecutorsOffice, defender } = createTestUsers([ + 'prosecutorsOffice', + 'defender', + ]) + const caseId = uuid() + const courtName = uuid() + const prosecutorsOfficeName = prosecutorsOffice.name + const prosecutorsOfficeEmail = prosecutorsOffice.email + const prosecutorInstitutionId = uuid() + const courtCaseNumber = uuid() + let theCase = { + id: caseId, + court: { name: courtName }, + origin: CaseOrigin.LOKE, + defendants: [ + { + defenderNationalId: defender.nationalId, + defenderName: defender.name, + defenderEmail: defender.email, + serviceRequirement: ServiceRequirement.REQUIRED, + }, + ], + prosecutor: { + institution: { name: prosecutorsOfficeName, id: prosecutorInstitutionId }, + }, + + courtCaseNumber, + } as Case + + let mockEmailService: EmailService + let givenWhenThen: GivenWhenThen + + process.env.POLICE_INSTITUTIONS_EMAILS = `{"${prosecutorInstitutionId}": "${prosecutorsOfficeEmail}"}` + + beforeEach(async () => { + const { emailService, indictmentCaseNotificationService } = + await createTestingNotificationModule() + + mockEmailService = emailService + + givenWhenThen = async ( + theCase: Case, + notificationType: IndictmentCaseNotificationType, + ) => { + const then = {} as Then + + await indictmentCaseNotificationService + .sendIndictmentCaseNotification(notificationType, theCase) + .then((result) => (then.result = result)) + .catch((error) => (then.error = error)) + + return then + } + }) + + describe('notifications sent to institution with registered e-mail', () => { + it('should not send a notification if indictment ruling decision is not RULING', async () => { + const then = await givenWhenThen( + theCase, + IndictmentCaseNotificationType.INDICTMENT_VERDICT_INFO, + ) + + expect(mockEmailService.sendEmail).toBeCalledTimes(0) + expect(then.result.delivered).toEqual(true) + }) + + it('should send a notification when indictment ruling decision is RULING', async () => { + const caseWithRulingDecision = { + ...theCase, + indictmentRulingDecision: CaseIndictmentRulingDecision.RULING, + } as Case + + const then = await givenWhenThen( + caseWithRulingDecision, + IndictmentCaseNotificationType.INDICTMENT_VERDICT_INFO, + ) + + expect(mockEmailService.sendEmail).toBeCalledWith( + expect.objectContaining({ + to: [ + { address: prosecutorsOfficeEmail, name: prosecutorsOfficeName }, + ], + subject: expect.stringContaining(`Máli lokið ${courtCaseNumber}`), + html: expect.stringContaining( + `Máli ${courtCaseNumber} hjá ${courtName} hefur verið lokið.`, + ), + }), + ) + + expect(then.result).toEqual({ delivered: true }) + }) + }) + + describe('notifications sent to institution without registered e-mail', () => { + it('should not send a notification', async () => { + const invalidProsecutorInstitutionId = uuid() + theCase = { + ...theCase, + prosecutor: { + ...theCase.prosecutor, + institution: { + ...theCase.prosecutor?.institution, + id: invalidProsecutorInstitutionId, + }, + }, + } as Case + + await givenWhenThen( + theCase, + IndictmentCaseNotificationType.INDICTMENT_VERDICT_INFO, + ) + + expect(mockEmailService.sendEmail).not.toBeCalled() + }) + }) +}) diff --git a/apps/judicial-system/backend/src/app/modules/notification/test/internalNotificationController/sendIndictmentsWaitingForConfirmationNotifications.spec.ts b/apps/judicial-system/backend/src/app/modules/notification/test/internalNotificationController/sendIndictmentsWaitingForConfirmationNotifications.spec.ts index 3c20d0d96a26..86c82c0c7275 100644 --- a/apps/judicial-system/backend/src/app/modules/notification/test/internalNotificationController/sendIndictmentsWaitingForConfirmationNotifications.spec.ts +++ b/apps/judicial-system/backend/src/app/modules/notification/test/internalNotificationController/sendIndictmentsWaitingForConfirmationNotifications.spec.ts @@ -60,7 +60,7 @@ describe('InternalNotificationController - Send indictments waiting for confirma const then = {} as Then await internalNotificationController - .sendNotification({ + .sendInstitutionNotification({ type: InstitutionNotificationType.INDICTMENTS_WAITING_FOR_CONFIRMATION, prosecutorsOfficeId, }) diff --git a/apps/judicial-system/web/messages/Core/index.ts b/apps/judicial-system/web/messages/Core/index.ts index f783a0114759..efb61f12781d 100644 --- a/apps/judicial-system/web/messages/Core/index.ts +++ b/apps/judicial-system/web/messages/Core/index.ts @@ -209,6 +209,11 @@ export const core = defineMessages({ defaultMessage: 'Halda áfram', description: 'Notað fyrir "Halda áfram" takka í öllum flæðum.', }, + cancel: { + id: 'judicial.system.core:cancel', + defaultMessage: 'Hætta við', + description: 'Notað fyrir "Hætta við" takka í öllum flæðum.', + }, createCase: { id: 'judicial.system.core:create_case', defaultMessage: 'Stofna mál', diff --git a/apps/judicial-system/web/src/components/BlueBoxWithIcon/BlueBoxWithDate.strings.ts b/apps/judicial-system/web/src/components/BlueBoxWithIcon/BlueBoxWithDate.strings.ts index a3f57cbebd4c..282e98b4bb14 100644 --- a/apps/judicial-system/web/src/components/BlueBoxWithIcon/BlueBoxWithDate.strings.ts +++ b/apps/judicial-system/web/src/components/BlueBoxWithIcon/BlueBoxWithDate.strings.ts @@ -83,4 +83,20 @@ export const strings = defineMessages({ description: 'Notaður sem titill í svæði þar sem kærufrestur viðurlagaákvörðunar er tekinn fram', }, + revokeSendToPrisonAdminModalTitle: { + id: 'judicial.system.core:blue_box_with_date.revoke_send_to_prison_admin_modal_title', + defaultMessage: 'Afturkalla úr fullnustu', + description: 'Notaður sem titill í "Afturkalla úr fullnustu" modal glugga.', + }, + revokeSendToPrisonAdminModalText: { + id: 'judicial.system.core:blue_box_with_date.revoke_send_to_prison_admin_modal_text', + defaultMessage: + 'Mál {courtCaseNumber} verður afturkallað.\nÁkærði: {defendant}.', + description: 'Notaður sem texti í "Afturkalla úr fullnustu" modal glugga.', + }, + revoke: { + id: 'judicial.system.core:blue_box_with_date.revoke', + defaultMessage: 'Afturkalla', + description: 'Notaður sem texti fyrir aðgerðina að afturkalla mál', + }, }) diff --git a/apps/judicial-system/web/src/components/BlueBoxWithIcon/BlueBoxWithDate.tsx b/apps/judicial-system/web/src/components/BlueBoxWithIcon/BlueBoxWithDate.tsx index 323fe5cf4469..24e179704afe 100644 --- a/apps/judicial-system/web/src/components/BlueBoxWithIcon/BlueBoxWithDate.tsx +++ b/apps/judicial-system/web/src/components/BlueBoxWithIcon/BlueBoxWithDate.tsx @@ -15,7 +15,7 @@ import { import { PUBLIC_PROSECUTOR_STAFF_INDICTMENT_SEND_TO_PRISON_ADMIN_ROUTE } from '@island.is/judicial-system/consts' import { formatDate } from '@island.is/judicial-system/formatters' import { VERDICT_APPEAL_WINDOW_DAYS } from '@island.is/judicial-system/types' -import { errors } from '@island.is/judicial-system-web/messages' +import { core, errors } from '@island.is/judicial-system-web/messages' import { CaseIndictmentRulingDecision, @@ -26,6 +26,7 @@ import { formatDateForServer, useDefendants } from '../../utils/hooks' import DateTime from '../DateTime/DateTime' import { FormContext } from '../FormProvider/FormProvider' import { getAppealExpirationInfo } from '../InfoCard/DefendantInfo/DefendantInfo' +import Modal from '../Modal/Modal' import SectionHeading from '../SectionHeading/SectionHeading' import { strings } from './BlueBoxWithDate.strings' import * as styles from './BlueBoxWithIcon.css' @@ -35,6 +36,8 @@ interface Props { icon?: IconMapIcon } +type VisibleModal = 'REVOKE_SEND_TO_PRISON_ADMIN' + const BlueBoxWithDate: FC<Props> = (props) => { const { defendant, icon } = props const { formatMessage } = useIntl() @@ -47,7 +50,8 @@ const BlueBoxWithDate: FC<Props> = (props) => { }) const [triggerAnimation, setTriggerAnimation] = useState<boolean>(false) const [triggerAnimation2, setTriggerAnimation2] = useState<boolean>(false) - const { setAndSendDefendantToServer } = useDefendants() + const [modalVisible, setModalVisible] = useState<VisibleModal>() + const { setAndSendDefendantToServer, isUpdatingDefendant } = useDefendants() const { workingCase, setWorkingCase } = useContext(FormContext) const router = useRouter() @@ -111,6 +115,8 @@ const BlueBoxWithDate: FC<Props> = (props) => { }, setWorkingCase, ) + + setModalVisible(undefined) } const appealExpirationInfo = useMemo(() => { @@ -335,7 +341,7 @@ const BlueBoxWithDate: FC<Props> = (props) => { {defendant.isSentToPrisonAdmin ? ( <Button variant="text" - onClick={handleRevokeSendToPrisonAdmin} + onClick={() => setModalVisible('REVOKE_SEND_TO_PRISON_ADMIN')} size="small" colorScheme="destructive" > @@ -355,6 +361,20 @@ const BlueBoxWithDate: FC<Props> = (props) => { </Button> )} </Box> + {modalVisible === 'REVOKE_SEND_TO_PRISON_ADMIN' && ( + <Modal + title={formatMessage(strings.revokeSendToPrisonAdminModalTitle)} + text={formatMessage(strings.revokeSendToPrisonAdminModalText, { + courtCaseNumber: workingCase.courtCaseNumber, + defendant: defendant.name, + })} + onPrimaryButtonClick={handleRevokeSendToPrisonAdmin} + primaryButtonText={formatMessage(strings.revoke)} + isPrimaryButtonLoading={isUpdatingDefendant} + secondaryButtonText={formatMessage(core.cancel)} + onSecondaryButtonClick={() => setModalVisible(undefined)} + /> + )} </> ) } diff --git a/apps/judicial-system/web/src/components/FormProvider/case.graphql b/apps/judicial-system/web/src/components/FormProvider/case.graphql index 341c18c355fc..00c390081d4a 100644 --- a/apps/judicial-system/web/src/components/FormProvider/case.graphql +++ b/apps/judicial-system/web/src/components/FormProvider/case.graphql @@ -216,6 +216,7 @@ query Case($input: CaseQueryInput!) { lawsBroken incidentDescription legalArguments + indictmentCountSubtypes } requestDriversLicenseSuspension appealState diff --git a/apps/judicial-system/web/src/components/IndictmentCaseFilesList/IndictmentCaseFilesList.tsx b/apps/judicial-system/web/src/components/IndictmentCaseFilesList/IndictmentCaseFilesList.tsx index 9dd7bb9cb3bd..b9646bc28671 100644 --- a/apps/judicial-system/web/src/components/IndictmentCaseFilesList/IndictmentCaseFilesList.tsx +++ b/apps/judicial-system/web/src/components/IndictmentCaseFilesList/IndictmentCaseFilesList.tsx @@ -5,6 +5,7 @@ import { AnimatePresence } from 'framer-motion' import { Box, Text } from '@island.is/island-ui/core' import { formatDate } from '@island.is/judicial-system/formatters' import { + Feature, isCompletedCase, isDefenceUser, isDistrictCourtUser, @@ -15,6 +16,7 @@ import { isTrafficViolationCase, } from '@island.is/judicial-system/types' import { + FeatureContext, FileNotFoundModal, PdfButton, SectionHeading, @@ -71,12 +73,16 @@ const IndictmentCaseFilesList: FC<Props> = ({ }) => { const { formatMessage } = useIntl() const { user, limitedAccess } = useContext(UserContext) + const { features } = useContext(FeatureContext) const { onOpen, fileNotFound, dismissFileNotFound } = useFileList({ caseId: workingCase.id, connectedCaseParentId, }) - const showTrafficViolationCaseFiles = isTrafficViolationCase(workingCase) + const showTrafficViolationCaseFiles = + features.includes(Feature.MULTIPLE_INDICTMENT_SUBTYPES) || + isTrafficViolationCase(workingCase) + const showSubpoenaPdf = displayGeneratedPDFs && workingCase.defendants?.some( diff --git a/apps/judicial-system/web/src/components/ServiceAnnouncement/ServiceAnnouncement.tsx b/apps/judicial-system/web/src/components/ServiceAnnouncement/ServiceAnnouncement.tsx index 8dac19a8c448..f70fe17e3e35 100644 --- a/apps/judicial-system/web/src/components/ServiceAnnouncement/ServiceAnnouncement.tsx +++ b/apps/judicial-system/web/src/components/ServiceAnnouncement/ServiceAnnouncement.tsx @@ -1,4 +1,4 @@ -import { FC, useEffect, useState } from 'react' +import { FC } from 'react' import { IntlShape, MessageDescriptor, useIntl } from 'react-intl' import { AlertMessage, Box, LoadingDots, Text } from '@island.is/island-ui/core' diff --git a/apps/judicial-system/web/src/routes/Court/Indictments/Summary/Summary.strings.ts b/apps/judicial-system/web/src/routes/Court/Indictments/Summary/Summary.strings.ts index e5c50b59327c..c9c5bdf3cdc2 100644 --- a/apps/judicial-system/web/src/routes/Court/Indictments/Summary/Summary.strings.ts +++ b/apps/judicial-system/web/src/routes/Court/Indictments/Summary/Summary.strings.ts @@ -52,4 +52,10 @@ export const strings = defineMessages({ description: 'Notaður sem texti á aukahnapp í staðfestingarglugga um hvort eigi að ljúka máli.', }, + onlyAssignedJudgeCanComplete: { + id: 'judicial.system.core:indictments.summary.only_assigned_judge_can_complete', + defaultMessage: 'Einungis skráður dómari getur lokið málinu', + description: + 'Notaður sem texti í stað "áfram" takkans á samantektarskjá ákæru þegar "áfram" takkinn er falinn', + }, }) diff --git a/apps/judicial-system/web/src/routes/Court/Indictments/Summary/Summary.tsx b/apps/judicial-system/web/src/routes/Court/Indictments/Summary/Summary.tsx index 51d109336b5e..2f14057d02bf 100644 --- a/apps/judicial-system/web/src/routes/Court/Indictments/Summary/Summary.tsx +++ b/apps/judicial-system/web/src/routes/Court/Indictments/Summary/Summary.tsx @@ -19,6 +19,7 @@ import { PageTitle, RenderFiles, SectionHeading, + UserContext, } from '@island.is/judicial-system-web/src/components' import { Defendants, @@ -43,6 +44,7 @@ const Summary: FC = () => { useContext(FormContext) const { transitionCase, isTransitioningCase } = useCase() const [modalVisible, setModalVisible] = useState<'CONFIRM_INDICTMENT'>() + const { user } = useContext(UserContext) const { onOpen } = useFileList({ caseId: workingCase.id, @@ -90,6 +92,13 @@ const Summary: FC = () => { workingCase.indictmentRulingDecision, ) + const canUserCompleteCase = + (workingCase.indictmentRulingDecision !== + CaseIndictmentRulingDecision.RULING && + workingCase.indictmentRulingDecision !== + CaseIndictmentRulingDecision.DISMISSAL) || + workingCase.judge?.id === user?.id + return ( <PageLayout workingCase={workingCase} @@ -159,6 +168,12 @@ const Summary: FC = () => { nextButtonIcon="checkmark" nextButtonText={formatMessage(strings.nextButtonText)} onNextButtonClick={() => setModalVisible('CONFIRM_INDICTMENT')} + hideNextButton={!canUserCompleteCase} + infoBoxText={ + canUserCompleteCase + ? '' + : formatMessage(strings.onlyAssignedJudgeCanComplete) + } /> </FormContentContainer> {modalVisible === 'CONFIRM_INDICTMENT' && ( diff --git a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/CaseFiles/CaseFiles.tsx b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/CaseFiles/CaseFiles.tsx index 72f3b46677af..e10112fd8b4e 100644 --- a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/CaseFiles/CaseFiles.tsx +++ b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/CaseFiles/CaseFiles.tsx @@ -5,9 +5,13 @@ import router from 'next/router' import { Box, InputFileUpload } from '@island.is/island-ui/core' import { fileExtensionWhitelist } from '@island.is/island-ui/core/types' import * as constants from '@island.is/judicial-system/consts' -import { isTrafficViolationCase } from '@island.is/judicial-system/types' +import { + Feature, + isTrafficViolationCase, +} from '@island.is/judicial-system/types' import { titles } from '@island.is/judicial-system-web/messages' import { + FeatureContext, FormContentContainer, FormContext, FormFooter, @@ -29,6 +33,7 @@ import * as strings from './CaseFiles.strings' const CaseFiles = () => { const { workingCase, isLoadingWorkingCase, caseNotFound } = useContext(FormContext) + const { features } = useContext(FeatureContext) const { formatMessage } = useIntl() const { uploadFiles, @@ -41,7 +46,9 @@ const CaseFiles = () => { workingCase.id, ) - const isTrafficViolationCaseCheck = isTrafficViolationCase(workingCase) + const isTrafficViolationCaseCheck = + features.includes(Feature.MULTIPLE_INDICTMENT_SUBTYPES) || + isTrafficViolationCase(workingCase) const stepIsValid = (isTrafficViolationCaseCheck || diff --git a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Indictment/Indictment.tsx b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Indictment/Indictment.tsx index 135bb7a57348..e30f6bd64f9a 100644 --- a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Indictment/Indictment.tsx +++ b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Indictment/Indictment.tsx @@ -380,7 +380,7 @@ const Indictment = () => { onChange={handleUpdateIndictmentCount} setWorkingCase={setWorkingCase} updateIndictmentCountState={updateIndictmentCountState} - ></IndictmentCount> + /> </AnimatePresence> </Box> </motion.div> diff --git a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Indictment/IndictmentCount.css.ts b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Indictment/IndictmentCount.css.ts new file mode 100644 index 000000000000..7671970527a8 --- /dev/null +++ b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Indictment/IndictmentCount.css.ts @@ -0,0 +1,15 @@ +import { style } from '@vanilla-extract/css' + +import { theme } from '@island.is/island-ui/theme' + +export const indictmentSubtypesContainter = style({ + display: 'flex', + flexWrap: 'wrap', + gap: theme.spacing[1], + marginBottom: theme.spacing[2], +}) + +export const indictmentSubtypesItem = style({ + flex: `1 1 calc(50% - ${theme.spacing[1]}px)`, + whiteSpace: 'nowrap', +}) diff --git a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Indictment/IndictmentCount.strings.ts b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Indictment/IndictmentCount.strings.ts index dcea457a39c5..d6b7abe87aff 100644 --- a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Indictment/IndictmentCount.strings.ts +++ b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Indictment/IndictmentCount.strings.ts @@ -141,4 +141,9 @@ export const indictmentCount = defineMessages({ 'Telst háttsemi þessi varða við {articles} umferðarlaga nr. 77/2019.', description: 'Notaður sem sjálfgefinn texti í heimfærslu svæði.', }, + selectIndictmentSubtype: { + id: 'judicial.system.core:indictments_indictment.indictment_count.select_indictment_subtype', + defaultMessage: 'Veldu sakarefni', + description: 'Notaður sem titill fyrir "Veldu sakarefni" svæði.', + }, }) diff --git a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Indictment/IndictmentCount.tsx b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Indictment/IndictmentCount.tsx index 995eb38f5d4c..d1c964386e88 100644 --- a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Indictment/IndictmentCount.tsx +++ b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Indictment/IndictmentCount.tsx @@ -5,14 +5,20 @@ import { IntlShape, useIntl } from 'react-intl' import { Box, Button, + Checkbox, Icon, Input, Select, Tag, } from '@island.is/island-ui/core' -import { formatDate } from '@island.is/judicial-system/formatters' +import { + capitalize, + formatDate, + indictmentSubtypes, +} from '@island.is/judicial-system/formatters' import { CrimeScene, + IndictmentSubtype, offenseSubstances, Substance, SubstanceMap, @@ -20,6 +26,7 @@ import { import { BlueBox, IndictmentInfo, + SectionHeading, } from '@island.is/judicial-system-web/src/components' import { IndictmentCountOffense } from '@island.is/judicial-system-web/src/graphql/schema' import { @@ -39,6 +46,7 @@ import { Substances as SubstanceChoices } from './Substances/Substances' import { indictmentCount as strings } from './IndictmentCount.strings' import { indictmentCountEnum as enumStrings } from './IndictmentCountEnum.strings' import { indictmentCountSubstanceEnum as substanceStrings } from './IndictmentCountSubstanceEnum.strings' +import * as styles from './IndictmentCount.css' interface Props { indictmentCount: TIndictmentCount @@ -338,6 +346,10 @@ export const IndictmentCount: FC<Props> = ({ const [legalArgumentsErrorMessage, setLegalArgumentsErrorMessage] = useState<string>('') + const subtypes = indictmentCount.policeCaseNumber + ? workingCase.indictmentSubtypes[indictmentCount.policeCaseNumber] + : [] + const offensesOptions = useMemo( () => Object.values(IndictmentCountOffense).map((offense) => ({ @@ -400,6 +412,21 @@ export const IndictmentCount: FC<Props> = ({ }) } + const handleSubtypeChange = ( + subtype: IndictmentSubtype, + checked: boolean, + ) => { + const currentSubtypes = new Set( + indictmentCount.indictmentCountSubtypes ?? [], + ) + + checked ? currentSubtypes.add(subtype) : currentSubtypes.delete(subtype) + + handleIndictmentCountChanges({ + indictmentCountSubtypes: Array.from(currentSubtypes), + }) + } + return ( <BlueBox> {onDelete && ( @@ -449,6 +476,39 @@ export const IndictmentCount: FC<Props> = ({ crimeScenes={workingCase.crimeScenes} /> </Box> + {subtypes.length > 1 && ( + <Box marginBottom={2}> + <SectionHeading + title={formatMessage(strings.selectIndictmentSubtype)} + heading="h4" + /> + <div className={styles.indictmentSubtypesContainter}> + {subtypes.map((subtype: IndictmentSubtype) => ( + <div + className={styles.indictmentSubtypesItem} + key={`${subtype}-${indictmentCount.id}`} + > + <Checkbox + name={`${subtype}-${indictmentCount.id}`} + value={subtype} + label={capitalize(indictmentSubtypes[subtype])} + checked={ + indictmentCount.indictmentCountSubtypes?.includes( + subtype, + ) ?? false + } + onChange={(evt) => { + handleSubtypeChange(subtype, evt.target.checked) + }} + backgroundColor="white" + large + filled + /> + </div> + ))} + </div> + </Box> + )} <Box marginBottom={2}> <InputMask mask={[/[A-Z]/i, /[A-Z]/i, /[A-Z]|[0-9]/i, /[0-9]/, /[0-9]/]} diff --git a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Overview/Overview.tsx b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Overview/Overview.tsx index 2640b512c74d..a7bbf3b8cfa5 100644 --- a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Overview/Overview.tsx +++ b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Overview/Overview.tsx @@ -280,20 +280,20 @@ const Overview: FC = () => { <RadioButton large name="indictmentConfirmationRequest" - id="confirmIndictment" + id="denyIndictment" backgroundColor="white" - label={formatMessage(strings.confirmIndictment)} - checked={indictmentConfirmationDecision === 'confirm'} - onChange={() => setIndictmentConfirmationDecision('confirm')} + label={formatMessage(strings.denyIndictment)} + checked={indictmentConfirmationDecision === 'deny'} + onChange={() => setIndictmentConfirmationDecision('deny')} /> <RadioButton large name="indictmentConfirmationRequest" - id="denyIndictment" + id="confirmIndictment" backgroundColor="white" - label={formatMessage(strings.denyIndictment)} - checked={indictmentConfirmationDecision === 'deny'} - onChange={() => setIndictmentConfirmationDecision('deny')} + label={formatMessage(strings.confirmIndictment)} + checked={indictmentConfirmationDecision === 'confirm'} + onChange={() => setIndictmentConfirmationDecision('confirm')} /> </div> </BlueBox> diff --git a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Processing/Processing.tsx b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Processing/Processing.tsx index b46712f2e366..f85025b8c9b6 100644 --- a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Processing/Processing.tsx +++ b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Processing/Processing.tsx @@ -12,12 +12,14 @@ import { import * as constants from '@island.is/judicial-system/consts' import { AdvocateType, + Feature, isTrafficViolationCase, } from '@island.is/judicial-system/types' import { core, titles } from '@island.is/judicial-system-web/messages' import { BlueBox, CommentsInput, + FeatureContext, FormContentContainer, FormContext, FormFooter, @@ -63,6 +65,7 @@ const Processing: FC = () => { isCaseUpToDate, refreshCase, } = useContext(FormContext) + const { features } = useContext(FeatureContext) const { updateCase, transitionCase, setAndSendCaseToServer } = useCase() const { formatMessage } = useIntl() const { updateDefendant, updateDefendantState } = useDefendants() @@ -73,7 +76,9 @@ const Processing: FC = () => { deleteCivilClaimant, } = useCivilClaimants() const router = useRouter() - const isTrafficViolationCaseCheck = isTrafficViolationCase(workingCase) + const isTrafficViolationCaseCheck = + features.includes(Feature.MULTIPLE_INDICTMENT_SUBTYPES) || + isTrafficViolationCase(workingCase) const [civilClaimantNationalIdUpdate, setCivilClaimantNationalIdUpdate] = useState<{ nationalId: string | null; civilClaimantId: string }>() const [hasCivilClaimantChoice, setHasCivilClaimantChoice] = diff --git a/apps/judicial-system/web/src/routes/PublicProsecutor/Indictments/SendToPrisonAdmin/SendToPrisonAdmin.strings.ts b/apps/judicial-system/web/src/routes/PublicProsecutor/Indictments/SendToPrisonAdmin/SendToPrisonAdmin.strings.ts index 6a513f738833..44201a0341f3 100644 --- a/apps/judicial-system/web/src/routes/PublicProsecutor/Indictments/SendToPrisonAdmin/SendToPrisonAdmin.strings.ts +++ b/apps/judicial-system/web/src/routes/PublicProsecutor/Indictments/SendToPrisonAdmin/SendToPrisonAdmin.strings.ts @@ -29,7 +29,7 @@ export const strings = defineMessages({ 'Notaður sem titill á tilkynningarglugga um að senda til fullnustu.', }, modalText: { - id: 'judicial.system.core:send_to_prison_admin.modal_text', + id: 'judicial.system.core:send_to_prison_admin.modal_text_v1', defaultMessage: 'Mál {courtCaseNumber} verður sent til Fangelsismálastofnunar til fullnustu.\nÁkærði: {defendant}.', description: diff --git a/apps/judicial-system/web/src/utils/hooks/useCaseList/index.tsx b/apps/judicial-system/web/src/utils/hooks/useCaseList/index.tsx index d184a5703fb9..6629594f8d5f 100644 --- a/apps/judicial-system/web/src/utils/hooks/useCaseList/index.tsx +++ b/apps/judicial-system/web/src/utils/hooks/useCaseList/index.tsx @@ -10,6 +10,7 @@ import { DEFENDER_ROUTE, } from '@island.is/judicial-system/consts' import { + Feature, isCompletedCase, isCourtOfAppealsUser, isDefenceUser, @@ -23,6 +24,7 @@ import { } from '@island.is/judicial-system/types' import { errors } from '@island.is/judicial-system-web/messages' import { + FeatureContext, FormContext, UserContext, } from '@island.is/judicial-system-web/src/components' @@ -40,6 +42,7 @@ const useCaseList = () => { >([null, false]) const { user, limitedAccess } = useContext(UserContext) const { getCase } = useContext(FormContext) + const { features } = useContext(FeatureContext) const { formatMessage } = useIntl() const { isTransitioningCase, isSendingNotification } = useCase() const router = useRouter() @@ -47,7 +50,9 @@ const useCaseList = () => { const openCase = useCallback( (caseToOpen: Case, openCaseInNewTab?: boolean) => { let routeTo = null - const isTrafficViolation = isTrafficViolationCase(caseToOpen) + const isTrafficViolation = + features.includes(Feature.MULTIPLE_INDICTMENT_SUBTYPES) || + isTrafficViolationCase(caseToOpen) if (isDefenceUser(user)) { if (isRequestCase(caseToOpen.type)) { @@ -137,7 +142,7 @@ const useCaseList = () => { router.push(`${routeTo}/${caseToOpen.id}`) } }, - [router, user], + [router, user, features], ) const handleOpenCase = useCallback( diff --git a/apps/judicial-system/web/src/utils/hooks/useIndictmentCounts/updateIndictmentCount.graphql b/apps/judicial-system/web/src/utils/hooks/useIndictmentCounts/updateIndictmentCount.graphql index f87f2173bf6f..f8aca1a9268d 100644 --- a/apps/judicial-system/web/src/utils/hooks/useIndictmentCounts/updateIndictmentCount.graphql +++ b/apps/judicial-system/web/src/utils/hooks/useIndictmentCounts/updateIndictmentCount.graphql @@ -11,5 +11,6 @@ mutation UpdateIndictmentCount($input: UpdateIndictmentCountInput!) { lawsBroken incidentDescription legalArguments + indictmentCountSubtypes } } diff --git a/apps/judicial-system/web/src/utils/hooks/useSections/index.ts b/apps/judicial-system/web/src/utils/hooks/useSections/index.ts index 5608d706ab7e..c0bc8bfbb355 100644 --- a/apps/judicial-system/web/src/utils/hooks/useSections/index.ts +++ b/apps/judicial-system/web/src/utils/hooks/useSections/index.ts @@ -1,3 +1,4 @@ +import { useContext } from 'react' import { useIntl } from 'react-intl' import { useRouter } from 'next/router' @@ -7,6 +8,7 @@ import { getAppealResultTextByValue, } from '@island.is/judicial-system/formatters' import { + Feature, isCompletedCase, isCourtOfAppealsUser, isDefenceUser, @@ -18,6 +20,7 @@ import { isTrafficViolationCase, } from '@island.is/judicial-system/types' import { core, sections } from '@island.is/judicial-system-web/messages' +import { FeatureContext } from '@island.is/judicial-system-web/src/components' import { RouteSection } from '@island.is/judicial-system-web/src/components/PageLayout/PageLayout' import { formatCaseResult } from '@island.is/judicial-system-web/src/components/PageLayout/utils' import { @@ -60,6 +63,7 @@ const useSections = ( ) => { const { formatMessage } = useIntl() const router = useRouter() + const { features } = useContext(FeatureContext) const isActive = (pathname: string) => router.pathname.replace(/\/\[\w+\]/g, '') === pathname @@ -402,7 +406,9 @@ const useSections = ( state === CaseState.RECEIVED || state === CaseState.WAITING_FOR_CANCELLATION || router.pathname === `${constants.INDICTMENTS_ADD_FILES_ROUTE}/[id]` - const isTrafficViolation = isTrafficViolationCase(workingCase) + const isTrafficViolation = + features.includes(Feature.MULTIPLE_INDICTMENT_SUBTYPES) || + isTrafficViolationCase(workingCase) return { name: formatMessage(sections.indictmentCaseProsecutorSection.title), diff --git a/apps/native/app/.babelrc.js b/apps/native/app/.babelrc.js new file mode 100644 index 000000000000..01d571a26229 --- /dev/null +++ b/apps/native/app/.babelrc.js @@ -0,0 +1,14 @@ +module.exports = function (api) { + api.cache(true) + + return { + presets: [ + ['module:@react-native/babel-preset', { useTransformReactJSX: true }], + ], + plugins: [ + // react-native-reanimated/plugin has to be listed last. + // Reason: In short, the Reanimated babel plugin automatically converts special JavaScript functions (called worklets) to allow them to be passed and run on the UI thread. + 'react-native-reanimated/plugin', + ], + } +} diff --git a/apps/native/app/.bundle/config b/apps/native/app/.bundle/config index 848943bb5274..d137d242ed74 100644 --- a/apps/native/app/.bundle/config +++ b/apps/native/app/.bundle/config @@ -1,2 +1,2 @@ BUNDLE_PATH: "vendor/bundle" -BUNDLE_FORCE_RUBY_PLATFORM: 1 +BUNDLE_FORCE_RUBY_PLATFORM: 1 \ No newline at end of file diff --git a/apps/native/app/.eslintrc.json b/apps/native/app/.eslintrc.json index d5e992022c7c..d6ea918430f5 100644 --- a/apps/native/app/.eslintrc.json +++ b/apps/native/app/.eslintrc.json @@ -6,7 +6,6 @@ "**/*.stories.tsx", "lib/pdf417/*-min.js" ], - "rules": {}, "overrides": [ { "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], @@ -20,7 +19,13 @@ "func-style": "off" } }, - { "files": ["*.ts", "*.tsx"], "rules": {} }, - { "files": ["*.js", "*.jsx"], "rules": {} } + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } ] } diff --git a/apps/native/app/.gitignore b/apps/native/app/.gitignore deleted file mode 100644 index edf1e8ff9ab0..000000000000 --- a/apps/native/app/.gitignore +++ /dev/null @@ -1,81 +0,0 @@ -# OSX -# -.DS_Store - -# Xcode -# -build/ -*.pbxuser -!default.pbxuser -*.mode1v3 -!default.mode1v3 -*.mode2v3 -!default.mode2v3 -*.perspectivev3 -!default.perspectivev3 -xcuserdata -*.xccheckout -*.moved-aside -DerivedData -*.hmap -*.ipa -*.dSYM.zip -*.xcuserstate -**/.xcode.env.local -ios/*.cer -ios/*.certSigningRequest -ios/*.mobileprovision -ios/*.p12 - -# Android/IntelliJ -# -build/ -.idea -.gradle -local.properties -*.iml -*.hprof -.cxx/ -*.keystore -!debug.keystore -google-services.json -service-account.json - -# node.js -# -node_modules/ -npm-debug.log -yarn-error.log - -# fastlane -# -# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the -# screenshots whenever they are needed. -# For more information about the recommended setup visit: -# https://docs.fastlane.tools/best-practices/source-control/ - -**/fastlane/report.xml -**/fastlane/Preview.html -**/fastlane/screenshots -**/fastlane/test_output - -# Bundle artifact -*.jsbundle - -# Ruby / CocoaPods -**/Pods/ -/vendor/bundle/ - -# Temporary files created by Metro to check the health of the file watcher -.metro-health-check* - -# testing -/coverage - -# Yarn -.yarn/* -!.yarn/patches -!.yarn/plugins -!.yarn/releases -!.yarn/sdks -!.yarn/versions diff --git a/apps/native/app/Gemfile b/apps/native/app/Gemfile index 8d72c37a80c3..ec21e32c9a69 100644 --- a/apps/native/app/Gemfile +++ b/apps/native/app/Gemfile @@ -6,4 +6,4 @@ ruby ">= 2.6.10" # Cocoapods 1.15 introduced a bug which break the build. We will remove the upper # bound in the template on Cocoapods with next React Native release. gem 'cocoapods', '>= 1.13', '< 1.15' -gem 'activesupport', '>= 6.1.7.5', '< 7.1.0' +gem 'activesupport', '>= 6.1.7.5', '< 7.1.0' \ No newline at end of file diff --git a/apps/native/app/android/app/build.gradle b/apps/native/app/android/app/build.gradle index 98b5f346f132..a32464e07a8f 100644 --- a/apps/native/app/android/app/build.gradle +++ b/apps/native/app/android/app/build.gradle @@ -103,7 +103,7 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion versionCode getMyVersionCode(143) - versionName "1.4.7" + versionName "1.4.8" manifestPlaceholders = [ appAuthRedirectScheme: "is.island.app" // project.config.get("BUNDLE_ID_ANDROID") ] @@ -171,4 +171,4 @@ dependencies { } } -apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project) +apply from: file("../../../../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project) diff --git a/apps/native/app/android/settings.gradle b/apps/native/app/android/settings.gradle index 7ed793eca85f..12979ce6cda2 100644 --- a/apps/native/app/android/settings.gradle +++ b/apps/native/app/android/settings.gradle @@ -1,10 +1,10 @@ rootProject.name = 'IslandApp' -apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); +apply from: file("../../../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings) include ':app', ':react-native-code-push' -project(':react-native-code-push').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-code-push/android/app') +project(':react-native-code-push').projectDir = new File(rootProject.projectDir, '../../../../node_modules/react-native-code-push/android/app') include ':react-native-clipboard' project(':react-native-clipboard').projectDir = new File(rootProject.projectDir, '../../node_modules/@react-native-clipboard/clipboard/android') diff --git a/apps/native/app/babel.config.js b/apps/native/app/babel.config.js deleted file mode 100644 index 24924e0cb1b1..000000000000 --- a/apps/native/app/babel.config.js +++ /dev/null @@ -1,17 +0,0 @@ -module.exports = { - presets: ['module:@react-native/babel-preset'], - plugins: [ - [ - 'module-resolver', - { - alias: { - '@ui': './src/ui', - '@island.is/application/types': '../../../libs/application/types/src', - }, - }, - ], - // react-native-reanimated/plugin has to be listed last. - // Reason: In short, the Reanimated babel plugin automatically converts special JavaScript functions (called worklets) to allow them to be passed and run on the UI thread. - 'react-native-reanimated/plugin', - ], -} diff --git a/apps/native/app/ios/IslandApp.xcodeproj/project.pbxproj b/apps/native/app/ios/IslandApp.xcodeproj/project.pbxproj index a6c26ffd0c01..4bcd227127d6 100644 --- a/apps/native/app/ios/IslandApp.xcodeproj/project.pbxproj +++ b/apps/native/app/ios/IslandApp.xcodeproj/project.pbxproj @@ -301,7 +301,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "set -e\n\nWITH_ENVIRONMENT=\"../node_modules/react-native/scripts/xcode/with-environment.sh\"\nREACT_NATIVE_XCODE=\"../node_modules/react-native/scripts/react-native-xcode.sh\"\n\n/bin/sh -c \"$WITH_ENVIRONMENT $REACT_NATIVE_XCODE\"\n"; + shellScript = "set -e\n\nWITH_ENVIRONMENT=\"../../../../node_modules/react-native/scripts/xcode/with-environment.sh\"\nREACT_NATIVE_XCODE=\"../../../../node_modules/react-native/scripts/react-native-xcode.sh\"\n\n/bin/sh -c \"$WITH_ENVIRONMENT $REACT_NATIVE_XCODE\"\n"; }; 1D5D14183630A5603D9A6D3E /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; @@ -425,7 +425,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "export RCT_METRO_PORT=\"${RCT_METRO_PORT:=8081}\"\necho \"export RCT_METRO_PORT=${RCT_METRO_PORT}\" > \"${SRCROOT}/../node_modules/react-native/scripts/.packager.env\"\nif [ -z \"${RCT_NO_LAUNCH_PACKAGER+xxx}\" ] ; then\n if nc -w 5 -z localhost ${RCT_METRO_PORT} ; then\n if ! curl -s \"http://localhost:${RCT_METRO_PORT}/status\" | grep -q \"packager-status:running\" ; then\n echo \"Port ${RCT_METRO_PORT} already in use, packager is either not running or not running correctly\"\n exit 2\n fi\n else\n open \"$SRCROOT/../node_modules/react-native/scripts/launchPackager.command\" || echo \"Can't start packager automatically\"\n fi\nfi\n"; + shellScript = "export RCT_METRO_PORT=\"${RCT_METRO_PORT:=8081}\"\necho \"export RCT_METRO_PORT=${RCT_METRO_PORT}\" > \"${SRCROOT}/../../../../node_modules/react-native/scripts/.packager.env\"\nif [ -z \"${RCT_NO_LAUNCH_PACKAGER+xxx}\" ] ; then\n if nc -w 5 -z localhost ${RCT_METRO_PORT} ; then\n if ! curl -s \"http://localhost:${RCT_METRO_PORT}/status\" | grep -q \"packager-status:running\" ; then\n echo \"Port ${RCT_METRO_PORT} already in use, packager is either not running or not running correctly\"\n exit 2\n fi\n else\n open \"$SRCROOT/../../../../node_modules/react-native/scripts/launchPackager.command\" || echo \"Can't start packager automatically\"\n fi\nfi\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ @@ -554,7 +554,7 @@ "$(inherited)", " ", ); - REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; + REACT_NATIVE_PATH = "${PODS_ROOT}/../../../../../node_modules/react-native"; SDKROOT = iphoneos; USE_HERMES = true; VALIDATE_PRODUCT = YES; @@ -629,7 +629,7 @@ "$(inherited)", " ", ); - REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; + REACT_NATIVE_PATH = "${PODS_ROOT}/../../../../../node_modules/react-native"; SDKROOT = iphoneos; USE_HERMES = true; VALIDATE_PRODUCT = YES; @@ -752,7 +752,7 @@ "$(inherited)", " ", ); - REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; + REACT_NATIVE_PATH = "${PODS_ROOT}/../../../../../node_modules/react-native"; SDKROOT = iphoneos; USE_HERMES = true; }; diff --git a/apps/native/app/ios/IslandApp/Info.plist b/apps/native/app/ios/IslandApp/Info.plist index 6d9a1a7cf5ac..42e798076119 100644 --- a/apps/native/app/ios/IslandApp/Info.plist +++ b/apps/native/app/ios/IslandApp/Info.plist @@ -17,7 +17,7 @@ <key>CFBundlePackageType</key> <string>APPL</string> <key>CFBundleShortVersionString</key> - <string>1.4.7</string> + <string>1.4.8</string> <key>CFBundleSignature</key> <string>????</string> <key>CFBundleURLTypes</key> diff --git a/apps/native/app/ios/Podfile.lock b/apps/native/app/ios/Podfile.lock index 3d5d52150dc7..4a489728f683 100644 --- a/apps/native/app/ios/Podfile.lock +++ b/apps/native/app/ios/Podfile.lock @@ -1,9 +1,9 @@ PODS: - - AppAuth (1.7.5): - - AppAuth/Core (= 1.7.5) - - AppAuth/ExternalUserAgent (= 1.7.5) - - AppAuth/Core (1.7.5) - - AppAuth/ExternalUserAgent (1.7.5): + - AppAuth (1.7.6): + - AppAuth/Core (= 1.7.6) + - AppAuth/ExternalUserAgent (= 1.7.6) + - AppAuth/Core (1.7.6) + - AppAuth/ExternalUserAgent (1.7.6): - AppAuth/Core - Base64 (1.1.2) - boost (1.83.0) @@ -141,7 +141,7 @@ PODS: - GoogleUtilities/ISASwizzler (~> 7.8) - GoogleUtilities/MethodSwizzler (~> 7.8) - nanopb (< 2.30910.0, >= 2.30908.0) - - FirebaseRemoteConfig (10.27.0): + - FirebaseRemoteConfig (10.29.0): - FirebaseABTesting (~> 10.0) - FirebaseCore (~> 10.0) - FirebaseInstallations (~> 10.0) @@ -149,8 +149,8 @@ PODS: - FirebaseSharedSwift (~> 10.0) - GoogleUtilities/Environment (~> 7.8) - "GoogleUtilities/NSData+zlib (~> 7.8)" - - FirebaseRemoteConfigInterop (10.27.0) - - FirebaseSharedSwift (10.27.0) + - FirebaseRemoteConfigInterop (10.29.0) + - FirebaseSharedSwift (10.29.0) - fmt (9.1.0) - glog (0.3.5) - GoogleAppMeasurement (10.3.0): @@ -1567,113 +1567,113 @@ PODS: - Yoga (0.0.0) DEPENDENCIES: - - boost (from `../node_modules/react-native/third-party-podspecs/boost.podspec`) - - CodePush (from `../node_modules/react-native-code-push`) - - "DatadogSDKReactNative (from `../node_modules/@datadog/mobile-react-native`)" - - DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`) - - EXApplication (from `../node_modules/expo-application/ios`) - - EXConstants (from `../node_modules/expo-constants/ios`) - - EXNotifications (from `../node_modules/expo-notifications/ios`) - - Expo (from `../node_modules/expo`) - - ExpoAsset (from `../node_modules/expo-asset/ios`) - - ExpoFileSystem (from `../node_modules/expo-file-system/ios`) - - ExpoFont (from `../node_modules/expo-font/ios`) - - ExpoHaptics (from `../node_modules/expo-haptics/ios`) - - ExpoKeepAwake (from `../node_modules/expo-keep-awake/ios`) - - ExpoLocalAuthentication (from `../node_modules/expo-local-authentication/ios`) - - ExpoModulesCore (from `../node_modules/expo-modules-core`) - - FBLazyVector (from `../node_modules/react-native/Libraries/FBLazyVector`) + - boost (from `../../../../node_modules/react-native/third-party-podspecs/boost.podspec`) + - CodePush (from `../../../../node_modules/react-native-code-push`) + - "DatadogSDKReactNative (from `../../../../node_modules/@datadog/mobile-react-native`)" + - DoubleConversion (from `../../../../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`) + - EXApplication (from `../../../../node_modules/expo-application/ios`) + - EXConstants (from `../../../../node_modules/expo-constants/ios`) + - EXNotifications (from `../../../../node_modules/expo-notifications/ios`) + - Expo (from `../../../../node_modules/expo`) + - ExpoAsset (from `../../../../node_modules/expo-asset/ios`) + - ExpoFileSystem (from `../../../../node_modules/expo-file-system/ios`) + - ExpoFont (from `../../../../node_modules/expo-font/ios`) + - ExpoHaptics (from `../../../../node_modules/expo-haptics/ios`) + - ExpoKeepAwake (from `../../../../node_modules/expo-keep-awake/ios`) + - ExpoLocalAuthentication (from `../../../../node_modules/expo-local-authentication/ios`) + - ExpoModulesCore (from `../../../../node_modules/expo-modules-core`) + - FBLazyVector (from `../../../../node_modules/react-native/Libraries/FBLazyVector`) - Firebase - FirebaseABTesting - FirebaseCore - FirebaseCoreInternal - FirebaseInstallations - - fmt (from `../node_modules/react-native/third-party-podspecs/fmt.podspec`) - - glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`) + - fmt (from `../../../../node_modules/react-native/third-party-podspecs/fmt.podspec`) + - glog (from `../../../../node_modules/react-native/third-party-podspecs/glog.podspec`) - GoogleUtilities - - hermes-engine (from `../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec`) - - Interactable (from `../node_modules/react-native-interactable`) - - RCT-Folly (from `../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`) - - RCT-Folly/Fabric (from `../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`) - - RCTDeprecation (from `../node_modules/react-native/ReactApple/Libraries/RCTFoundation/RCTDeprecation`) - - RCTRequired (from `../node_modules/react-native/Libraries/Required`) - - RCTTypeSafety (from `../node_modules/react-native/Libraries/TypeSafety`) - - React (from `../node_modules/react-native/`) - - React-callinvoker (from `../node_modules/react-native/ReactCommon/callinvoker`) + - hermes-engine (from `../../../../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec`) + - Interactable (from `../../../../node_modules/react-native-interactable`) + - RCT-Folly (from `../../../../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`) + - RCT-Folly/Fabric (from `../../../../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`) + - RCTDeprecation (from `../../../../node_modules/react-native/ReactApple/Libraries/RCTFoundation/RCTDeprecation`) + - RCTRequired (from `../../../../node_modules/react-native/Libraries/Required`) + - RCTTypeSafety (from `../../../../node_modules/react-native/Libraries/TypeSafety`) + - React (from `../../../../node_modules/react-native/`) + - React-callinvoker (from `../../../../node_modules/react-native/ReactCommon/callinvoker`) - React-Codegen (from `build/generated/ios`) - - React-Core (from `../node_modules/react-native/`) - - React-Core/RCTWebSocket (from `../node_modules/react-native/`) - - React-CoreModules (from `../node_modules/react-native/React/CoreModules`) - - React-cxxreact (from `../node_modules/react-native/ReactCommon/cxxreact`) - - React-debug (from `../node_modules/react-native/ReactCommon/react/debug`) - - React-Fabric (from `../node_modules/react-native/ReactCommon`) - - React-FabricImage (from `../node_modules/react-native/ReactCommon`) - - React-featureflags (from `../node_modules/react-native/ReactCommon/react/featureflags`) - - React-graphics (from `../node_modules/react-native/ReactCommon/react/renderer/graphics`) - - React-hermes (from `../node_modules/react-native/ReactCommon/hermes`) - - React-ImageManager (from `../node_modules/react-native/ReactCommon/react/renderer/imagemanager/platform/ios`) - - React-jserrorhandler (from `../node_modules/react-native/ReactCommon/jserrorhandler`) - - React-jsi (from `../node_modules/react-native/ReactCommon/jsi`) - - React-jsiexecutor (from `../node_modules/react-native/ReactCommon/jsiexecutor`) - - React-jsinspector (from `../node_modules/react-native/ReactCommon/jsinspector-modern`) - - React-jsitracing (from `../node_modules/react-native/ReactCommon/hermes/executor/`) - - React-logger (from `../node_modules/react-native/ReactCommon/logger`) - - React-Mapbuffer (from `../node_modules/react-native/ReactCommon`) - - react-native-app-auth (from `../node_modules/react-native-app-auth`) - - react-native-blob-util (from `../node_modules/react-native-blob-util`) - - "react-native-cookies (from `../node_modules/@react-native-cookies/cookies`)" - - react-native-date-picker (from `../node_modules/react-native-date-picker`) - - react-native-mmkv-storage (from `../node_modules/react-native-mmkv-storage`) - - "react-native-netinfo (from `../node_modules/@react-native-community/netinfo`)" - - react-native-passkey (from `../node_modules/react-native-passkey`) - - react-native-passkit-wallet (from `../node_modules/react-native-passkit-wallet`) - - react-native-pdf (from `../node_modules/react-native-pdf`) - - "react-native-progress-bar-android (from `../node_modules/@react-native-community/progress-bar-android`)" - - "react-native-progress-view (from `../node_modules/@react-native-community/progress-view`)" - - react-native-quick-base64 (from `../node_modules/react-native-quick-base64`) - - react-native-spotlight-search (from `../node_modules/react-native-spotlight-search`) - - react-native-webview (from `../node_modules/react-native-webview`) - - React-nativeconfig (from `../node_modules/react-native/ReactCommon`) - - React-NativeModulesApple (from `../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios`) - - React-perflogger (from `../node_modules/react-native/ReactCommon/reactperflogger`) - - React-RCTActionSheet (from `../node_modules/react-native/Libraries/ActionSheetIOS`) - - React-RCTAnimation (from `../node_modules/react-native/Libraries/NativeAnimation`) - - React-RCTAppDelegate (from `../node_modules/react-native/Libraries/AppDelegate`) - - React-RCTBlob (from `../node_modules/react-native/Libraries/Blob`) - - React-RCTFabric (from `../node_modules/react-native/React`) - - React-RCTImage (from `../node_modules/react-native/Libraries/Image`) - - React-RCTLinking (from `../node_modules/react-native/Libraries/LinkingIOS`) - - React-RCTNetwork (from `../node_modules/react-native/Libraries/Network`) - - React-RCTSettings (from `../node_modules/react-native/Libraries/Settings`) - - React-RCTText (from `../node_modules/react-native/Libraries/Text`) - - React-RCTVibration (from `../node_modules/react-native/Libraries/Vibration`) - - React-rendererdebug (from `../node_modules/react-native/ReactCommon/react/renderer/debug`) - - React-rncore (from `../node_modules/react-native/ReactCommon`) - - React-RuntimeApple (from `../node_modules/react-native/ReactCommon/react/runtime/platform/ios`) - - React-RuntimeCore (from `../node_modules/react-native/ReactCommon/react/runtime`) - - React-runtimeexecutor (from `../node_modules/react-native/ReactCommon/runtimeexecutor`) - - React-RuntimeHermes (from `../node_modules/react-native/ReactCommon/react/runtime`) - - React-runtimescheduler (from `../node_modules/react-native/ReactCommon/react/renderer/runtimescheduler`) - - React-utils (from `../node_modules/react-native/ReactCommon/react/utils`) - - ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`) - - ReactNativeKeyboardManager (from `../node_modules/react-native-keyboard-manager`) - - ReactNativeNavigation (from `../node_modules/react-native-navigation`) - - "RNCAsyncStorage (from `../node_modules/@react-native-community/async-storage`)" - - "RNCClipboard (from `../node_modules/@react-native-clipboard/clipboard`)" - - RNDeviceInfo (from `../node_modules/react-native-device-info`) - - "RNFBAnalytics (from `../node_modules/@react-native-firebase/analytics`)" - - "RNFBApp (from `../node_modules/@react-native-firebase/app`)" - - "RNFBMessaging (from `../node_modules/@react-native-firebase/messaging`)" - - "RNFBPerf (from `../node_modules/@react-native-firebase/perf`)" - - RNGestureHandler (from `../node_modules/react-native-gesture-handler`) - - RNInAppBrowser (from `../node_modules/react-native-inappbrowser-reborn`) - - RNKeychain (from `../node_modules/react-native-keychain`) - - RNQuickAction (from `../node_modules/react-native-quick-actions`) + - React-Core (from `../../../../node_modules/react-native/`) + - React-Core/RCTWebSocket (from `../../../../node_modules/react-native/`) + - React-CoreModules (from `../../../../node_modules/react-native/React/CoreModules`) + - React-cxxreact (from `../../../../node_modules/react-native/ReactCommon/cxxreact`) + - React-debug (from `../../../../node_modules/react-native/ReactCommon/react/debug`) + - React-Fabric (from `../../../../node_modules/react-native/ReactCommon`) + - React-FabricImage (from `../../../../node_modules/react-native/ReactCommon`) + - React-featureflags (from `../../../../node_modules/react-native/ReactCommon/react/featureflags`) + - React-graphics (from `../../../../node_modules/react-native/ReactCommon/react/renderer/graphics`) + - React-hermes (from `../../../../node_modules/react-native/ReactCommon/hermes`) + - React-ImageManager (from `../../../../node_modules/react-native/ReactCommon/react/renderer/imagemanager/platform/ios`) + - React-jserrorhandler (from `../../../../node_modules/react-native/ReactCommon/jserrorhandler`) + - React-jsi (from `../../../../node_modules/react-native/ReactCommon/jsi`) + - React-jsiexecutor (from `../../../../node_modules/react-native/ReactCommon/jsiexecutor`) + - React-jsinspector (from `../../../../node_modules/react-native/ReactCommon/jsinspector-modern`) + - React-jsitracing (from `../../../../node_modules/react-native/ReactCommon/hermes/executor/`) + - React-logger (from `../../../../node_modules/react-native/ReactCommon/logger`) + - React-Mapbuffer (from `../../../../node_modules/react-native/ReactCommon`) + - react-native-app-auth (from `../../../../node_modules/react-native-app-auth`) + - react-native-blob-util (from `../../../../node_modules/react-native-blob-util`) + - "react-native-cookies (from `../../../../node_modules/@react-native-cookies/cookies`)" + - react-native-date-picker (from `../../../../node_modules/react-native-date-picker`) + - react-native-mmkv-storage (from `../../../../node_modules/react-native-mmkv-storage`) + - "react-native-netinfo (from `../../../../node_modules/@react-native-community/netinfo`)" + - react-native-passkey (from `../../../../node_modules/react-native-passkey`) + - react-native-passkit-wallet (from `../../../../node_modules/react-native-passkit-wallet`) + - react-native-pdf (from `../../../../node_modules/react-native-pdf`) + - "react-native-progress-bar-android (from `../../../../node_modules/@react-native-community/progress-bar-android`)" + - "react-native-progress-view (from `../../../../node_modules/@react-native-community/progress-view`)" + - react-native-quick-base64 (from `../../../../node_modules/react-native-quick-base64`) + - react-native-spotlight-search (from `../../../../node_modules/react-native-spotlight-search`) + - react-native-webview (from `../../../../node_modules/react-native-webview`) + - React-nativeconfig (from `../../../../node_modules/react-native/ReactCommon`) + - React-NativeModulesApple (from `../../../../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios`) + - React-perflogger (from `../../../../node_modules/react-native/ReactCommon/reactperflogger`) + - React-RCTActionSheet (from `../../../../node_modules/react-native/Libraries/ActionSheetIOS`) + - React-RCTAnimation (from `../../../../node_modules/react-native/Libraries/NativeAnimation`) + - React-RCTAppDelegate (from `../../../../node_modules/react-native/Libraries/AppDelegate`) + - React-RCTBlob (from `../../../../node_modules/react-native/Libraries/Blob`) + - React-RCTFabric (from `../../../../node_modules/react-native/React`) + - React-RCTImage (from `../../../../node_modules/react-native/Libraries/Image`) + - React-RCTLinking (from `../../../../node_modules/react-native/Libraries/LinkingIOS`) + - React-RCTNetwork (from `../../../../node_modules/react-native/Libraries/Network`) + - React-RCTSettings (from `../../../../node_modules/react-native/Libraries/Settings`) + - React-RCTText (from `../../../../node_modules/react-native/Libraries/Text`) + - React-RCTVibration (from `../../../../node_modules/react-native/Libraries/Vibration`) + - React-rendererdebug (from `../../../../node_modules/react-native/ReactCommon/react/renderer/debug`) + - React-rncore (from `../../../../node_modules/react-native/ReactCommon`) + - React-RuntimeApple (from `../../../../node_modules/react-native/ReactCommon/react/runtime/platform/ios`) + - React-RuntimeCore (from `../../../../node_modules/react-native/ReactCommon/react/runtime`) + - React-runtimeexecutor (from `../../../../node_modules/react-native/ReactCommon/runtimeexecutor`) + - React-RuntimeHermes (from `../../../../node_modules/react-native/ReactCommon/react/runtime`) + - React-runtimescheduler (from `../../../../node_modules/react-native/ReactCommon/react/renderer/runtimescheduler`) + - React-utils (from `../../../../node_modules/react-native/ReactCommon/react/utils`) + - ReactCommon/turbomodule/core (from `../../../../node_modules/react-native/ReactCommon`) + - ReactNativeKeyboardManager (from `../../../../node_modules/react-native-keyboard-manager`) + - ReactNativeNavigation (from `../../../../node_modules/react-native-navigation`) + - "RNCAsyncStorage (from `../../../../node_modules/@react-native-community/async-storage`)" + - "RNCClipboard (from `../../../../node_modules/@react-native-clipboard/clipboard`)" + - RNDeviceInfo (from `../../../../node_modules/react-native-device-info`) + - "RNFBAnalytics (from `../../../../node_modules/@react-native-firebase/analytics`)" + - "RNFBApp (from `../../../../node_modules/@react-native-firebase/app`)" + - "RNFBMessaging (from `../../../../node_modules/@react-native-firebase/messaging`)" + - "RNFBPerf (from `../../../../node_modules/@react-native-firebase/perf`)" + - RNGestureHandler (from `../../../../node_modules/react-native-gesture-handler`) + - RNInAppBrowser (from `../../../../node_modules/react-native-inappbrowser-reborn`) + - RNKeychain (from `../../../../node_modules/react-native-keychain`) + - RNQuickAction (from `../../../../node_modules/react-native-quick-actions`) - RNReanimated (from `../node_modules/react-native-reanimated`) - - RNShare (from `../node_modules/react-native-share`) - - RNSVG (from `../node_modules/react-native-svg`) + - RNShare (from `../../../../node_modules/react-native-share`) + - RNSVG (from `../../../../node_modules/react-native-svg`) - VisionCamera (from `../node_modules/react-native-vision-camera`) - - Yoga (from `../node_modules/react-native/ReactCommon/yoga`) + - Yoga (from `../../../../node_modules/react-native/ReactCommon/yoga`) SPEC REPOS: trunk: @@ -1709,207 +1709,207 @@ SPEC REPOS: EXTERNAL SOURCES: boost: - :podspec: "../node_modules/react-native/third-party-podspecs/boost.podspec" + :podspec: "../../../../node_modules/react-native/third-party-podspecs/boost.podspec" CodePush: - :path: "../node_modules/react-native-code-push" + :path: "../../../../node_modules/react-native-code-push" DatadogSDKReactNative: - :path: "../node_modules/@datadog/mobile-react-native" + :path: "../../../../node_modules/@datadog/mobile-react-native" DoubleConversion: - :podspec: "../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec" + :podspec: "../../../../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec" EXApplication: - :path: "../node_modules/expo-application/ios" + :path: "../../../../node_modules/expo-application/ios" EXConstants: - :path: "../node_modules/expo-constants/ios" + :path: "../../../../node_modules/expo-constants/ios" EXNotifications: - :path: "../node_modules/expo-notifications/ios" + :path: "../../../../node_modules/expo-notifications/ios" Expo: - :path: "../node_modules/expo" + :path: "../../../../node_modules/expo" ExpoAsset: - :path: "../node_modules/expo-asset/ios" + :path: "../../../../node_modules/expo-asset/ios" ExpoFileSystem: - :path: "../node_modules/expo-file-system/ios" + :path: "../../../../node_modules/expo-file-system/ios" ExpoFont: - :path: "../node_modules/expo-font/ios" + :path: "../../../../node_modules/expo-font/ios" ExpoHaptics: - :path: "../node_modules/expo-haptics/ios" + :path: "../../../../node_modules/expo-haptics/ios" ExpoKeepAwake: - :path: "../node_modules/expo-keep-awake/ios" + :path: "../../../../node_modules/expo-keep-awake/ios" ExpoLocalAuthentication: - :path: "../node_modules/expo-local-authentication/ios" + :path: "../../../../node_modules/expo-local-authentication/ios" ExpoModulesCore: - :path: "../node_modules/expo-modules-core" + :path: "../../../../node_modules/expo-modules-core" FBLazyVector: - :path: "../node_modules/react-native/Libraries/FBLazyVector" + :path: "../../../../node_modules/react-native/Libraries/FBLazyVector" fmt: - :podspec: "../node_modules/react-native/third-party-podspecs/fmt.podspec" + :podspec: "../../../../node_modules/react-native/third-party-podspecs/fmt.podspec" glog: - :podspec: "../node_modules/react-native/third-party-podspecs/glog.podspec" + :podspec: "../../../../node_modules/react-native/third-party-podspecs/glog.podspec" hermes-engine: - :podspec: "../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec" + :podspec: "../../../../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec" :tag: hermes-2024-06-28-RNv0.74.3-7bda0c267e76d11b68a585f84cfdd65000babf85 Interactable: - :path: "../node_modules/react-native-interactable" + :path: "../../../../node_modules/react-native-interactable" RCT-Folly: - :podspec: "../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec" + :podspec: "../../../../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec" RCTDeprecation: - :path: "../node_modules/react-native/ReactApple/Libraries/RCTFoundation/RCTDeprecation" + :path: "../../../../node_modules/react-native/ReactApple/Libraries/RCTFoundation/RCTDeprecation" RCTRequired: - :path: "../node_modules/react-native/Libraries/Required" + :path: "../../../../node_modules/react-native/Libraries/Required" RCTTypeSafety: - :path: "../node_modules/react-native/Libraries/TypeSafety" + :path: "../../../../node_modules/react-native/Libraries/TypeSafety" React: - :path: "../node_modules/react-native/" + :path: "../../../../node_modules/react-native/" React-callinvoker: - :path: "../node_modules/react-native/ReactCommon/callinvoker" + :path: "../../../../node_modules/react-native/ReactCommon/callinvoker" React-Codegen: :path: build/generated/ios React-Core: - :path: "../node_modules/react-native/" + :path: "../../../../node_modules/react-native/" React-CoreModules: - :path: "../node_modules/react-native/React/CoreModules" + :path: "../../../../node_modules/react-native/React/CoreModules" React-cxxreact: - :path: "../node_modules/react-native/ReactCommon/cxxreact" + :path: "../../../../node_modules/react-native/ReactCommon/cxxreact" React-debug: - :path: "../node_modules/react-native/ReactCommon/react/debug" + :path: "../../../../node_modules/react-native/ReactCommon/react/debug" React-Fabric: - :path: "../node_modules/react-native/ReactCommon" + :path: "../../../../node_modules/react-native/ReactCommon" React-FabricImage: - :path: "../node_modules/react-native/ReactCommon" + :path: "../../../../node_modules/react-native/ReactCommon" React-featureflags: - :path: "../node_modules/react-native/ReactCommon/react/featureflags" + :path: "../../../../node_modules/react-native/ReactCommon/react/featureflags" React-graphics: - :path: "../node_modules/react-native/ReactCommon/react/renderer/graphics" + :path: "../../../../node_modules/react-native/ReactCommon/react/renderer/graphics" React-hermes: - :path: "../node_modules/react-native/ReactCommon/hermes" + :path: "../../../../node_modules/react-native/ReactCommon/hermes" React-ImageManager: - :path: "../node_modules/react-native/ReactCommon/react/renderer/imagemanager/platform/ios" + :path: "../../../../node_modules/react-native/ReactCommon/react/renderer/imagemanager/platform/ios" React-jserrorhandler: - :path: "../node_modules/react-native/ReactCommon/jserrorhandler" + :path: "../../../../node_modules/react-native/ReactCommon/jserrorhandler" React-jsi: - :path: "../node_modules/react-native/ReactCommon/jsi" + :path: "../../../../node_modules/react-native/ReactCommon/jsi" React-jsiexecutor: - :path: "../node_modules/react-native/ReactCommon/jsiexecutor" + :path: "../../../../node_modules/react-native/ReactCommon/jsiexecutor" React-jsinspector: - :path: "../node_modules/react-native/ReactCommon/jsinspector-modern" + :path: "../../../../node_modules/react-native/ReactCommon/jsinspector-modern" React-jsitracing: - :path: "../node_modules/react-native/ReactCommon/hermes/executor/" + :path: "../../../../node_modules/react-native/ReactCommon/hermes/executor/" React-logger: - :path: "../node_modules/react-native/ReactCommon/logger" + :path: "../../../../node_modules/react-native/ReactCommon/logger" React-Mapbuffer: - :path: "../node_modules/react-native/ReactCommon" + :path: "../../../../node_modules/react-native/ReactCommon" react-native-app-auth: - :path: "../node_modules/react-native-app-auth" + :path: "../../../../node_modules/react-native-app-auth" react-native-blob-util: - :path: "../node_modules/react-native-blob-util" + :path: "../../../../node_modules/react-native-blob-util" react-native-cookies: - :path: "../node_modules/@react-native-cookies/cookies" + :path: "../../../../node_modules/@react-native-cookies/cookies" react-native-date-picker: - :path: "../node_modules/react-native-date-picker" + :path: "../../../../node_modules/react-native-date-picker" react-native-mmkv-storage: - :path: "../node_modules/react-native-mmkv-storage" + :path: "../../../../node_modules/react-native-mmkv-storage" react-native-netinfo: - :path: "../node_modules/@react-native-community/netinfo" + :path: "../../../../node_modules/@react-native-community/netinfo" react-native-passkey: - :path: "../node_modules/react-native-passkey" + :path: "../../../../node_modules/react-native-passkey" react-native-passkit-wallet: - :path: "../node_modules/react-native-passkit-wallet" + :path: "../../../../node_modules/react-native-passkit-wallet" react-native-pdf: - :path: "../node_modules/react-native-pdf" + :path: "../../../../node_modules/react-native-pdf" react-native-progress-bar-android: - :path: "../node_modules/@react-native-community/progress-bar-android" + :path: "../../../../node_modules/@react-native-community/progress-bar-android" react-native-progress-view: - :path: "../node_modules/@react-native-community/progress-view" + :path: "../../../../node_modules/@react-native-community/progress-view" react-native-quick-base64: - :path: "../node_modules/react-native-quick-base64" + :path: "../../../../node_modules/react-native-quick-base64" react-native-spotlight-search: - :path: "../node_modules/react-native-spotlight-search" + :path: "../../../../node_modules/react-native-spotlight-search" react-native-webview: - :path: "../node_modules/react-native-webview" + :path: "../../../../node_modules/react-native-webview" React-nativeconfig: - :path: "../node_modules/react-native/ReactCommon" + :path: "../../../../node_modules/react-native/ReactCommon" React-NativeModulesApple: - :path: "../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios" + :path: "../../../../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios" React-perflogger: - :path: "../node_modules/react-native/ReactCommon/reactperflogger" + :path: "../../../../node_modules/react-native/ReactCommon/reactperflogger" React-RCTActionSheet: - :path: "../node_modules/react-native/Libraries/ActionSheetIOS" + :path: "../../../../node_modules/react-native/Libraries/ActionSheetIOS" React-RCTAnimation: - :path: "../node_modules/react-native/Libraries/NativeAnimation" + :path: "../../../../node_modules/react-native/Libraries/NativeAnimation" React-RCTAppDelegate: - :path: "../node_modules/react-native/Libraries/AppDelegate" + :path: "../../../../node_modules/react-native/Libraries/AppDelegate" React-RCTBlob: - :path: "../node_modules/react-native/Libraries/Blob" + :path: "../../../../node_modules/react-native/Libraries/Blob" React-RCTFabric: - :path: "../node_modules/react-native/React" + :path: "../../../../node_modules/react-native/React" React-RCTImage: - :path: "../node_modules/react-native/Libraries/Image" + :path: "../../../../node_modules/react-native/Libraries/Image" React-RCTLinking: - :path: "../node_modules/react-native/Libraries/LinkingIOS" + :path: "../../../../node_modules/react-native/Libraries/LinkingIOS" React-RCTNetwork: - :path: "../node_modules/react-native/Libraries/Network" + :path: "../../../../node_modules/react-native/Libraries/Network" React-RCTSettings: - :path: "../node_modules/react-native/Libraries/Settings" + :path: "../../../../node_modules/react-native/Libraries/Settings" React-RCTText: - :path: "../node_modules/react-native/Libraries/Text" + :path: "../../../../node_modules/react-native/Libraries/Text" React-RCTVibration: - :path: "../node_modules/react-native/Libraries/Vibration" + :path: "../../../../node_modules/react-native/Libraries/Vibration" React-rendererdebug: - :path: "../node_modules/react-native/ReactCommon/react/renderer/debug" + :path: "../../../../node_modules/react-native/ReactCommon/react/renderer/debug" React-rncore: - :path: "../node_modules/react-native/ReactCommon" + :path: "../../../../node_modules/react-native/ReactCommon" React-RuntimeApple: - :path: "../node_modules/react-native/ReactCommon/react/runtime/platform/ios" + :path: "../../../../node_modules/react-native/ReactCommon/react/runtime/platform/ios" React-RuntimeCore: - :path: "../node_modules/react-native/ReactCommon/react/runtime" + :path: "../../../../node_modules/react-native/ReactCommon/react/runtime" React-runtimeexecutor: - :path: "../node_modules/react-native/ReactCommon/runtimeexecutor" + :path: "../../../../node_modules/react-native/ReactCommon/runtimeexecutor" React-RuntimeHermes: - :path: "../node_modules/react-native/ReactCommon/react/runtime" + :path: "../../../../node_modules/react-native/ReactCommon/react/runtime" React-runtimescheduler: - :path: "../node_modules/react-native/ReactCommon/react/renderer/runtimescheduler" + :path: "../../../../node_modules/react-native/ReactCommon/react/renderer/runtimescheduler" React-utils: - :path: "../node_modules/react-native/ReactCommon/react/utils" + :path: "../../../../node_modules/react-native/ReactCommon/react/utils" ReactCommon: - :path: "../node_modules/react-native/ReactCommon" + :path: "../../../../node_modules/react-native/ReactCommon" ReactNativeKeyboardManager: - :path: "../node_modules/react-native-keyboard-manager" + :path: "../../../../node_modules/react-native-keyboard-manager" ReactNativeNavigation: - :path: "../node_modules/react-native-navigation" + :path: "../../../../node_modules/react-native-navigation" RNCAsyncStorage: - :path: "../node_modules/@react-native-community/async-storage" + :path: "../../../../node_modules/@react-native-community/async-storage" RNCClipboard: - :path: "../node_modules/@react-native-clipboard/clipboard" + :path: "../../../../node_modules/@react-native-clipboard/clipboard" RNDeviceInfo: - :path: "../node_modules/react-native-device-info" + :path: "../../../../node_modules/react-native-device-info" RNFBAnalytics: - :path: "../node_modules/@react-native-firebase/analytics" + :path: "../../../../node_modules/@react-native-firebase/analytics" RNFBApp: - :path: "../node_modules/@react-native-firebase/app" + :path: "../../../../node_modules/@react-native-firebase/app" RNFBMessaging: - :path: "../node_modules/@react-native-firebase/messaging" + :path: "../../../../node_modules/@react-native-firebase/messaging" RNFBPerf: - :path: "../node_modules/@react-native-firebase/perf" + :path: "../../../../node_modules/@react-native-firebase/perf" RNGestureHandler: - :path: "../node_modules/react-native-gesture-handler" + :path: "../../../../node_modules/react-native-gesture-handler" RNInAppBrowser: - :path: "../node_modules/react-native-inappbrowser-reborn" + :path: "../../../../node_modules/react-native-inappbrowser-reborn" RNKeychain: - :path: "../node_modules/react-native-keychain" + :path: "../../../../node_modules/react-native-keychain" RNQuickAction: - :path: "../node_modules/react-native-quick-actions" + :path: "../../../../node_modules/react-native-quick-actions" RNReanimated: :path: "../node_modules/react-native-reanimated" RNShare: - :path: "../node_modules/react-native-share" + :path: "../../../../node_modules/react-native-share" RNSVG: - :path: "../node_modules/react-native-svg" + :path: "../../../../node_modules/react-native-svg" VisionCamera: :path: "../node_modules/react-native-vision-camera" Yoga: - :path: "../node_modules/react-native/ReactCommon/yoga" + :path: "../../../../node_modules/react-native/ReactCommon/yoga" SPEC CHECKSUMS: - AppAuth: 501c04eda8a8d11f179dbe8637b7a91bb7e5d2fa + AppAuth: d4f13a8fe0baf391b2108511793e4b479691fb73 Base64: cecfb41a004124895a7bcee567a89bae5a89d49b boost: d3f49c53809116a5d38da093a8aa78bf551aed09 CodePush: eaa66fc8dd9ff611304ecc48397ce8831ba79ac9 @@ -1938,9 +1938,9 @@ SPEC CHECKSUMS: FirebaseInstallations: 766dabca09fd94aef922538aaf144cc4a6fb6869 FirebaseMessaging: e345b219fd15d325f0cf2fef28cb8ce00d851b3f FirebasePerformance: 8f1c8e5a4fcc5a68400835518ee63a6d63dbff0c - FirebaseRemoteConfig: 37a2ba3c8c454be8553a41ba1a2f4a4f0b845670 - FirebaseRemoteConfigInterop: c55a739f5ab121792776e191d9fd437dc624a541 - FirebaseSharedSwift: a03fe7a59ee646fef71099a887f774fe25d745b8 + FirebaseRemoteConfig: 48ef3f243742a8d72422ccfc9f986e19d7de53fd + FirebaseRemoteConfigInterop: 6efda51fb5e2f15b16585197e26eaa09574e8a4d + FirebaseSharedSwift: 20530f495084b8d840f78a100d8c5ee613375f6e fmt: 4c2741a687cc09f0634a2e2c72a838b99f1ff120 glog: fdfdfe5479092de0c4bdbebedd9056951f092c4f GoogleAppMeasurement: c7d6fff39bf2d829587d74088d582e32d75133c3 @@ -1962,7 +1962,7 @@ SPEC CHECKSUMS: RCTTypeSafety: f5ecbc86c5c5fa163c05acb7a1c5012e15b5f994 React: fc9fa7258eff606f44d58c5b233a82dc9cf09018 React-callinvoker: e3fab14d69607fb7e8e3a57e5a415aed863d3599 - React-Codegen: 6fa87b7c6b8efcd0cef4bfeaec8c8bc8a6abe75a + React-Codegen: df959b3e7962cad0125362bb448ae59d9c0c8dfe React-Core: 3a5fd9e781cecf87803e5b091496a606a3df774a React-CoreModules: cbf4707dafab8f9f826ac0c63a07d0bf5d01e256 React-cxxreact: 7b188556271e3c7fdf22a04819f6a6225045b9dd @@ -2030,7 +2030,7 @@ SPEC CHECKSUMS: RNInAppBrowser: e36d6935517101ccba0e875bac8ad7b0cb655364 RNKeychain: ff836453cba46938e0e9e4c22e43d43fa2c90333 RNQuickAction: 6d404a869dc872cde841ad3147416a670d13fa93 - RNReanimated: f9192536fa8a312c737eaf15c905f78831193ef1 + RNReanimated: d093daf3973a7ee9f77c10a9337e10390b2969e2 RNShare: 0fad69ae2d71de9d1f7b9a43acf876886a6cb99c RNSVG: 43b64ed39c14ce830d840903774154ca0c1f27ec SocketRocket: abac6f5de4d4d62d24e11868d7a2f427e0ef940d diff --git a/apps/native/app/jest.config.js b/apps/native/app/jest.config.js deleted file mode 100644 index 1fbafc9cb781..000000000000 --- a/apps/native/app/jest.config.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - preset: 'react-native', -} diff --git a/apps/native/app/jest.config.ts b/apps/native/app/jest.config.ts new file mode 100644 index 000000000000..3e943d58cfc5 --- /dev/null +++ b/apps/native/app/jest.config.ts @@ -0,0 +1,22 @@ +module.exports = { + displayName: 'IslandApp', + preset: 'react-native', + resolver: '@nx/jest/plugins/resolver', + moduleFileExtensions: ['ts', 'js', 'html', 'tsx', 'jsx'], + setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'], + moduleNameMapper: { + '\\.svg$': '@nx/react-native/plugins/jest/svg-mock', + }, + transform: { + '^.+.(js|ts|tsx)$': [ + 'babel-jest', + { + configFile: __dirname + '/.babelrc.js', + }, + ], + '^.+.(bmp|gif|jpg|jpeg|mp4|png|psd|svg|webp)$': require.resolve( + 'react-native/jest/assetFileTransformer.js', + ), + }, + coverageDirectory: '../../../coverage/apps/native/app', +} diff --git a/apps/native/app/metro.config.js b/apps/native/app/metro.config.js index 491f6980daae..70fab7425451 100644 --- a/apps/native/app/metro.config.js +++ b/apps/native/app/metro.config.js @@ -1,18 +1,8 @@ -const path = require('path') +const { withNxMetro } = require('@nx/react-native') const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config') -const libs = ['application/types'] - -const extraNodeModules = libs.reduce((acc, lib) => { - acc[`@island.is/${lib}`] = path.resolve(__dirname, `../../../libs/${lib}/src`) - return acc -}, {}) - -const watchFolders = [ - ...libs.map((lib) => path.resolve(__dirname, `../../../libs/${lib}/src`)), -] - -const nodeModulesPaths = [path.resolve(path.join(__dirname, './node_modules'))] +const defaultConfig = getDefaultConfig(__dirname) +const { assetExts, sourceExts } = defaultConfig.resolver /** * Metro configuration @@ -20,15 +10,22 @@ const nodeModulesPaths = [path.resolve(path.join(__dirname, './node_modules'))] * * @type {import('metro-config').MetroConfig} */ -const config = { +const customConfig = { + transformer: { + babelTransformerPath: require.resolve('react-native-svg-transformer'), + }, resolver: { - extraNodeModules: { - '@ui': path.resolve(__dirname + '/src/ui'), - ...extraNodeModules, - }, - nodeModulesPaths, + assetExts: assetExts.filter((ext) => ext !== 'svg'), + sourceExts: [...sourceExts, 'cjs', 'mjs', 'svg'], }, - watchFolders, } -module.exports = mergeConfig(getDefaultConfig(__dirname), config) +module.exports = withNxMetro(mergeConfig(defaultConfig, customConfig), { + // Change this to true to see debugging info. + // Useful if you have issues resolving modules + debug: false, + // all the file extensions used for imports other than 'ts', 'tsx', 'js', 'jsx', 'json' + extensions: [], + // Specify folders to watch, in addition to Nx defaults (workspace libraries and node_modules) + watchFolders: [], +}) diff --git a/apps/native/app/package.json b/apps/native/app/package.json index 3e42a3ae7714..3ebb950c5cba 100644 --- a/apps/native/app/package.json +++ b/apps/native/app/package.json @@ -1,7 +1,7 @@ { "name": "@island.is/native-app", "version": "1.2.5", - "private": true, + "license": "MIT", "scripts": { "release:android": "cd android && ./gradlew bundleProdRelease bundleDevRelease", "android": "react-native run-android --mode devDebug", @@ -51,6 +51,8 @@ "@react-native/eslint-config": "0.74.87", "@react-native/metro-config": "0.74.87", "@react-native/typescript-config": "0.74.87", + "@testing-library/jest-native": "5.4.3", + "@testing-library/react-native": "12.9.0", "apollo3-cache-persist": "0.15.0", "compare-versions": "6.1.1", "configcat-js": "7.0.0", @@ -63,8 +65,9 @@ "expo-notifications": "0.28.9", "intl": "1.2.5", "lodash": "4.17.21", + "metro-config": "0.81.0", "path-to-regexp": "6.2.2", - "react": "18.2.0", + "react": "18.3.1", "react-intl": "5.20.12", "react-native": "0.74.5", "react-native-app-auth": "7.2.0", @@ -96,9 +99,6 @@ "styled-components": "6.1.11", "zustand": "3.5.12" }, - "installConfig": { - "hoistingLimits": "workspaces" - }, "devDependencies": { "@babel/core": "^7.20.0", "@babel/preset-env": "^7.20.0", @@ -112,7 +112,6 @@ "babel-jest": "^29.6.3", "babel-loader": "^8.3.0", "babel-plugin-formatjs": "10.3.9", - "babel-plugin-module-resolver": "5.0.2", "jest": "29.7.0", "react-test-renderer": "18.2.0", "typescript": "5.3.3" diff --git a/apps/native/app/project.json b/apps/native/app/project.json index 337b98afd2fe..a23ee071865a 100644 --- a/apps/native/app/project.json +++ b/apps/native/app/project.json @@ -6,30 +6,82 @@ "tags": ["scope:js"], "generators": {}, "targets": { - "serve": { - "executor": "nx:run-commands", + "start": { + "executor": "@nx/react-native:start", + "dependsOn": [], "options": { - "cwd": "apps/native/app", - "command": "yarn start" + "port": 8081 } }, - "build": { - "executor": "nx:run-commands", + "run-ios": { + "executor": "@nx/react-native:run-ios", + "dependsOn": [], + "options": {} + }, + "bundle-ios": { + "executor": "@nx/react-native:bundle", + "dependsOn": [], + "outputs": ["{options.bundleOutput}"], "options": { - "cwd": "apps/native/app", - "command": "yarn build-mock" + "entryFile": "index.js", + "platform": "ios", + "bundleOutput": "dist/apps/native/app/ios/main.jsbundle" } }, - "test": { - "executor": "nx:run-commands", + "run-android": { + "executor": "@nx/react-native:run-android", + "dependsOn": [], + "options": {} + }, + "build-android": { + "executor": "@nx/react-native:build-android", + "outputs": [ + "{projectRoot}/android/app/build/outputs/bundle", + "{projectRoot}/android/app/build/outputs/apk" + ], + "dependsOn": [], + "options": {} + }, + "build-ios": { + "executor": "@nx/react-native:build-ios", + "outputs": ["{projectRoot}/ios/build/Build"], + "dependsOn": [], + "options": {} + }, + "pod-install": { + "executor": "@nx/react-native:pod-install", + "dependsOn": ["sync-deps"], + "outputs": ["{projectRoot}/ios/Pods", "{projectRoot}/ios/Podfile.lock"], + "options": {} + }, + "upgrade": { + "executor": "@nx/react-native:upgrade", + "options": {} + }, + "bundle-android": { + "executor": "@nx/react-native:bundle", + "dependsOn": [], + "outputs": ["{options.bundleOutput}"], "options": { - "cwd": "apps/native/app", - "command": "yarn test-mock" + "entryFile": "index.js", + "platform": "android", + "bundleOutput": "dist/apps/native/app/android/main.jsbundle" } }, + "sync-deps": { + "executor": "@nx/react-native:sync-deps", + "options": {} + }, "lint": { "executor": "@nx/eslint:lint" }, + "test": { + "executor": "@nx/jest:jest", + "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], + "options": { + "jestConfig": "apps/native/app/jest.config.ts" + } + }, "codegen/frontend-client": { "executor": "nx:run-commands", "options": { diff --git a/apps/native/app/src/components/bottom-tabs-indicator/bottom-tabs-indicator.tsx b/apps/native/app/src/components/bottom-tabs-indicator/bottom-tabs-indicator.tsx index 27efe493886d..4967aaee8f59 100644 --- a/apps/native/app/src/components/bottom-tabs-indicator/bottom-tabs-indicator.tsx +++ b/apps/native/app/src/components/bottom-tabs-indicator/bottom-tabs-indicator.tsx @@ -1,9 +1,9 @@ -import { dynamicColor } from '@ui' import React, { useEffect, useRef, useState } from 'react' import { Animated, SafeAreaView, View, useWindowDimensions } from 'react-native' import { useTheme } from 'styled-components' import styled from 'styled-components/native' import { useUiStore } from '../../stores/ui-store' +import { dynamicColor } from '../../ui' const Host = styled.View` position: absolute; diff --git a/apps/native/app/src/components/offline/offline-banner.tsx b/apps/native/app/src/components/offline/offline-banner.tsx index 60f01b78f504..04c03fe828b6 100644 --- a/apps/native/app/src/components/offline/offline-banner.tsx +++ b/apps/native/app/src/components/offline/offline-banner.tsx @@ -1,8 +1,8 @@ -import { Alert, DARK_YELLOW_200, dynamicColor } from '@ui' import React, { useEffect, useRef } from 'react' import { Animated, Easing, SafeAreaView } from 'react-native' import { Navigation } from 'react-native-navigation' import styled from 'styled-components/native' +import { Alert, DARK_YELLOW_200, dynamicColor } from '../../ui' import { getIntl } from '../../contexts/i18n-provider' import { useOfflineActions } from '../../stores/offline-store' import { ComponentRegistry as CR } from '../../utils/component-registry' diff --git a/apps/native/app/src/components/pin-keypad/pin-keypad.tsx b/apps/native/app/src/components/pin-keypad/pin-keypad.tsx index c6bf5a106aca..3c2cebe2792f 100644 --- a/apps/native/app/src/components/pin-keypad/pin-keypad.tsx +++ b/apps/native/app/src/components/pin-keypad/pin-keypad.tsx @@ -1,4 +1,3 @@ -import { dynamicColor, font } from '@ui' import React, { useCallback, useEffect, useState } from 'react' import { useIntl } from 'react-intl' import { @@ -10,6 +9,7 @@ import { useWindowDimensions, } from 'react-native' import styled, { useTheme } from 'styled-components/native' +import { dynamicColor, font } from '../../ui' import { testIDs } from '../../utils/test-ids' interface PinKeypadProps { diff --git a/apps/native/app/src/components/visualized-pin-code/visualized-pin-code.tsx b/apps/native/app/src/components/visualized-pin-code/visualized-pin-code.tsx index c74a91d9a201..4385e265feb5 100644 --- a/apps/native/app/src/components/visualized-pin-code/visualized-pin-code.tsx +++ b/apps/native/app/src/components/visualized-pin-code/visualized-pin-code.tsx @@ -1,7 +1,7 @@ -import { dynamicColor } from '@ui' import React, { useCallback, useEffect, useRef } from 'react' import { Animated, ViewStyle } from 'react-native' import styled from 'styled-components/native' +import { dynamicColor } from '../../ui' interface VisualizedPinCodeProps { code: string diff --git a/apps/native/app/src/hooks/use-connectivity-indicator.ts b/apps/native/app/src/hooks/use-connectivity-indicator.ts index 3f06c4b4077e..3e324a320655 100644 --- a/apps/native/app/src/hooks/use-connectivity-indicator.ts +++ b/apps/native/app/src/hooks/use-connectivity-indicator.ts @@ -1,11 +1,10 @@ import { QueryResult } from '@apollo/client' - -import { theme } from '@ui' import isEqual from 'lodash/isEqual' import { useEffect, useRef } from 'react' import { Navigation, OptionsTopBar } from 'react-native-navigation' import { OptionsTopBarButton } from 'react-native-navigation/lib/src/interfaces/Options' +import { theme } from '../ui' import { useOfflineStore } from '../stores/offline-store' import { ButtonRegistry as BR } from '../utils/component-registry' import { isDefined } from '../utils/is-defined' diff --git a/apps/native/app/src/lib/passkeys/helpers.ts b/apps/native/app/src/lib/passkeys/helpers.ts index 594f65f393ba..eea55f73f3c3 100644 --- a/apps/native/app/src/lib/passkeys/helpers.ts +++ b/apps/native/app/src/lib/passkeys/helpers.ts @@ -1,6 +1,6 @@ import { - PasskeyRegistrationResult, PasskeyAuthenticationResult, + PasskeyRegistrationResult, } from 'react-native-passkey' import { AuthPasskeyAuthenticationOptions, @@ -106,6 +106,10 @@ export const convertBase64UrlToBase64String = (base64Url: string) => { return base64Url.replace(/-/g, '+').replace(/_/g, '/') } +const MY_PAGES_PATH = '/minarsidur' +const APPLICATIONS_PATH = '/umsoknir' +const allowedPaths = [MY_PAGES_PATH, APPLICATIONS_PATH] + export const addPasskeyAsLoginHint = ( url: string, authenticationResponse: string, @@ -120,14 +124,10 @@ export const addPasskeyAsLoginHint = ( return false } - if (url.includes('/minarsidur')) { - return `${origin}/minarsidur/login?login_hint=passkey:${authenticationResponse}&target_link_uri=${encodeURIComponent( - url, - )}` - } + const matchedPath = allowedPaths.find((path) => url.includes(path)) - if (url.includes('/umsoknir')) { - return `${origin}/umsoknir/login?login_hint=passkey:${authenticationResponse}&target_link_uri=${encodeURIComponent( + if (matchedPath) { + return `${origin}/bff/login?login_hint=passkey:${authenticationResponse}&target_link_uri=${encodeURIComponent( url, )}` } @@ -139,7 +139,7 @@ export const doesUrlSupportPasskey = (url: string): boolean => { (url.startsWith('https://beta.dev01.devland.is') || url.startsWith('https://beta.staging01.devland.is') || url.startsWith('https://island.is')) && - (url.includes('/minarsidur') || url.includes('/umsoknir')) + (url.includes(MY_PAGES_PATH) || url.includes(APPLICATIONS_PATH)) ) { return true } diff --git a/apps/native/app/src/lib/show-picker.ts b/apps/native/app/src/lib/show-picker.ts index 20a41ca0167f..dbc2d73fe856 100644 --- a/apps/native/app/src/lib/show-picker.ts +++ b/apps/native/app/src/lib/show-picker.ts @@ -1,6 +1,6 @@ -import { dynamicColor } from '@ui' import { ActionSheetIOS } from 'react-native' import DialogAndroid from 'react-native-dialogs' +import { dynamicColor } from '../ui' import { uiStore } from '../stores/ui-store' import { isAndroid, isIos } from '../utils/devices' diff --git a/apps/native/app/src/screens/air-discount/air-discount.tsx b/apps/native/app/src/screens/air-discount/air-discount.tsx index 971b7b711686..f74b83dbd9d4 100644 --- a/apps/native/app/src/screens/air-discount/air-discount.tsx +++ b/apps/native/app/src/screens/air-discount/air-discount.tsx @@ -1,12 +1,3 @@ -import { - Alert, - Heading, - Link, - LinkText, - Problem, - Skeleton, - Typography, -} from '@ui' import React from 'react' import { Image, SafeAreaView, ScrollView, View } from 'react-native' import { NavigationFunctionComponent } from 'react-native-navigation' @@ -18,8 +9,17 @@ import { useGetAirDiscountFlightLegsQuery, useGetAirDiscountQuery, } from '../../graphql/types/schema' -import { AirDiscountCard } from '@ui/lib/card/air-discount-card' -import { Bullet } from '@ui/lib/bullet/bullet' +import { + Alert, + Heading, + Link, + LinkText, + Problem, + Skeleton, + Typography, + Bullet, + AirDiscountCard, +} from '../../ui' import { useConnectivityIndicator } from '../../hooks/use-connectivity-indicator' import { AirfaresUsageTable } from './airfares-usage-table' import externalLinkIcon from '../../assets/icons/external-link.png' diff --git a/apps/native/app/src/screens/air-discount/airfares-usage-table.tsx b/apps/native/app/src/screens/air-discount/airfares-usage-table.tsx index 22f60d265286..786660f7771b 100644 --- a/apps/native/app/src/screens/air-discount/airfares-usage-table.tsx +++ b/apps/native/app/src/screens/air-discount/airfares-usage-table.tsx @@ -2,7 +2,7 @@ import React from 'react' import { FormattedDate, FormattedTime } from 'react-intl' import styled from 'styled-components/native' -import { Typography, dynamicColor } from '@ui' +import { Typography, dynamicColor } from '../../ui' import { GetAirDiscountFlightLegsQuery } from '../../graphql/types/schema' import { useTheme } from 'styled-components' diff --git a/apps/native/app/src/screens/app-lock/app-lock.tsx b/apps/native/app/src/screens/app-lock/app-lock.tsx index 56ffc28596e2..28edb1dc7d16 100644 --- a/apps/native/app/src/screens/app-lock/app-lock.tsx +++ b/apps/native/app/src/screens/app-lock/app-lock.tsx @@ -1,4 +1,3 @@ -import { dynamicColor, font } from '@ui' import { selectionAsync } from 'expo-haptics' import { authenticateAsync, @@ -17,6 +16,8 @@ import { useNavigationComponentDidDisappear, } from 'react-native-navigation-hooks/dist' import styled from 'styled-components/native' + +import { dynamicColor, font } from '../../ui' import logo from '../../assets/logo/logo-64w.png' import { PinKeypad } from '../../components/pin-keypad/pin-keypad' import { VisualizedPinCode } from '../../components/visualized-pin-code/visualized-pin-code' diff --git a/apps/native/app/src/screens/applications/applications.tsx b/apps/native/app/src/screens/applications/applications.tsx index f999a0c124a9..b4951062324f 100644 --- a/apps/native/app/src/screens/applications/applications.tsx +++ b/apps/native/app/src/screens/applications/applications.tsx @@ -1,4 +1,3 @@ -import { EmptyList, StatusCardSkeleton } from '@ui' import { useCallback, useMemo, useState } from 'react' import { useIntl } from 'react-intl' import { Image, RefreshControl, ScrollView, View } from 'react-native' @@ -6,6 +5,7 @@ import { NavigationFunctionComponent } from 'react-native-navigation' import { useNavigationComponentDidAppear } from 'react-native-navigation-hooks' import { useTheme } from 'styled-components' +import { EmptyList, StatusCardSkeleton } from '../../ui' import illustrationSrc from '../../assets/illustrations/le-jobs-s3.png' import { Application, diff --git a/apps/native/app/src/screens/applications/components/applications-list.tsx b/apps/native/app/src/screens/applications/components/applications-list.tsx index 4d6234a1777b..e936fe82c504 100644 --- a/apps/native/app/src/screens/applications/components/applications-list.tsx +++ b/apps/native/app/src/screens/applications/components/applications-list.tsx @@ -1,12 +1,3 @@ -import { - Badge, - badgeColorSchemes, - EmptyList, - Problem, - StatusCard, - StatusCardSkeleton, - TopLine, -} from '@ui' import { useCallback, useMemo, useRef, useState } from 'react' import { useIntl } from 'react-intl' import { @@ -18,6 +9,16 @@ import { View, } from 'react-native' import { useTheme } from 'styled-components' + +import { + Badge, + badgeColorSchemes, + EmptyList, + Problem, + StatusCard, + StatusCardSkeleton, + TopLine, +} from '../../../ui' import illustrationSrc from '../../../assets/illustrations/le-jobs-s3.png' import { Application, diff --git a/apps/native/app/src/screens/applications/components/applications-preview.tsx b/apps/native/app/src/screens/applications/components/applications-preview.tsx index d9bcbcfd5c55..357c4f1a4f7d 100644 --- a/apps/native/app/src/screens/applications/components/applications-preview.tsx +++ b/apps/native/app/src/screens/applications/components/applications-preview.tsx @@ -1,3 +1,7 @@ +import { useIntl } from 'react-intl' +import { TouchableOpacity, View } from 'react-native' +import { useTheme } from 'styled-components' + import { Badge, badgeColorSchemes, @@ -6,11 +10,7 @@ import { StatusCard, Typography, ViewPager, -} from '@ui' -import { useIntl } from 'react-intl' -import { TouchableOpacity, View } from 'react-native' -import { useTheme } from 'styled-components' - +} from '../../../ui' import { Application } from '../../../graphql/types/schema' import { getApplicationType } from '../utils/getApplicationType' import { getBadgeVariant } from '../utils/getBadgeVariant' diff --git a/apps/native/app/src/screens/assets/assets-detail.tsx b/apps/native/app/src/screens/assets/assets-detail.tsx index 7c2f03da9c7c..2d39b01c4ac8 100644 --- a/apps/native/app/src/screens/assets/assets-detail.tsx +++ b/apps/native/app/src/screens/assets/assets-detail.tsx @@ -1,4 +1,3 @@ -import { Divider, Input, InputRow, NavigationBarSheet } from '@ui' import React from 'react' import { useIntl } from 'react-intl' import { ScrollView, View } from 'react-native' @@ -6,6 +5,8 @@ import { Navigation, NavigationFunctionComponent, } from 'react-native-navigation' + +import { Divider, Input, InputRow, NavigationBarSheet } from '../../ui' import { useGetAssetQuery } from '../../graphql/types/schema' import { createNavigationOptionHooks } from '../../hooks/create-navigation-option-hooks' import { testIDs } from '../../utils/test-ids' diff --git a/apps/native/app/src/screens/assets/assets-overview.tsx b/apps/native/app/src/screens/assets/assets-overview.tsx index 80ae907a351b..508f83c60f5b 100644 --- a/apps/native/app/src/screens/assets/assets-overview.tsx +++ b/apps/native/app/src/screens/assets/assets-overview.tsx @@ -1,4 +1,3 @@ -import { AssetCard, EmptyList, Skeleton, TopLine } from '@ui' import React, { useCallback, useRef, useState } from 'react' import { useIntl } from 'react-intl' import { @@ -12,6 +11,8 @@ import { } from 'react-native' import { NavigationFunctionComponent } from 'react-native-navigation' import { useTheme } from 'styled-components/native' + +import { AssetCard, EmptyList, Skeleton, TopLine } from '../../ui' import illustrationSrc from '../../assets/illustrations/le-moving-s1.png' import { BottomTabsIndicator } from '../../components/bottom-tabs-indicator/bottom-tabs-indicator' import { useListAssetsQuery } from '../../graphql/types/schema' diff --git a/apps/native/app/src/screens/cognito-auth/cognito-auth.tsx b/apps/native/app/src/screens/cognito-auth/cognito-auth.tsx index d64f21849c18..718232be294b 100644 --- a/apps/native/app/src/screens/cognito-auth/cognito-auth.tsx +++ b/apps/native/app/src/screens/cognito-auth/cognito-auth.tsx @@ -7,7 +7,6 @@ import { } from '@apollo/client/core' import { setContext } from '@apollo/client/link/context' import { useAsyncStorage } from '@react-native-community/async-storage' -import { Button } from '@ui' import { useEffect, useState } from 'react' import { ActionSheetIOS, Linking, Text, View } from 'react-native' import { @@ -19,6 +18,8 @@ import { Navigation, NavigationFunctionComponent, } from 'react-native-navigation' + +import { Button } from '../../ui' import { config } from '../../config' import { openNativeBrowser } from '../../lib/rn-island' import { cognitoAuthUrl, configs } from './config-switcher' diff --git a/apps/native/app/src/screens/document-detail/document-detail.tsx b/apps/native/app/src/screens/document-detail/document-detail.tsx index 9914b0937f78..f12d411990f6 100644 --- a/apps/native/app/src/screens/document-detail/document-detail.tsx +++ b/apps/native/app/src/screens/document-detail/document-detail.tsx @@ -1,6 +1,4 @@ import { useApolloClient, useFragment_experimental } from '@apollo/client' -import { Alert, blue400, dynamicColor, Header, Loader } from '@ui' -import { Problem } from '@ui/lib/problem/problem' import React, { useEffect, useRef, useState } from 'react' import { FormattedDate, useIntl } from 'react-intl' import { @@ -22,6 +20,8 @@ import { import Pdf, { Source } from 'react-native-pdf' import WebView from 'react-native-webview' import styled, { useTheme } from 'styled-components/native' + +import { Alert, blue400, dynamicColor, Header, Loader, Problem } from '../../ui' import { DocumentV2, DocumentV2Action, diff --git a/apps/native/app/src/screens/document-detail/utils/get-buttons-for-actions.tsx b/apps/native/app/src/screens/document-detail/utils/get-buttons-for-actions.tsx index b5baae22f5ea..2dd2b9438144 100644 --- a/apps/native/app/src/screens/document-detail/utils/get-buttons-for-actions.tsx +++ b/apps/native/app/src/screens/document-detail/utils/get-buttons-for-actions.tsx @@ -1,8 +1,8 @@ -import { Button } from '@ui' import styled from 'styled-components' import { View } from 'react-native' import { isValidElement } from 'react' +import { Button } from '../../../ui' import openIcon from '../../../assets/icons/external-link.png' import downloadIcon from '../../../assets/icons/download.png' import { DocumentV2Action } from '../../../graphql/types/schema' diff --git a/apps/native/app/src/screens/family/family-details.tsx b/apps/native/app/src/screens/family/family-details.tsx index e4cf541c22cd..1733c663aea3 100644 --- a/apps/native/app/src/screens/family/family-details.tsx +++ b/apps/native/app/src/screens/family/family-details.tsx @@ -1,4 +1,3 @@ -import { Input, InputRow, NavigationBarSheet, Typography } from '@ui' import React from 'react' import { useIntl } from 'react-intl' import { SafeAreaView, ScrollView, View } from 'react-native' @@ -14,6 +13,8 @@ import { useNationalRegistryChildCustodyQuery, useNationalRegistrySpouseQuery, } from '../../graphql/types/schema' + +import { Input, InputRow, NavigationBarSheet, Typography } from '../../ui' import { createNavigationOptionHooks } from '../../hooks/create-navigation-option-hooks' import { formatNationalId } from '../../lib/format-national-id' import { testIDs } from '../../utils/test-ids' diff --git a/apps/native/app/src/screens/family/family-overview.tsx b/apps/native/app/src/screens/family/family-overview.tsx index f2bb50879b2a..13cd53270394 100644 --- a/apps/native/app/src/screens/family/family-overview.tsx +++ b/apps/native/app/src/screens/family/family-overview.tsx @@ -1,4 +1,3 @@ -import { EmptyList, FamilyMemberCard, Problem, Skeleton, TopLine } from '@ui' import React, { useCallback, useRef, useState } from 'react' import { useIntl } from 'react-intl' import { @@ -12,6 +11,14 @@ import { } from 'react-native' import { NavigationFunctionComponent } from 'react-native-navigation' import { useTheme } from 'styled-components/native' + +import { + EmptyList, + FamilyMemberCard, + Skeleton, + TopLine, + Problem, +} from '../../ui' import illustrationSrc from '../../assets/illustrations/hero_spring.png' import { BottomTabsIndicator } from '../../components/bottom-tabs-indicator/bottom-tabs-indicator' import { diff --git a/apps/native/app/src/screens/finance/components/finance-status-card.tsx b/apps/native/app/src/screens/finance/components/finance-status-card.tsx index e8f884674ca8..2e9688994997 100644 --- a/apps/native/app/src/screens/finance/components/finance-status-card.tsx +++ b/apps/native/app/src/screens/finance/components/finance-status-card.tsx @@ -1,14 +1,15 @@ +import { useState } from 'react' +import { FormattedMessage, useIntl } from 'react-intl' +import { Image, Linking, Pressable, View } from 'react-native' +import styled, { useTheme } from 'styled-components/native' + import { Skeleton, Typography, blue400, dynamicColor, ExpandableCard, -} from '@ui' -import { useState } from 'react' -import { FormattedMessage, useIntl } from 'react-intl' -import { Image, Linking, Pressable, View } from 'react-native' -import styled, { useTheme } from 'styled-components/native' +} from '../../../ui' import chevronDown from '../../../assets/icons/chevron-down.png' import { ChargeType, diff --git a/apps/native/app/src/screens/finance/finance-status-detail.tsx b/apps/native/app/src/screens/finance/finance-status-detail.tsx index 5c8fbf6f166f..b3e89890e86a 100644 --- a/apps/native/app/src/screens/finance/finance-status-detail.tsx +++ b/apps/native/app/src/screens/finance/finance-status-detail.tsx @@ -1,10 +1,11 @@ -import { Input, InputRow, NavigationBarSheet } from '@ui' import { useIntl } from 'react-intl' import { ScrollView, View } from 'react-native' import { Navigation, NavigationFunctionComponent, } from 'react-native-navigation' + +import { Input, InputRow, NavigationBarSheet } from '../../ui' import { useGetFinanceStatusDetailsQuery } from '../../graphql/types/schema' import { createNavigationOptionHooks } from '../../hooks/create-navigation-option-hooks' import { testIDs } from '../../utils/test-ids' diff --git a/apps/native/app/src/screens/finance/finance.tsx b/apps/native/app/src/screens/finance/finance.tsx index 095a70507600..1462b7a6bf2e 100644 --- a/apps/native/app/src/screens/finance/finance.tsx +++ b/apps/native/app/src/screens/finance/finance.tsx @@ -1,8 +1,9 @@ -import { Button, Heading, Skeleton, TableViewCell, Typography } from '@ui' import { FormattedMessage, useIntl } from 'react-intl' import { SafeAreaView, ScrollView } from 'react-native' import { NavigationFunctionComponent } from 'react-native-navigation' import { useTheme } from 'styled-components/native' + +import { Button, Heading, Skeleton, TableViewCell, Typography } from '../../ui' import externalLink from '../../assets/icons/external-link.png' import { getConfig } from '../../config' import { GetFinanceStatus } from '../../graphql/types/finance.types' diff --git a/apps/native/app/src/screens/health/health-overview.tsx b/apps/native/app/src/screens/health/health-overview.tsx index 740cac1bbb45..2c25853fb0f5 100644 --- a/apps/native/app/src/screens/health/health-overview.tsx +++ b/apps/native/app/src/screens/health/health-overview.tsx @@ -1,12 +1,3 @@ -import { - Alert, - Button, - Heading, - Input, - InputRow, - Problem, - Typography, -} from '@ui' import React, { useCallback, useMemo, useState } from 'react' import { FormattedMessage, useIntl } from 'react-intl' import { @@ -22,6 +13,15 @@ import { NavigationFunctionComponent } from 'react-native-navigation' import styled, { useTheme } from 'styled-components/native' import { ApolloError } from '@apollo/client' +import { + Alert, + Button, + Heading, + Input, + InputRow, + Problem, + Typography, +} from '../../ui' import { useGetHealthCenterQuery, useGetHealthInsuranceOverviewQuery, diff --git a/apps/native/app/src/screens/home/air-discount-module.tsx b/apps/native/app/src/screens/home/air-discount-module.tsx index bbabf15460d4..69fb3eefd079 100644 --- a/apps/native/app/src/screens/home/air-discount-module.tsx +++ b/apps/native/app/src/screens/home/air-discount-module.tsx @@ -1,3 +1,9 @@ +import React from 'react' +import { FormattedMessage, useIntl } from 'react-intl' +import { Image, SafeAreaView, TouchableOpacity } from 'react-native' +import styled, { useTheme } from 'styled-components/native' +import { ApolloError } from '@apollo/client' + import { Typography, Heading, @@ -5,21 +11,14 @@ import { ViewPager, EmptyCard, GeneralCardSkeleton, -} from '@ui' - -import React from 'react' -import { FormattedMessage, useIntl } from 'react-intl' -import { Image, SafeAreaView, TouchableOpacity } from 'react-native' -import styled, { useTheme } from 'styled-components/native' -import { ApolloError } from '@apollo/client' - + AirDiscountCard, +} from '../../ui' import illustrationSrc from '../../assets/illustrations/le-jobs-s2.png' import { navigateTo } from '../../lib/deep-linking' import { GetAirDiscountQuery, useGetAirDiscountQuery, } from '../../graphql/types/schema' -import { AirDiscountCard } from '@ui/lib/card/air-discount-card' import { screenWidth } from '../../utils/dimensions' const Host = styled.View` diff --git a/apps/native/app/src/screens/home/applications-module.tsx b/apps/native/app/src/screens/home/applications-module.tsx index 0b0210b7cdae..c916f738a571 100644 --- a/apps/native/app/src/screens/home/applications-module.tsx +++ b/apps/native/app/src/screens/home/applications-module.tsx @@ -1,10 +1,10 @@ -import { EmptyCard, StatusCardSkeleton } from '@ui' import React from 'react' import { useIntl } from 'react-intl' import styled from 'styled-components' import { Image, SafeAreaView, View } from 'react-native' import { ApolloError } from '@apollo/client' +import { EmptyCard, StatusCardSkeleton } from '../../ui' import leJobss3 from '../../assets/illustrations/le-jobs-s3.png' import { Application, diff --git a/apps/native/app/src/screens/home/hello-module.tsx b/apps/native/app/src/screens/home/hello-module.tsx index 7bab98b63b9b..ef94534fdc9e 100644 --- a/apps/native/app/src/screens/home/hello-module.tsx +++ b/apps/native/app/src/screens/home/hello-module.tsx @@ -1,11 +1,10 @@ -import { Typography, Skeleton } from '@ui' import * as FileSystem from 'expo-file-system' - import React, { useEffect } from 'react' import { FormattedMessage } from 'react-intl' import { Image, SafeAreaView } from 'react-native' import styled, { useTheme } from 'styled-components/native' +import { Typography, Skeleton } from '../../ui' import { useAuthStore } from '../../stores/auth-store' import { usePreferencesStore } from '../../stores/preferences-store' import { useGetFrontPageImageQuery } from '../../graphql/types/schema' diff --git a/apps/native/app/src/screens/home/home-options.tsx b/apps/native/app/src/screens/home/home-options.tsx index b79ace437971..153272ea9e0a 100644 --- a/apps/native/app/src/screens/home/home-options.tsx +++ b/apps/native/app/src/screens/home/home-options.tsx @@ -1,10 +1,10 @@ -import { Heading, TableViewCell, Typography } from '@ui' import React from 'react' import { FormattedMessage, useIntl } from 'react-intl' import { Platform, SafeAreaView, ScrollView, Switch } from 'react-native' import { NavigationFunctionComponent } from 'react-native-navigation' import { useTheme } from 'styled-components' +import { Heading, TableViewCell, Typography } from '../../ui' import { createNavigationOptionHooks } from '../../hooks/create-navigation-option-hooks' import { preferencesStore, diff --git a/apps/native/app/src/screens/home/home.tsx b/apps/native/app/src/screens/home/home.tsx index f228c0843a88..2b02f0f5d313 100644 --- a/apps/native/app/src/screens/home/home.tsx +++ b/apps/native/app/src/screens/home/home.tsx @@ -1,4 +1,3 @@ -import { TopLine } from '@ui' import React, { ReactElement, useCallback, @@ -17,6 +16,7 @@ import CodePush from 'react-native-code-push' import { NavigationFunctionComponent } from 'react-native-navigation' import { BottomTabsIndicator } from '../../components/bottom-tabs-indicator/bottom-tabs-indicator' +import { TopLine } from '../../ui' import { createNavigationOptionHooks } from '../../hooks/create-navigation-option-hooks' import { useAndroidNotificationPermission } from '../../hooks/use-android-notification-permission' import { useConnectivityIndicator } from '../../hooks/use-connectivity-indicator' diff --git a/apps/native/app/src/screens/home/inbox-module.tsx b/apps/native/app/src/screens/home/inbox-module.tsx index 73ac02f007c5..a4d3ab7d3efb 100644 --- a/apps/native/app/src/screens/home/inbox-module.tsx +++ b/apps/native/app/src/screens/home/inbox-module.tsx @@ -1,17 +1,17 @@ -import { - Typography, - Heading, - ChevronRight, - ListItemSkeleton, - EmptyCard, -} from '@ui' - import React from 'react' import { FormattedMessage, useIntl } from 'react-intl' import { Image, SafeAreaView, TouchableOpacity } from 'react-native' import styled, { useTheme } from 'styled-components/native' import { ApolloError } from '@apollo/client' +import { + Typography, + Heading, + ChevronRight, + ListItemSkeleton, + EmptyCard, + InboxCard, +} from '../../ui' import leCompanys3 from '../../assets/illustrations/le-company-s3.png' import { navigateTo } from '../../lib/deep-linking' import { @@ -19,7 +19,6 @@ import { useListDocumentsQuery, } from '../../graphql/types/schema' import { useOrganizationsStore } from '../../stores/organizations-store' -import { InboxCard } from '@ui/lib/card/inbox-card' const Host = styled.View` margin-bottom: ${({ theme }) => theme.spacing[2]}px; diff --git a/apps/native/app/src/screens/home/licenses-module.tsx b/apps/native/app/src/screens/home/licenses-module.tsx index b8a3f9ecb108..b3bf4ab5ae49 100644 --- a/apps/native/app/src/screens/home/licenses-module.tsx +++ b/apps/native/app/src/screens/home/licenses-module.tsx @@ -1,3 +1,9 @@ +import React from 'react' +import { FormattedMessage, useIntl } from 'react-intl' +import { Image, SafeAreaView, TouchableOpacity } from 'react-native' +import styled, { useTheme } from 'styled-components/native' +import { ApolloError } from '@apollo/client' + import { Typography, Heading, @@ -5,14 +11,7 @@ import { ViewPager, EmptyCard, GeneralCardSkeleton, -} from '@ui' - -import React from 'react' -import { FormattedMessage, useIntl } from 'react-intl' -import { Image, SafeAreaView, TouchableOpacity } from 'react-native' -import styled, { useTheme } from 'styled-components/native' -import { ApolloError } from '@apollo/client' - +} from '../../ui' import { navigateTo } from '../../lib/deep-linking' import { GenericLicenseType, diff --git a/apps/native/app/src/screens/home/onboarding-module.tsx b/apps/native/app/src/screens/home/onboarding-module.tsx index 62eca38b932a..7e024fecc09f 100644 --- a/apps/native/app/src/screens/home/onboarding-module.tsx +++ b/apps/native/app/src/screens/home/onboarding-module.tsx @@ -1,13 +1,13 @@ -import { Close, Heading, ViewPager, WelcomeCard } from '@ui' import React from 'react' import { SafeAreaView, TouchableOpacity } from 'react-native' import { useTheme } from 'styled-components/native' +import { useIntl } from 'react-intl' + +import { Close, Heading, ViewPager, WelcomeCard } from '../../ui' import illustration1 from '../../assets/illustrations/digital-services-m3.png' import illustration3 from '../../assets/illustrations/le-company-s2.png' import illustration2 from '../../assets/illustrations/le-retirement-s3-large.png' import illustration4 from '../../assets/illustrations/le_jobs_s5.png' - -import { useIntl } from 'react-intl' import { useAuthStore } from '../../stores/auth-store' import { usePreferencesStore } from '../../stores/preferences-store' diff --git a/apps/native/app/src/screens/home/vehicles-module.tsx b/apps/native/app/src/screens/home/vehicles-module.tsx index 34c5c709c86d..7a8686d2cc60 100644 --- a/apps/native/app/src/screens/home/vehicles-module.tsx +++ b/apps/native/app/src/screens/home/vehicles-module.tsx @@ -1,3 +1,9 @@ +import React, { useMemo } from 'react' +import { FormattedMessage, useIntl } from 'react-intl' +import { Image, SafeAreaView, TouchableOpacity } from 'react-native' +import styled, { useTheme } from 'styled-components/native' +import { ApolloError } from '@apollo/client' + import { Typography, Heading, @@ -5,14 +11,7 @@ import { ViewPager, EmptyCard, GeneralCardSkeleton, -} from '@ui' - -import React, { useMemo } from 'react' -import { FormattedMessage, useIntl } from 'react-intl' -import { Image, SafeAreaView, TouchableOpacity } from 'react-native' -import styled, { useTheme } from 'styled-components/native' -import { ApolloError } from '@apollo/client' - +} from '../../ui' import illustrationSrc from '../../assets/illustrations/le-moving-s4.png' import { navigateTo } from '../../lib/deep-linking' import { VehicleItem } from '../vehicles/components/vehicle-item' diff --git a/apps/native/app/src/screens/inbox/inbox-filter.tsx b/apps/native/app/src/screens/inbox/inbox-filter.tsx index 121def2ffdd8..700cc8470dca 100644 --- a/apps/native/app/src/screens/inbox/inbox-filter.tsx +++ b/apps/native/app/src/screens/inbox/inbox-filter.tsx @@ -1,3 +1,8 @@ +import { useEffect, useState } from 'react' +import { useIntl } from 'react-intl' +import { ScrollView, View } from 'react-native' +import { Navigation } from 'react-native-navigation' + import { Accordion, AccordionItem, @@ -5,11 +10,7 @@ import { Checkbox, DatePickerInput, theme, -} from '@ui' -import { useEffect, useState } from 'react' -import { useIntl } from 'react-intl' -import { ScrollView, View } from 'react-native' -import { Navigation } from 'react-native-navigation' +} from '../../ui' import { createNavigationOptionHooks } from '../../hooks/create-navigation-option-hooks' import { useConnectivityIndicator } from '../../hooks/use-connectivity-indicator' import { diff --git a/apps/native/app/src/screens/inbox/inbox.tsx b/apps/native/app/src/screens/inbox/inbox.tsx index ea6c5ab10e02..fb83ce224dd1 100644 --- a/apps/native/app/src/screens/inbox/inbox.tsx +++ b/apps/native/app/src/screens/inbox/inbox.tsx @@ -1,13 +1,3 @@ -import { - Button, - EmptyList, - ListItem, - ListItemSkeleton, - SearchBar, - Tag, - TopLine, - InboxCard, -} from '@ui' import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useIntl } from 'react-intl' import { @@ -26,6 +16,17 @@ import { } from 'react-native-navigation' import { useNavigationComponentDidAppear } from 'react-native-navigation-hooks/dist' import styled, { useTheme } from 'styled-components/native' + +import { + Button, + EmptyList, + ListItem, + ListItemSkeleton, + SearchBar, + Tag, + TopLine, + InboxCard, +} from '../../ui' import filterIcon from '../../assets/icons/filter-icon.png' import inboxReadIcon from '../../assets/icons/inbox-read.png' import illustrationSrc from '../../assets/illustrations/le-company-s3.png' diff --git a/apps/native/app/src/screens/license-scanner/license-scan-detail.tsx b/apps/native/app/src/screens/license-scanner/license-scan-detail.tsx index 0bf8dacd2215..8e1c92ca83e9 100644 --- a/apps/native/app/src/screens/license-scanner/license-scan-detail.tsx +++ b/apps/native/app/src/screens/license-scanner/license-scan-detail.tsx @@ -1,4 +1,3 @@ -import { ScanResultCard, SupportedGenericLicenseTypes } from '@ui' import { useIntl } from 'react-intl' import { Navigation, @@ -7,6 +6,8 @@ import { } from 'react-native-navigation' import { useNavigationButtonPress } from 'react-native-navigation-hooks/dist' import styled from 'styled-components/native' + +import { ScanResultCard, SupportedGenericLicenseTypes } from '../../ui' import { LICENSE_SCANNER_DONE } from '../../constants/navigation-buttons' import { VerifyLicenseBarcodeError, diff --git a/apps/native/app/src/screens/license-scanner/license-scanner.tsx b/apps/native/app/src/screens/license-scanner/license-scanner.tsx index e5bcd13c9be4..2c08022a210b 100644 --- a/apps/native/app/src/screens/license-scanner/license-scanner.tsx +++ b/apps/native/app/src/screens/license-scanner/license-scanner.tsx @@ -1,4 +1,3 @@ -import { Bubble, Button, theme } from '@ui' import { impactAsync, ImpactFeedbackStyle } from 'expo-haptics' import React, { useCallback, useEffect, useRef, useState } from 'react' import { useIntl } from 'react-intl' @@ -34,6 +33,8 @@ import { } from 'react-native-vision-camera' import { CodeScanner, CodeType } from 'react-native-vision-camera' import styled from 'styled-components/native' + +import { Bubble, Button, theme } from '../../ui' import flashligth from '../../assets/icons/flashlight.png' import { LICENSE_SCANNER_DONE } from '../../constants/navigation-buttons' import { diff --git a/apps/native/app/src/screens/login/login.tsx b/apps/native/app/src/screens/login/login.tsx index c2f8a19c3509..2c25f637b067 100644 --- a/apps/native/app/src/screens/login/login.tsx +++ b/apps/native/app/src/screens/login/login.tsx @@ -1,4 +1,3 @@ -import { Button, dynamicColor, font, Illustration } from '@ui' import React, { useEffect, useState } from 'react' import { FormattedMessage, useIntl } from 'react-intl' import { @@ -15,6 +14,8 @@ import { } from 'react-native' import { NavigationFunctionComponent } from 'react-native-navigation' import styled from 'styled-components/native' + +import { Button, dynamicColor, font, Illustration } from '../../ui' import logo from '../../assets/logo/logo-64w.png' import { useBrowser } from '../../lib/use-browser' import { useAuthStore } from '../../stores/auth-store' diff --git a/apps/native/app/src/screens/login/testing-login.tsx b/apps/native/app/src/screens/login/testing-login.tsx index f598ec587f0c..921a4980105a 100644 --- a/apps/native/app/src/screens/login/testing-login.tsx +++ b/apps/native/app/src/screens/login/testing-login.tsx @@ -1,4 +1,3 @@ -import { Button, dynamicColor, font } from '@ui' import React, { useEffect, useState } from 'react' import { FormattedMessage, useIntl } from 'react-intl' import { @@ -15,6 +14,8 @@ import { } from 'react-native' import { NavigationFunctionComponent } from 'react-native-navigation' import styled from 'styled-components/native' + +import { Button, dynamicColor, font } from '../../ui' import logo from '../../assets/logo/logo-64w.png' import testinglogo from '../../assets/logo/testing-logo-64w.png' import { environments, isTestingApp, useConfig } from '../../config' diff --git a/apps/native/app/src/screens/more/more.tsx b/apps/native/app/src/screens/more/more.tsx index 358ae75ae4d3..57fd1d1dbbac 100644 --- a/apps/native/app/src/screens/more/more.tsx +++ b/apps/native/app/src/screens/more/more.tsx @@ -1,10 +1,11 @@ -import { FamilyMemberCard, MoreCard } from '@ui' import React, { useState } from 'react' import { useIntl } from 'react-intl' import { SafeAreaView, ScrollView, TouchableHighlight } from 'react-native' import { NavigationFunctionComponent } from 'react-native-navigation' import { useNavigationComponentDidAppear } from 'react-native-navigation-hooks' import styled, { useTheme } from 'styled-components/native' + +import { FamilyMemberCard, MoreCard } from '../../ui' import assetsIcon from '../../assets/icons/assets.png' import familyIcon from '../../assets/icons/family.png' import financeIcon from '../../assets/icons/finance.png' diff --git a/apps/native/app/src/screens/more/personal-info.tsx b/apps/native/app/src/screens/more/personal-info.tsx index 87417710ad0c..1bb9f87e3b40 100644 --- a/apps/native/app/src/screens/more/personal-info.tsx +++ b/apps/native/app/src/screens/more/personal-info.tsx @@ -1,4 +1,3 @@ -import { Alert, Button, Input, InputRow, NavigationBarSheet } from '@ui' import React from 'react' import { useIntl } from 'react-intl' import { ScrollView, View } from 'react-native' @@ -6,6 +5,8 @@ import { Navigation, NavigationFunctionComponent, } from 'react-native-navigation' + +import { Alert, Button, Input, InputRow, NavigationBarSheet } from '../../ui' import { useNationalRegistryUserQuery } from '../../graphql/types/schema' import { createNavigationOptionHooks } from '../../hooks/create-navigation-option-hooks' import { navigateTo } from '../../lib/deep-linking' diff --git a/apps/native/app/src/screens/notifications/notifications.tsx b/apps/native/app/src/screens/notifications/notifications.tsx index 913c1b6d6d50..0326ee9d0463 100644 --- a/apps/native/app/src/screens/notifications/notifications.tsx +++ b/apps/native/app/src/screens/notifications/notifications.tsx @@ -1,13 +1,4 @@ -import { - Button, - NavigationBarSheet, - NotificationCard, - Problem, - ListItemSkeleton, - EmptyList, -} from '@ui' import { useApolloClient } from '@apollo/client' - import { dismissAllNotificationsAsync } from 'expo-notifications' import React, { useCallback, useEffect, useMemo, useState } from 'react' import { useIntl } from 'react-intl' @@ -24,6 +15,15 @@ import { } from 'react-native-navigation' import { useTheme } from 'styled-components' import styled from 'styled-components/native' + +import { + Button, + NavigationBarSheet, + NotificationCard, + Problem, + ListItemSkeleton, + EmptyList, +} from '../../ui' import { GetUserNotificationsQuery, Notification, @@ -32,7 +32,6 @@ import { useMarkAllNotificationsAsSeenMutation, useMarkUserNotificationAsReadMutation, } from '../../graphql/types/schema' - import { createNavigationOptionHooks } from '../../hooks/create-navigation-option-hooks' import { navigateTo, navigateToUniversalLink } from '../../lib/deep-linking' import { useNotificationsStore } from '../../stores/notifications-store' diff --git a/apps/native/app/src/screens/onboarding/onboarding-biometrics.tsx b/apps/native/app/src/screens/onboarding/onboarding-biometrics.tsx index 96009ba82e73..a8bde5809c16 100644 --- a/apps/native/app/src/screens/onboarding/onboarding-biometrics.tsx +++ b/apps/native/app/src/screens/onboarding/onboarding-biometrics.tsx @@ -1,4 +1,3 @@ -import { Button, CancelButton, Illustration, Onboarding } from '@ui' import { AuthenticationType, authenticateAsync, @@ -8,6 +7,8 @@ import React, { useEffect, useState } from 'react' import { FormattedMessage, useIntl } from 'react-intl' import { AppState, Platform } from 'react-native' import { NavigationFunctionComponent } from 'react-native-navigation' + +import { Button, CancelButton, Illustration, Onboarding } from '../../ui' import finger from '../../assets/icons/finger-16.png' import iris from '../../assets/icons/iris-16.png' import { preferencesStore } from '../../stores/preferences-store' diff --git a/apps/native/app/src/screens/onboarding/onboarding-notifications.tsx b/apps/native/app/src/screens/onboarding/onboarding-notifications.tsx index 6b9a4ec8727a..8b8a59204f6c 100644 --- a/apps/native/app/src/screens/onboarding/onboarding-notifications.tsx +++ b/apps/native/app/src/screens/onboarding/onboarding-notifications.tsx @@ -1,7 +1,8 @@ -import { Button, CancelButton, Illustration, Onboarding } from '@ui' import React from 'react' import { FormattedMessage, useIntl } from 'react-intl' import { NavigationFunctionComponent } from 'react-native-navigation' + +import { Button, CancelButton, Illustration, Onboarding } from '../../ui' import allow from '../../assets/icons/allow.png' import { preferencesStore } from '../../stores/preferences-store' import { nextOnboardingStep } from '../../utils/onboarding' diff --git a/apps/native/app/src/screens/onboarding/onboarding-pin-code.tsx b/apps/native/app/src/screens/onboarding/onboarding-pin-code.tsx index 0c73703ae5c0..132cd806bc37 100644 --- a/apps/native/app/src/screens/onboarding/onboarding-pin-code.tsx +++ b/apps/native/app/src/screens/onboarding/onboarding-pin-code.tsx @@ -1,4 +1,3 @@ -import { CancelButton, dynamicColor, font } from '@ui' import React, { useEffect, useState } from 'react' import { FormattedMessage, useIntl } from 'react-intl' import { Image, SafeAreaView, View } from 'react-native' @@ -8,6 +7,8 @@ import { NavigationFunctionComponent, } from 'react-native-navigation' import styled from 'styled-components/native' + +import { CancelButton, dynamicColor, font } from '../../ui' import logo from '../../assets/logo/logo-64w.png' import { PinKeypad } from '../../components/pin-keypad/pin-keypad' import { VisualizedPinCode } from '../../components/visualized-pin-code/visualized-pin-code' diff --git a/apps/native/app/src/screens/passkey/passkey.tsx b/apps/native/app/src/screens/passkey/passkey.tsx index 6cd5d67130d5..a6ba79951324 100644 --- a/apps/native/app/src/screens/passkey/passkey.tsx +++ b/apps/native/app/src/screens/passkey/passkey.tsx @@ -1,4 +1,3 @@ -import { Button, Typography, NavigationBarSheet, LinkText } from '@ui' import React, { useEffect, useState } from 'react' import { useIntl, FormattedMessage } from 'react-intl' import { @@ -15,6 +14,8 @@ import { Navigation, NavigationFunctionComponent, } from 'react-native-navigation' + +import { Button, Typography, NavigationBarSheet, LinkText } from '../../ui' import { createNavigationOptionHooks } from '../../hooks/create-navigation-option-hooks' import logo from '../../assets/logo/logo-64w.png' import externalLink from '../../assets/icons/external-link.png' diff --git a/apps/native/app/src/screens/settings/edit-bank-info.tsx b/apps/native/app/src/screens/settings/edit-bank-info.tsx index a6de5f0f03e8..ce12824385b5 100644 --- a/apps/native/app/src/screens/settings/edit-bank-info.tsx +++ b/apps/native/app/src/screens/settings/edit-bank-info.tsx @@ -1,4 +1,3 @@ -import { Button, NavigationBarSheet, TextField, Typography } from '@ui' import React, { useEffect } from 'react' import { useIntl } from 'react-intl' import { Alert, ScrollView, View } from 'react-native' @@ -6,6 +5,8 @@ import { Navigation, NavigationFunctionComponent, } from 'react-native-navigation' + +import { Button, NavigationBarSheet, TextField, Typography } from '../../ui' import { useGetProfileQuery } from '../../graphql/types/schema' import { createNavigationOptionHooks } from '../../hooks/create-navigation-option-hooks' import { bankInfoObject, stringifyBankData } from '../../lib/bank-info-helper' diff --git a/apps/native/app/src/screens/settings/edit-confirm.tsx b/apps/native/app/src/screens/settings/edit-confirm.tsx index c624d83e259a..1e201ad189c1 100644 --- a/apps/native/app/src/screens/settings/edit-confirm.tsx +++ b/apps/native/app/src/screens/settings/edit-confirm.tsx @@ -1,10 +1,3 @@ -import { - Button, - CancelButton, - NavigationBarSheet, - TextField, - Typography, -} from '@ui' import React from 'react' import { useIntl } from 'react-intl' import { Alert, ScrollView, View } from 'react-native' @@ -12,6 +5,14 @@ import { Navigation, NavigationFunctionComponent, } from 'react-native-navigation' + +import { + Button, + CancelButton, + NavigationBarSheet, + TextField, + Typography, +} from '../../ui' import { createNavigationOptionHooks } from '../../hooks/create-navigation-option-hooks' import { testIDs } from '../../utils/test-ids' import { useUpdateUserProfile } from './utils/profile-queries' diff --git a/apps/native/app/src/screens/settings/edit-email.tsx b/apps/native/app/src/screens/settings/edit-email.tsx index 12994c6d11f9..649c808bdcd8 100644 --- a/apps/native/app/src/screens/settings/edit-email.tsx +++ b/apps/native/app/src/screens/settings/edit-email.tsx @@ -1,5 +1,4 @@ import { useApolloClient } from '@apollo/client' -import { Button, NavigationBarSheet, TextField, Typography } from '@ui' import React, { useEffect } from 'react' import { useIntl } from 'react-intl' import { Alert, ScrollView, View } from 'react-native' @@ -7,6 +6,8 @@ import { Navigation, NavigationFunctionComponent, } from 'react-native-navigation' + +import { Button, NavigationBarSheet, TextField, Typography } from '../../ui' import { CreateEmailVerificationDocument, CreateEmailVerificationMutation, diff --git a/apps/native/app/src/screens/settings/edit-phone.tsx b/apps/native/app/src/screens/settings/edit-phone.tsx index 88cc19ff8b7b..169e337304ab 100644 --- a/apps/native/app/src/screens/settings/edit-phone.tsx +++ b/apps/native/app/src/screens/settings/edit-phone.tsx @@ -1,5 +1,4 @@ import { useApolloClient } from '@apollo/client' -import { Button, NavigationBarSheet, TextField, Typography } from '@ui' import React, { useEffect, useState } from 'react' import { useIntl } from 'react-intl' import { Alert, ScrollView, View } from 'react-native' @@ -7,6 +6,8 @@ import { Navigation, NavigationFunctionComponent, } from 'react-native-navigation' + +import { Button, NavigationBarSheet, TextField, Typography } from '../../ui' import { CreateSmsVerificationDocument, CreateSmsVerificationMutation, diff --git a/apps/native/app/src/screens/settings/settings.tsx b/apps/native/app/src/screens/settings/settings.tsx index 81f1f49e81c6..3538ec341113 100644 --- a/apps/native/app/src/screens/settings/settings.tsx +++ b/apps/native/app/src/screens/settings/settings.tsx @@ -1,11 +1,4 @@ import { useApolloClient } from '@apollo/client' -import { - Alert, - NavigationBarSheet, - TableViewAccessory, - TableViewCell, - TableViewGroup, -} from '@ui' import { authenticateAsync } from 'expo-local-authentication' import React, { useEffect, useRef, useState } from 'react' import { useIntl } from 'react-intl' @@ -26,6 +19,14 @@ import { NavigationFunctionComponent, } from 'react-native-navigation' import { useTheme } from 'styled-components/native' + +import { + Alert, + NavigationBarSheet, + TableViewAccessory, + TableViewCell, + TableViewGroup, +} from '../../ui' import editIcon from '../../assets/icons/edit.png' import chevronForward from '../../ui/assets/icons/chevron-forward.png' import { PressableHighlight } from '../../components/pressable-highlight/pressable-highlight' diff --git a/apps/native/app/src/screens/update-app/update-app.tsx b/apps/native/app/src/screens/update-app/update-app.tsx index 9f9b06863c1c..b0c498865482 100644 --- a/apps/native/app/src/screens/update-app/update-app.tsx +++ b/apps/native/app/src/screens/update-app/update-app.tsx @@ -1,4 +1,3 @@ -import { Button, Typography, NavigationBarSheet } from '@ui' import React, { useEffect } from 'react' import { useIntl, FormattedMessage } from 'react-intl' import { View, Image, SafeAreaView, Linking } from 'react-native' @@ -7,6 +6,8 @@ import { Navigation, NavigationFunctionComponent, } from 'react-native-navigation' + +import { Button, Typography, NavigationBarSheet } from '../../ui' import { createNavigationOptionHooks } from '../../hooks/create-navigation-option-hooks' import logo from '../../assets/logo/logo-64w.png' import illustrationSrc from '../../assets/illustrations/digital-services-m1-dots.png' diff --git a/apps/native/app/src/screens/vaccinations/components/vaccination-card.tsx b/apps/native/app/src/screens/vaccinations/components/vaccination-card.tsx index 122f512a8ae2..22ed1fc34af6 100644 --- a/apps/native/app/src/screens/vaccinations/components/vaccination-card.tsx +++ b/apps/native/app/src/screens/vaccinations/components/vaccination-card.tsx @@ -1,3 +1,9 @@ +import { useState } from 'react' +import { FormattedMessage, useIntl } from 'react-intl' +import { TouchableOpacity, View } from 'react-native' +import styled, { useTheme } from 'styled-components/native' +import { Markdown } from '../../../ui/lib/markdown/markdown' + import { Badge, ExpandableCard, @@ -5,13 +11,7 @@ import { Skeleton, Typography, dynamicColor, -} from '@ui' -import { useState } from 'react' -import { FormattedMessage, useIntl } from 'react-intl' -import { TouchableOpacity, View } from 'react-native' -import styled, { useTheme } from 'styled-components/native' -import { Markdown } from '../../../ui/lib/markdown/markdown' - +} from '../../../ui' import chevronDown from '../../../assets/icons/chevron-down.png' import clockIcon from '../../../assets/icons/clock.png' import externalLinkIcon from '../../../assets/icons/external-link.png' diff --git a/apps/native/app/src/screens/vaccinations/vaccinations.tsx b/apps/native/app/src/screens/vaccinations/vaccinations.tsx index 61c976ce77a5..388f8a0a98a4 100644 --- a/apps/native/app/src/screens/vaccinations/vaccinations.tsx +++ b/apps/native/app/src/screens/vaccinations/vaccinations.tsx @@ -1,16 +1,16 @@ -import { - GeneralCardSkeleton, - Heading, - Problem, - TabButtons, - Typography, -} from '@ui' import React, { useCallback, useState } from 'react' import { FormattedMessage, useIntl } from 'react-intl' import { RefreshControl, SafeAreaView, ScrollView, View } from 'react-native' import { NavigationFunctionComponent } from 'react-native-navigation' import styled from 'styled-components/native' +import { + GeneralCardSkeleton, + Heading, + Problem, + TabButtons, + Typography, +} from '../../ui' import { useGetVaccinationsQuery } from '../../graphql/types/schema' import { createNavigationOptionHooks } from '../../hooks/create-navigation-option-hooks' import { useConnectivityIndicator } from '../../hooks/use-connectivity-indicator' diff --git a/apps/native/app/src/screens/vehicles/components/mileage-cell.tsx b/apps/native/app/src/screens/vehicles/components/mileage-cell.tsx index 598d50120634..2cb51c87904a 100644 --- a/apps/native/app/src/screens/vehicles/components/mileage-cell.tsx +++ b/apps/native/app/src/screens/vehicles/components/mileage-cell.tsx @@ -1,6 +1,7 @@ -import { Skeleton, Typography, useDynamicColor } from '@ui' import { useIntl } from 'react-intl' import { Image, Pressable, View } from 'react-native' + +import { Skeleton, Typography, useDynamicColor } from '../../../ui' import clock from '../../../assets/icons/clock.png' export function MileageCell({ diff --git a/apps/native/app/src/screens/vehicles/components/vehicle-item.tsx b/apps/native/app/src/screens/vehicles/components/vehicle-item.tsx index afe6b8c607a5..8bb582694dcc 100644 --- a/apps/native/app/src/screens/vehicles/components/vehicle-item.tsx +++ b/apps/native/app/src/screens/vehicles/components/vehicle-item.tsx @@ -1,8 +1,9 @@ -import { Label, VehicleCard } from '@ui' import React from 'react' import { FormattedDate, FormattedMessage } from 'react-intl' import { SafeAreaView, TouchableHighlight, View, ViewStyle } from 'react-native' import styled, { useTheme } from 'styled-components/native' + +import { Label, VehicleCard } from '../../../ui' import { ListVehiclesV2Query } from '../../../graphql/types/schema' import { navigateTo } from '../../../lib/deep-linking' diff --git a/apps/native/app/src/screens/vehicles/vehicle-mileage.screen.tsx b/apps/native/app/src/screens/vehicles/vehicle-mileage.screen.tsx index e6bcd2c9ddf6..b4cfebe8576a 100644 --- a/apps/native/app/src/screens/vehicles/vehicle-mileage.screen.tsx +++ b/apps/native/app/src/screens/vehicles/vehicle-mileage.screen.tsx @@ -1,11 +1,3 @@ -import { - Button, - Divider, - NavigationBarSheet, - TextField, - Typography, - useDynamicColor, -} from '@ui' import { useCallback, useMemo, useState } from 'react' import { FormattedDate, useIntl } from 'react-intl' import { Alert, FlatList, View } from 'react-native' @@ -14,6 +6,14 @@ import { NavigationFunctionComponent, } from 'react-native-navigation' +import { + Button, + Divider, + NavigationBarSheet, + TextField, + Typography, + useDynamicColor, +} from '../../ui' import externalLinkIcon from '../../assets/icons/external-link.png' import { GetVehicleDocument, diff --git a/apps/native/app/src/screens/vehicles/vehicles-detail.tsx b/apps/native/app/src/screens/vehicles/vehicles-detail.tsx index fe31f2262dd8..e580181712ef 100644 --- a/apps/native/app/src/screens/vehicles/vehicles-detail.tsx +++ b/apps/native/app/src/screens/vehicles/vehicles-detail.tsx @@ -1,8 +1,9 @@ -import { Button, Divider, Input, InputRow } from '@ui' import React from 'react' import { useIntl } from 'react-intl' import { ScrollView, Text, View } from 'react-native' import { NavigationFunctionComponent } from 'react-native-navigation' + +import { Button, Divider, Input, InputRow } from '../../ui' import { useGetVehicleQuery } from '../../graphql/types/schema' import { createNavigationOptionHooks } from '../../hooks/create-navigation-option-hooks' import { useConnectivityIndicator } from '../../hooks/use-connectivity-indicator' diff --git a/apps/native/app/src/screens/vehicles/vehicles.tsx b/apps/native/app/src/screens/vehicles/vehicles.tsx index 9922c500edd5..4a4640236ed2 100644 --- a/apps/native/app/src/screens/vehicles/vehicles.tsx +++ b/apps/native/app/src/screens/vehicles/vehicles.tsx @@ -1,4 +1,3 @@ -import { EmptyList, GeneralCardSkeleton, TopLine } from '@ui' import React, { useCallback, useMemo, useRef, useState } from 'react' import { FormattedMessage } from 'react-intl' import { @@ -11,6 +10,8 @@ import { } from 'react-native' import { NavigationFunctionComponent } from 'react-native-navigation' import { useTheme } from 'styled-components/native' + +import { EmptyList, GeneralCardSkeleton, TopLine } from '../../ui' import illustrationSrc from '../../assets/illustrations/le-moving-s4.png' import { BottomTabsIndicator } from '../../components/bottom-tabs-indicator/bottom-tabs-indicator' import { diff --git a/apps/native/app/src/screens/wallet-pass/components/field-render.tsx b/apps/native/app/src/screens/wallet-pass/components/field-render.tsx index 7e1eebbb79e1..70a5e9782072 100644 --- a/apps/native/app/src/screens/wallet-pass/components/field-render.tsx +++ b/apps/native/app/src/screens/wallet-pass/components/field-render.tsx @@ -1,7 +1,8 @@ -import { Field, FieldCard, FieldGroup, FieldLabel, FieldRow } from '@ui' import React from 'react' import { View } from 'react-native' import { useTheme } from 'styled-components' + +import { Field, FieldCard, FieldGroup, FieldLabel, FieldRow } from '../../../ui' import { GenericLicenseDataField, GenericLicenseType, diff --git a/apps/native/app/src/screens/wallet-pass/wallet-pass.tsx b/apps/native/app/src/screens/wallet-pass/wallet-pass.tsx index ed13f963ca81..f8c7ac96c40d 100644 --- a/apps/native/app/src/screens/wallet-pass/wallet-pass.tsx +++ b/apps/native/app/src/screens/wallet-pass/wallet-pass.tsx @@ -1,9 +1,3 @@ -import { - Alert as InfoAlert, - dynamicColor, - LICENSE_CARD_ROW_GAP, - LicenseCard, -} from '@ui' import * as FileSystem from 'expo-file-system' import React, { useCallback, useEffect, useRef, useState } from 'react' import { useIntl } from 'react-intl' @@ -23,6 +17,12 @@ import { NavigationFunctionComponent } from 'react-native-navigation' import PassKit, { AddPassButton } from 'react-native-passkit-wallet' import styled, { useTheme } from 'styled-components/native' +import { + Alert as InfoAlert, + dynamicColor, + LICENSE_CARD_ROW_GAP, + LicenseCard, +} from '../../ui' import { useFeatureFlag } from '../../contexts/feature-flag-provider' import { GenericLicenseType, diff --git a/apps/native/app/src/screens/wallet-passport/wallet-passport.tsx b/apps/native/app/src/screens/wallet-passport/wallet-passport.tsx index a1566d76adca..22d55641864c 100644 --- a/apps/native/app/src/screens/wallet-passport/wallet-passport.tsx +++ b/apps/native/app/src/screens/wallet-passport/wallet-passport.tsx @@ -1,15 +1,3 @@ -import { - Accordion, - AccordionItem, - Alert, - CustomLicenseType, - dynamicColor, - font, - Input, - InputRow, - LicenseCard, - LinkText, -} from '@ui' import React from 'react' import { useIntl } from 'react-intl' import { @@ -21,6 +9,19 @@ import { } from 'react-native' import { NavigationFunctionComponent } from 'react-native-navigation' import styled from 'styled-components/native' + +import { + Accordion, + AccordionItem, + Alert, + CustomLicenseType, + dynamicColor, + font, + Input, + InputRow, + LicenseCard, + LinkText, +} from '../../ui' import IconStatusVerified from '../../assets/icons/valid.png' import IconStatusNonVerified from '../../assets/icons/warning.png' import { useGetIdentityDocumentQuery } from '../../graphql/types/schema' diff --git a/apps/native/app/src/screens/wallet/components/wallet-item.tsx b/apps/native/app/src/screens/wallet/components/wallet-item.tsx index 8a6eec412b70..b510442cbfd7 100644 --- a/apps/native/app/src/screens/wallet/components/wallet-item.tsx +++ b/apps/native/app/src/screens/wallet/components/wallet-item.tsx @@ -1,7 +1,8 @@ -import { CustomLicenseType, LicenseCard } from '@ui' import React from 'react' import { SafeAreaView, ViewStyle } from 'react-native' import styled from 'styled-components/native' + +import { CustomLicenseType, LicenseCard } from '../../../ui' import { Pressable as PressableRaw } from '../../../components/pressable/pressable' import { GenericUserLicense, diff --git a/apps/native/app/src/screens/wallet/wallet.tsx b/apps/native/app/src/screens/wallet/wallet.tsx index 4dd8de8f321d..1e8926e808eb 100644 --- a/apps/native/app/src/screens/wallet/wallet.tsx +++ b/apps/native/app/src/screens/wallet/wallet.tsx @@ -1,5 +1,3 @@ -import { Alert, EmptyList, GeneralCardSkeleton, TopLine } from '@ui' - import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useIntl } from 'react-intl' import { @@ -15,6 +13,7 @@ import SpotlightSearch from 'react-native-spotlight-search' import { useTheme } from 'styled-components/native' import { useNavigationComponentDidAppear } from 'react-native-navigation-hooks' +import { Alert, EmptyList, GeneralCardSkeleton, TopLine } from '../../ui' import illustrationSrc from '../../assets/illustrations/le-retirement-s3.png' import { BottomTabsIndicator } from '../../components/bottom-tabs-indicator/bottom-tabs-indicator' import { diff --git a/apps/native/app/src/test-setup.ts b/apps/native/app/src/test-setup.ts new file mode 100644 index 000000000000..fbf15de85357 --- /dev/null +++ b/apps/native/app/src/test-setup.ts @@ -0,0 +1 @@ +import '@testing-library/jest-native/extend-expect' diff --git a/apps/native/app/src/types/react-native.d.ts b/apps/native/app/src/types/react-native.d.ts index 18e99d85c455..7b0398d64dfc 100644 --- a/apps/native/app/src/types/react-native.d.ts +++ b/apps/native/app/src/types/react-native.d.ts @@ -85,7 +85,3 @@ declare module 'react-native-dialogs' { options: OptionsPrompt, ): Promise<PromptResponse> } - -declare module '@island.is/application/types/lib/ApplicationTypes' { - export const ApplicationConfigurations: Record<string, any> -} diff --git a/apps/native/app/src/types/styled-components.d.ts b/apps/native/app/src/types/styled-components.d.ts index 3f627c3a8c23..050eed343750 100644 --- a/apps/native/app/src/types/styled-components.d.ts +++ b/apps/native/app/src/types/styled-components.d.ts @@ -1,5 +1,5 @@ -import { Theme } from '@ui' import 'styled-components' +import { Theme } from '../ui' declare module 'styled-components' { export interface Shade { diff --git a/apps/native/app/src/ui/index.ts b/apps/native/app/src/ui/index.ts index 870051ba7b87..08e58483874e 100644 --- a/apps/native/app/src/ui/index.ts +++ b/apps/native/app/src/ui/index.ts @@ -20,6 +20,7 @@ export * from './lib/card/welcome-card' export * from './lib/card/link-card' export * from './lib/card/inbox-card' export * from './lib/card/more-card' +export * from './lib/card/air-discount-card' export * from './lib/card/expandable-card' export * from './lib/checkbox/checkbox' export * from './lib/date-picker/date-picker' @@ -61,4 +62,5 @@ export * from './lib/scan-result-card/scan-result-card' export * from './lib/label/label' export * from './lib/progress-meter/progress-meter' export * from './lib/tab-buttons/tab-buttons' +export * from './lib/bullet/bullet' export * from './utils/index' diff --git a/apps/native/app/src/ui/lib/card/license-card.tsx b/apps/native/app/src/ui/lib/card/license-card.tsx index 71f6c5cfa98d..4df3e3d0cbd9 100644 --- a/apps/native/app/src/ui/lib/card/license-card.tsx +++ b/apps/native/app/src/ui/lib/card/license-card.tsx @@ -1,5 +1,3 @@ -import { Barcode } from '@ui/lib/barcode/barcode' -import { Skeleton } from '@ui/lib/skeleton/skeleton' import React from 'react' import { FormattedDate, useIntl } from 'react-intl' import { @@ -10,6 +8,9 @@ import { ViewStyle, } from 'react-native' import styled, { useTheme } from 'styled-components/native' + +import { Barcode } from '../barcode/barcode' +import { Skeleton } from '../skeleton/skeleton' import { ExpirationProgressBar } from '../../../components/progress-bar/expiration-progress-bar' import { GenericLicenseType } from '../../../graphql/types/schema' import { isString } from '../../../utils/is-string' diff --git a/apps/native/app/src/ui/lib/date-picker/date-picker.tsx b/apps/native/app/src/ui/lib/date-picker/date-picker.tsx index d31868bd922f..cd7322b451ce 100644 --- a/apps/native/app/src/ui/lib/date-picker/date-picker.tsx +++ b/apps/native/app/src/ui/lib/date-picker/date-picker.tsx @@ -4,7 +4,7 @@ import DatePicker from 'react-native-date-picker' import calendarIcon from '../../assets/icons/calendar.png' import { Typography } from '../typography/typography' -import { dynamicColor } from '@ui/utils/dynamic-color' +import { dynamicColor } from '../../utils/dynamic-color' import { useIntl } from 'react-intl' import { View, Image } from 'react-native' diff --git a/apps/native/app/src/ui/lib/detail/header.tsx b/apps/native/app/src/ui/lib/detail/header.tsx index b92bd609a363..6348a241d54a 100644 --- a/apps/native/app/src/ui/lib/detail/header.tsx +++ b/apps/native/app/src/ui/lib/detail/header.tsx @@ -4,7 +4,7 @@ import styled from 'styled-components/native' import { dynamicColor } from '../../utils/dynamic-color' import { Skeleton } from '../skeleton/skeleton' import { Typography } from '../typography/typography' -import { Label } from '@ui' +import { Label } from '../label/label' const Host = styled.View<{ hasBorder?: boolean }>` padding-bottom: ${({ theme }) => theme.spacing[1]}px; diff --git a/apps/native/app/src/ui/lib/empty-state/empty-list.tsx b/apps/native/app/src/ui/lib/empty-state/empty-list.tsx index f55706c3abf9..1c67450255dd 100644 --- a/apps/native/app/src/ui/lib/empty-state/empty-list.tsx +++ b/apps/native/app/src/ui/lib/empty-state/empty-list.tsx @@ -2,7 +2,7 @@ import React from 'react' import { View } from 'react-native' import styled from 'styled-components/native' -import { dynamicColor } from '@ui/utils' +import { dynamicColor } from '../../utils' import { Typography } from '../typography/typography' const Host = styled.View` diff --git a/apps/native/app/src/ui/lib/empty-state/empty-state.stories.tsx b/apps/native/app/src/ui/lib/empty-state/empty-state.stories.tsx index e57489d3c65e..0f1f778a85e9 100644 --- a/apps/native/app/src/ui/lib/empty-state/empty-state.stories.tsx +++ b/apps/native/app/src/ui/lib/empty-state/empty-state.stories.tsx @@ -1,8 +1,9 @@ import { text, withKnobs } from '@storybook/addon-knobs' import { storiesOf } from '@storybook/react-native' -import { LinkText } from '@ui' import React from 'react' import { Image, View } from 'react-native' + +import { LinkText } from '../link/link-text' import illustrationSrc from '../../assets/empty-list/LE-Company-S3.png' import leJobss4 from '../../assets/illustrations/le-jobs-s4.png' import { EmptyCard } from './empty-card' diff --git a/apps/native/app/src/ui/lib/input/input.tsx b/apps/native/app/src/ui/lib/input/input.tsx index 19b526329285..fdddd77efb76 100644 --- a/apps/native/app/src/ui/lib/input/input.tsx +++ b/apps/native/app/src/ui/lib/input/input.tsx @@ -2,11 +2,12 @@ import Clipboard from '@react-native-clipboard/clipboard' import React from 'react' import { Image, TouchableOpacity, View } from 'react-native' import styled from 'styled-components/native' + +import { Label } from '../label/label' import CopyIcon from '../../assets/icons/copy.png' import { dynamicColor } from '../../utils' import { Skeleton } from '../skeleton/skeleton' import { Typography } from '../typography/typography' -import { Label } from '@ui' const Host = styled.SafeAreaView<{ noBorder: boolean diff --git a/apps/native/app/src/ui/lib/label/label.tsx b/apps/native/app/src/ui/lib/label/label.tsx index c476dfe7d3ab..f08993c00506 100644 --- a/apps/native/app/src/ui/lib/label/label.tsx +++ b/apps/native/app/src/ui/lib/label/label.tsx @@ -1,6 +1,7 @@ -import { dynamicColor } from '@ui/utils' import { Image } from 'react-native' import styled, { DefaultTheme } from 'styled-components/native' + +import { dynamicColor } from '../../utils' import dangerIcon from '../../assets/alert/danger.png' import infoIcon from '../../assets/alert/info-alert.png' import warningIcon from '../../assets/alert/warning.png' diff --git a/apps/native/app/src/ui/lib/link/link-text.tsx b/apps/native/app/src/ui/lib/link/link-text.tsx index e072ff69a06b..68cac93bcee4 100644 --- a/apps/native/app/src/ui/lib/link/link-text.tsx +++ b/apps/native/app/src/ui/lib/link/link-text.tsx @@ -2,7 +2,7 @@ import React from 'react' import { Image } from 'react-native' import styled, { useTheme } from 'styled-components/native' import { ImageSourcePropType, Text } from 'react-native' -import { fontByWeight } from '@ui/utils' +import { fontByWeight } from '../../utils' const Host = styled.View` border-bottom-width: 1px; diff --git a/apps/native/app/src/ui/lib/list/list-item.tsx b/apps/native/app/src/ui/lib/list/list-item.tsx index 7017ff41282b..e40b5ca67893 100644 --- a/apps/native/app/src/ui/lib/list/list-item.tsx +++ b/apps/native/app/src/ui/lib/list/list-item.tsx @@ -1,9 +1,10 @@ -import { Label, Typography } from '@ui' import React, { isValidElement } from 'react' import { FormattedDate, useIntl } from 'react-intl' import { Image, ImageSourcePropType } from 'react-native' import styled from 'styled-components/native' +import { Typography } from '../typography/typography' +import { Label } from '../label/label' import { dynamicColor } from '../../utils' const Host = styled.SafeAreaView<{ unread?: boolean }>` diff --git a/apps/native/app/src/ui/lib/problem/problem-template.tsx b/apps/native/app/src/ui/lib/problem/problem-template.tsx index 1ebe261c581f..8e263176f4a4 100644 --- a/apps/native/app/src/ui/lib/problem/problem-template.tsx +++ b/apps/native/app/src/ui/lib/problem/problem-template.tsx @@ -1,8 +1,10 @@ -import { Colors, Typography } from '@ui' import { ReactNode } from 'react' import { Image, View } from 'react-native' import styled from 'styled-components/native' +import { Typography } from '../typography/typography' +import { Colors } from '../../utils' + type Variant = 'info' | 'error' | 'warning' export type ProblemTemplateBaseProps = { diff --git a/apps/native/app/src/ui/lib/search-bar/search-bar.tsx b/apps/native/app/src/ui/lib/search-bar/search-bar.tsx index 952643326ad4..1ce1609bfbe1 100644 --- a/apps/native/app/src/ui/lib/search-bar/search-bar.tsx +++ b/apps/native/app/src/ui/lib/search-bar/search-bar.tsx @@ -1,4 +1,3 @@ -import { useDynamicColor } from '@ui/utils' import React, { useRef, useState } from 'react' import { Animated, @@ -12,6 +11,8 @@ import { View, } from 'react-native' import styled, { useTheme } from 'styled-components/native' + +import { useDynamicColor } from '../../utils' import closeIcon from '../../assets/icons/close.png' import searchIcon from '../../assets/icons/search.png' import { font } from '../../utils/font' diff --git a/apps/native/app/src/utils/applications-utils.ts b/apps/native/app/src/utils/applications-utils.ts index c1efd10afed6..93a6c31774cc 100644 --- a/apps/native/app/src/utils/applications-utils.ts +++ b/apps/native/app/src/utils/applications-utils.ts @@ -1,4 +1,4 @@ -import { ApplicationConfigurations } from '@island.is/application/types/lib/ApplicationTypes' +import { ApplicationConfigurations } from '@island.is/application/types' import { getConfig } from '../config' import { Application, diff --git a/apps/native/app/src/utils/get-theme-with-preferences.ts b/apps/native/app/src/utils/get-theme-with-preferences.ts index ebc84f0dace3..dbc84ce835f4 100644 --- a/apps/native/app/src/utils/get-theme-with-preferences.ts +++ b/apps/native/app/src/utils/get-theme-with-preferences.ts @@ -1,6 +1,7 @@ -import { theme } from '@ui' import { Appearance, ColorSchemeName } from 'react-native' + import { AppearanceMode, ThemeMode } from '../stores/preferences-store' +import { theme } from '../ui' export const themes = { light: { diff --git a/apps/native/app/tsconfig.app.json b/apps/native/app/tsconfig.app.json new file mode 100644 index 000000000000..7b1b69d710d7 --- /dev/null +++ b/apps/native/app/tsconfig.app.json @@ -0,0 +1,18 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../../dist/out-tsc", + "types": ["node"] + }, + "files": ["../../../node_modules/@nx/react-native/typings/svg.d.ts"], + "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.js", "src/**/*.jsx"], + "exclude": [ + "**/*.stories.tsx", + "**/*.stories.ts", + "src/screens/devtools/storybook.tsx", + "jest.config.ts", + "src/**/*.spec.ts", + "src/**/*.spec.tsx", + "src/test-setup.ts" + ] +} diff --git a/apps/native/app/tsconfig.json b/apps/native/app/tsconfig.json index 12bba9640d48..6089be1e8395 100644 --- a/apps/native/app/tsconfig.json +++ b/apps/native/app/tsconfig.json @@ -1,18 +1,20 @@ { - "extends": "@tsconfig/react-native/tsconfig.json", + "extends": "../../../tsconfig.base.json", "compilerOptions": { - "baseUrl": ".", - "strict": true, - "paths": { - "@ui": ["src/ui"], - "@ui/*": ["src/ui/*"], - "@island.is/application/types": ["../../libs/application/types/src"], - "@island.is/application/types/*": ["../../libs/application/types/src/*"] - } + "isolatedModules": true, + "jsx": "react-native", + "lib": ["esnext"], + "resolveJsonModule": true, + "declaration": true }, - "exclude": [ - "**/*.stories.tsx", - "**/*.stories.ts", - "src/screens/devtools/storybook.tsx" + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.app.json" + }, + { + "path": "./tsconfig.spec.json" + } ] } diff --git a/apps/native/app/tsconfig.spec.json b/apps/native/app/tsconfig.spec.json new file mode 100644 index 000000000000..8f5c0a003b80 --- /dev/null +++ b/apps/native/app/tsconfig.spec.json @@ -0,0 +1,21 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../../dist/out-tsc", + "module": "commonjs", + "types": ["jest", "node"] + }, + "files": ["src/test-setup.ts"], + "include": [ + "jest.config.ts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.test.tsx", + "src/**/*.spec.tsx", + "src/**/*.test.js", + "src/**/*.spec.js", + "src/**/*.test.jsx", + "src/**/*.spec.jsx", + "src/**/*.d.ts" + ] +} diff --git a/apps/portals/admin/project.json b/apps/portals/admin/project.json index ae909c4cae73..4cbb46369d9e 100644 --- a/apps/portals/admin/project.json +++ b/apps/portals/admin/project.json @@ -96,9 +96,7 @@ "start-bff": { "executor": "nx:run-commands", "options": { - "commands": [ - "node -r esbuild-register src/cli/cli.ts run-local-env services-bff-portals-admin" - ], + "commands": ["yarn infra run-local-env services-bff-portals-admin"], "cwd": "infra" } }, diff --git a/apps/portals/my-pages/project.json b/apps/portals/my-pages/project.json index 285c3d1a352e..592d65f6910d 100644 --- a/apps/portals/my-pages/project.json +++ b/apps/portals/my-pages/project.json @@ -105,9 +105,7 @@ "start-bff": { "executor": "nx:run-commands", "options": { - "commands": [ - "node -r esbuild-register src/cli/cli.ts run-local-env services-bff-portals-my-pages" - ], + "commands": ["yarn infra run-local-env services-bff-portals-my-pages"], "cwd": "infra" } }, diff --git a/apps/portals/my-pages/src/components/Loaders/AuthOverlay/AuthOverlay.tsx b/apps/portals/my-pages/src/components/Loaders/AuthOverlay/AuthOverlay.tsx index 79e6410f2d8f..f186fe7a5d17 100644 --- a/apps/portals/my-pages/src/components/Loaders/AuthOverlay/AuthOverlay.tsx +++ b/apps/portals/my-pages/src/components/Loaders/AuthOverlay/AuthOverlay.tsx @@ -2,11 +2,11 @@ import { Text } from '@island.is/island-ui/core' import { useLocale } from '@island.is/localization' import { m } from '@island.is/portals/my-pages/core' -import { useBff } from '@island.is/react-spa/bff' +import { useAuth } from '@island.is/react-spa/bff' import * as styles from './AuthOverlay.css' const AuthOverlay = () => { - const { authState } = useBff() + const { authState } = useAuth() const { formatMessage } = useLocale() if (authState === 'switching') diff --git a/apps/services/bff/README.md b/apps/services/bff/README.md index a2c7452afc20..c02a7596717c 100644 --- a/apps/services/bff/README.md +++ b/apps/services/bff/README.md @@ -37,3 +37,7 @@ Runs tests with Jest and outputs coverage to `coverage/apps/services/bff`. ## Code owners and maintainers - [Aranja](https://github.com/orgs/island-is/teams/aranja/members) + +## Troubleshooting + +If you encounter any issues while setting up or running the BFF service, please refer to the [Troubleshooting Guide](TROUBLESHOOT_GUIDE.md) diff --git a/apps/services/bff/TROUBLESHOOT_GUIDE.md b/apps/services/bff/TROUBLESHOOT_GUIDE.md new file mode 100644 index 000000000000..c895b9793b41 --- /dev/null +++ b/apps/services/bff/TROUBLESHOOT_GUIDE.md @@ -0,0 +1,37 @@ +# BFF Service Troubleshooting Guide + +## Common Issues and Solutions + +- Make sure that you are running the `dev` script and not `start` script. For example when running the service portal you should run: + `yarn dev service-portal` and not `yarn start service-portal`. + +- Make sure to install dependencies and run code generation with the following command: + `yarn install && yarn codegen`. + +- Make sure to login to the correct AWS account and region: + + - Run `aws sso login --profile islandis-dev` to login to the correct AWS account. + +- Not necessary but a nice to have is to have Redis server running: + + - Run `yarn dev-services services-bff` to start Redis server. + - This is necessary if you want the sessions to persist. + +- Make sure that you are not running the same service twice, since you could already be running a service like API. + Lets take `yarn dev application-system-form` as an example. This script starts the following services: + + - `yarn get-secrets application-system-api` + - `yarn nx run application-system-api:dev-services` + - `yarn nx run application-system-api:migrate` + - `yarn nx codegen/backend-schema api` + - `yarn nx run services-user-profile:dev-init` + - `yarn nx run service-portal:start-bff` + - `yarn start application-system-form` + So make sure that you are not running the same service twice. + +- If you are running the service on Windows, we recommend using [WSL2](https://docs.microsoft.com/en-us/windows/wsl/install) for running the services within the repo. + This is because all of the scripts generated by the `infra` package are not tested on Windows and may not work as expected. + +- On macOS, the AirPlay Receiver is listening on the same port as the Redis server, e.g. 7000. Either change the port or disable the AirPlay Receiver. + +- If everything fails, then a good old computer restart might do the trick. diff --git a/apps/services/user-notification/infra/user-notification.ts b/apps/services/user-notification/infra/user-notification.ts index 96c8788bf22a..4cd6e621c5c1 100644 --- a/apps/services/user-notification/infra/user-notification.ts +++ b/apps/services/user-notification/infra/user-notification.ts @@ -1,16 +1,16 @@ -import { - Base, - Client, - NationalRegistryB2C, - RskCompanyInfo, -} from '../../../../infra/src/dsl/xroad' import { CodeOwners, + ServiceBuilder, json, ref, service, - ServiceBuilder, } from '../../../../infra/src/dsl/dsl' +import { + Base, + Client, + NationalRegistryB2C, + RskCompanyInfo, +} from '../../../../infra/src/dsl/xroad' const serviceName = 'user-notification' const serviceWorkerName = `${serviceName}-worker` @@ -42,6 +42,11 @@ const getEnv = (services: { '@island.is/auth/delegations/index:system', ]), SERVICE_PORTAL_CLICK_ACTION_URL: 'https://island.is/minarsidur', + SERVICE_PORTAL_BFF_LOGIN_URL: { + dev: 'https://beta.dev01.devland.is/bff/login', + staging: 'https://beta.staging01.devland.is/bff/login', + prod: 'https://island.is/bff/login', + }, EMAIL_FROM_ADDRESS: { dev: 'development@island.is', staging: 'development@island.is', diff --git a/apps/services/user-notification/src/app/modules/notifications/notificationsWorker/mocks.ts b/apps/services/user-notification/src/app/modules/notifications/notificationsWorker/mocks.ts index 02638dd0f0f3..62af04a27bb1 100644 --- a/apps/services/user-notification/src/app/modules/notifications/notificationsWorker/mocks.ts +++ b/apps/services/user-notification/src/app/modules/notifications/notificationsWorker/mocks.ts @@ -233,6 +233,7 @@ export const MockUserNotificationsConfig: ConfigType< emailFromAddress: 'development@island.is', isConfigured: true, servicePortalClickActionUrl: 'https://island.is/minarsidur', + servicePortalBffLoginUrl: 'https://island.is/bff/login', redis: { nodes: ['node'], ssl: false, diff --git a/apps/services/user-notification/src/app/modules/notifications/notificationsWorker/notificationsWorker.service.spec.ts b/apps/services/user-notification/src/app/modules/notifications/notificationsWorker/notificationsWorker.service.spec.ts index df1a95d6b828..ab618464b2d7 100644 --- a/apps/services/user-notification/src/app/modules/notifications/notificationsWorker/notificationsWorker.service.spec.ts +++ b/apps/services/user-notification/src/app/modules/notifications/notificationsWorker/notificationsWorker.service.spec.ts @@ -1,29 +1,28 @@ -import { getConnectionToken, getModelToken } from '@nestjs/sequelize' import { INestApplication, Type } from '@nestjs/common' +import { getConnectionToken, getModelToken } from '@nestjs/sequelize' import { TestingModuleBuilder } from '@nestjs/testing' import { Sequelize } from 'sequelize-typescript' -import { testServer, truncate, useDatabase } from '@island.is/testing/nest' -import { UserProfileDto, V2UsersApi } from '@island.is/clients/user-profile' -import { getQueueServiceToken, QueueService } from '@island.is/message-queue' -import { FeatureFlagService } from '@island.is/nest/feature-flags' -import { NationalRegistryV3ClientService } from '@island.is/clients/national-registry-v3' -import { EmailService } from '@island.is/email-service' import { DelegationsApi } from '@island.is/clients/auth/delegation-api' import { CmsService } from '@island.is/clients/cms' +import { NationalRegistryV3ClientService } from '@island.is/clients/national-registry-v3' import { CompanyExtendedInfo, CompanyRegistryClientService, } from '@island.is/clients/rsk/company-registry' +import { UserProfileDto, V2UsersApi } from '@island.is/clients/user-profile' +import { EmailService } from '@island.is/email-service' +import { QueueService, getQueueServiceToken } from '@island.is/message-queue' +import { FeatureFlagService } from '@island.is/nest/feature-flags' +import { testServer, truncate, useDatabase } from '@island.is/testing/nest' import { UserNotificationsConfig } from '../../../../config' import { FIREBASE_PROVIDER } from '../../../../constants' import { AppModule } from '../../../app.module' import { SequelizeConfigService } from '../../../sequelizeConfig.service' -import { NotificationDispatchService } from '../notificationDispatch.service' import { Notification } from '../notification.model' +import { NotificationDispatchService } from '../notificationDispatch.service' import { NotificationsService } from '../notifications.service' -import { NotificationsWorkerService } from './notificationsWorker.service' import { wait } from './helpers' import { MockDelegationsService, @@ -31,19 +30,20 @@ import { MockNationalRegistryV3ClientService, MockUserNotificationsConfig, companyUser, - userWithNoEmail, + delegationSubjectId, + getMockHnippTemplate, + mockTemplateId, + userProfiles, userWithDelegations, userWithDelegations2, userWithDocumentNotificationsDisabled, userWithEmailNotificationsDisabled, userWithFeatureFlagDisabled, - userWithSendToDelegationsFeatureFlagDisabled, - getMockHnippTemplate, - mockTemplateId, - delegationSubjectId, userWithNoDelegations, - userProfiles, + userWithNoEmail, + userWithSendToDelegationsFeatureFlagDisabled, } from './mocks' +import { NotificationsWorkerService } from './notificationsWorker.service' const workingHoursDelta = 1000 * 60 * 60 // 1 hour const insideWorkingHours = new Date(2021, 1, 1, 9, 0, 0) @@ -303,7 +303,7 @@ describe('NotificationsWorkerService', () => { expect.objectContaining({ component: 'ImageWithLink', context: expect.objectContaining({ - href: `https://island.is/minarsidur/login?login_hint=${delegationSubjectId}&target_link_uri=https://island.is/minarsidur/postholf`, + href: `https://island.is/bff/login?login_hint=${delegationSubjectId}&target_link_uri=https://island.is/minarsidur/postholf`, }), }), ]), diff --git a/apps/services/user-notification/src/app/modules/notifications/notificationsWorker/notificationsWorker.service.ts b/apps/services/user-notification/src/app/modules/notifications/notificationsWorker/notificationsWorker.service.ts index fd353149f63d..d0d9ae9368fd 100644 --- a/apps/services/user-notification/src/app/modules/notifications/notificationsWorker/notificationsWorker.service.ts +++ b/apps/services/user-notification/src/app/modules/notifications/notificationsWorker/notificationsWorker.service.ts @@ -1,21 +1,20 @@ import { Inject, Injectable, OnApplicationBootstrap } from '@nestjs/common' -import { join } from 'path' import { InjectModel } from '@nestjs/sequelize' import { isCompany } from 'kennitala' +import { join } from 'path' import { User } from '@island.is/auth-nest-tools' import { DocumentsScope } from '@island.is/auth/scopes' +import { DelegationsApi } from '@island.is/clients/auth/delegation-api' import { - EinstaklingurDTONafnAllt, EinstaklingurDTONafnItar, NationalRegistryV3ClientService, } from '@island.is/clients/national-registry-v3' import { + ActorProfileDto, UserProfileDto, V2UsersApi, - ActorProfileDto, } from '@island.is/clients/user-profile' -import { DelegationsApi } from '@island.is/clients/auth/delegation-api' import { Body, EmailService, Message } from '@island.is/email-service' import type { Logger } from '@island.is/logging' import { LOGGER_PROVIDER } from '@island.is/logging' @@ -29,17 +28,17 @@ import { type ConfigType } from '@island.is/nest/config' import { FeatureFlagService, Features } from '@island.is/nest/feature-flags' import type { Locale } from '@island.is/shared/types' -import { UserNotificationsConfig } from '../../../../config' -import { MessageProcessorService } from '../messageProcessor.service' -import { NotificationDispatchService } from '../notificationDispatch.service' -import { CreateHnippNotificationDto } from '../dto/createHnippNotification.dto' -import { NotificationsService } from '../notifications.service' -import { HnippTemplate } from '../dto/hnippTemplate.response' -import { Notification } from '../notification.model' import { CompanyExtendedInfo, CompanyRegistryClientService, } from '@island.is/clients/rsk/company-registry' +import { UserNotificationsConfig } from '../../../../config' +import { CreateHnippNotificationDto } from '../dto/createHnippNotification.dto' +import { HnippTemplate } from '../dto/hnippTemplate.response' +import { MessageProcessorService } from '../messageProcessor.service' +import { Notification } from '../notification.model' +import { NotificationDispatchService } from '../notificationDispatch.service' +import { NotificationsService } from '../notifications.service' type HandleNotification = { profile: { @@ -475,8 +474,8 @@ export class NotificationsWorkerService { return shouldUseThirdPartyLogin ? `${ - this.config.servicePortalClickActionUrl - }/login?login_hint=${subjectId}&target_link_uri=${encodeURI( + this.config.servicePortalBffLoginUrl + }?login_hint=${subjectId}&target_link_uri=${encodeURI( formattedTemplate.clickActionUrl, )}` : formattedTemplate.clickActionUrl diff --git a/apps/services/user-notification/src/config.ts b/apps/services/user-notification/src/config.ts index ee1134be1e34..0ba2d9017bcb 100644 --- a/apps/services/user-notification/src/config.ts +++ b/apps/services/user-notification/src/config.ts @@ -1,13 +1,14 @@ import { z } from 'zod' -import { defineConfig } from '@island.is/nest/config' import { processJob } from '@island.is/infra-nest-server' +import { defineConfig } from '@island.is/nest/config' // Exported for testing purposes export const schema = z.object({ isWorker: z.boolean(), firebaseCredentials: z.string(), servicePortalClickActionUrl: z.string(), + servicePortalBffLoginUrl: z.string(), contentfulAccessToken: z.string(), emailFromAddress: z.string(), redis: z.object({ @@ -27,6 +28,9 @@ export const UserNotificationsConfig = defineConfig({ servicePortalClickActionUrl: env.optional('SERVICE_PORTAL_CLICK_ACTION_URL') ?? 'https://island.is/minarsidur', + servicePortalBffLoginUrl: + env.optional('SERVICE_PORTAL_BFF_LOGIN_URL') ?? + 'https://island.is/bff/login', contentfulAccessToken: env.optional('CONTENTFUL_ACCESS_TOKEN', ''), emailFromAddress: env.required( 'EMAIL_FROM_ADDRESS', diff --git a/apps/services/user-notification/test/environment.ts b/apps/services/user-notification/test/environment.ts index 77956bffa36e..d7605cd63d56 100644 --- a/apps/services/user-notification/test/environment.ts +++ b/apps/services/user-notification/test/environment.ts @@ -5,6 +5,7 @@ export const environment = { SQS_ACCESS_KEY: 'testing', SQS_SECRET_ACCESS_KEY: 'testing', SERVICE_PORTAL_CLICK_ACTION_URL: 'https://island.is/minarsidur', + SERVICE_PORTAL_BFF_LOGIN_URL: 'https://island.is/bff/login', // Disable redis registration during testing REDIS_URL_NODE_01: '[]', } as const diff --git a/apps/web/components/GenericList/GenericList.tsx b/apps/web/components/GenericList/GenericList.tsx index bbc7592e5172..3bc8dfc8ba48 100644 --- a/apps/web/components/GenericList/GenericList.tsx +++ b/apps/web/components/GenericList/GenericList.tsx @@ -15,6 +15,7 @@ import { GridColumn, GridContainer, GridRow, + Hyphen, Icon, type IconProps, Inline, @@ -85,9 +86,11 @@ export const NonClickableItem = ({ item }: ItemProps) => { <Stack space={3}> <Stack space={0}> <Stack space={0}> - <Text variant="eyebrow" color="purple400"> - {item.date && format(new Date(item.date), 'dd.MM.yyyy')} - </Text> + {item.date && ( + <Text variant="eyebrow" color="purple400"> + {format(new Date(item.date), 'dd.MM.yyyy')} + </Text> + )} <Text variant="h3" as="span" color="dark400"> {item.title} </Text> @@ -153,24 +156,48 @@ export const ClickableItem = ({ item, baseUrl }: ClickableItemProps) => { <Stack space={3}> <Box width="full"> <Box width="full"> - <Box className={styles.clickableItemTopRowContainer}> - <Inline space={2} justifyContent="spaceBetween"> - <Text variant="eyebrow" color="purple400"> - {item.date && format(new Date(item.date), 'dd.MM.yyyy')} + {item.date && ( + <Box className={styles.clickableItemTopRowContainer}> + <Inline space={2} justifyContent="spaceBetween"> + <Text variant="eyebrow" color="purple400"> + {format(new Date(item.date), 'dd.MM.yyyy')} + </Text> + {icon && ( + <Icon + size="medium" + type="outline" + color="blue400" + icon={icon} + /> + )} + </Inline> + </Box> + )} + <GridRow> + <GridColumn + span={ + !item.date && icon + ? ['10/12', '10/12', '10/12', '10/12', '11/12'] + : '1/1' + } + > + <Text variant="h3" as="span" color="blue400"> + <Hyphen>{item.title}</Hyphen> </Text> - {icon && ( - <Icon - size="medium" - type="outline" - color="blue400" - icon={icon} - /> - )} - </Inline> - </Box> - <Text variant="h3" as="span" color="blue400"> - {item.title} - </Text> + </GridColumn> + {!item.date && icon && ( + <GridColumn span={['2/12', '2/12', '2/12', '2/12', '1/12']}> + <Box display="flex" justifyContent="flexEnd"> + <Icon + size="medium" + type="outline" + color="blue400" + icon={icon} + /> + </Box> + </GridColumn> + )} + </GridRow> </Box> {item.cardIntro?.length > 0 && ( <Box>{webRichText(item.cardIntro ?? [])}</Box> diff --git a/apps/web/components/Header/LoginButton.css.ts b/apps/web/components/Header/LoginButton.css.ts deleted file mode 100644 index 7a7a92b0078b..000000000000 --- a/apps/web/components/Header/LoginButton.css.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { style } from '@vanilla-extract/css' - -export const dropdownMenu = style({ - width: 193, -}) diff --git a/apps/web/components/Header/LoginButton.tsx b/apps/web/components/Header/LoginButton.tsx index a7e696641095..7dc22605409b 100644 --- a/apps/web/components/Header/LoginButton.tsx +++ b/apps/web/components/Header/LoginButton.tsx @@ -15,10 +15,8 @@ import { webLoginButtonSelect } from '@island.is/plausible' import { useI18n } from '@island.is/web/i18n' import { LayoutProps } from '@island.is/web/layouts/main' -import * as styles from './LoginButton.css' - const minarsidurLink = '/minarsidur/' -const minarsidurDelegationsLink = '/minarsidur/login?prompt=select_account' +const minarsidurDelegationsLink = '/bff/login?prompt=select_account' export function LoginButton(props: { colorScheme: ButtonTypes['colorScheme'] @@ -116,7 +114,6 @@ export function LoginButton(props: { return ( <DropdownMenu fixed - menuClassName={cn({ [styles.dropdownMenu]: Boolean(props.topItem) })} disclosure={ <Button colorScheme={props.colorScheme} diff --git a/apps/web/components/Organization/Wrapper/OrganizationWrapper.tsx b/apps/web/components/Organization/Wrapper/OrganizationWrapper.tsx index 44e38cb37d59..9c68d0398745 100644 --- a/apps/web/components/Organization/Wrapper/OrganizationWrapper.tsx +++ b/apps/web/components/Organization/Wrapper/OrganizationWrapper.tsx @@ -1,6 +1,7 @@ import React, { PropsWithChildren, ReactNode, + useContext, useEffect, useMemo, useState, @@ -44,6 +45,7 @@ import { } from '@island.is/web/components' import { DefaultHeader, WatsonChatPanel } from '@island.is/web/components' import { SLICE_SPACING, STICKY_NAV_MAX_WIDTH } from '@island.is/web/constants' +import { GlobalContext } from '@island.is/web/context' import { Image, Organization, @@ -712,6 +714,8 @@ export const OrganizationFooter: React.FC< let OrganizationFooterComponent = null + const { isServiceWeb } = useContext(GlobalContext) + switch (organization?.slug) { case 'syslumenn': case 'district-commissioner': @@ -910,6 +914,29 @@ export const OrganizationFooter: React.FC< </> ) break + case 'vinnueftirlitid': + case 'aosh': + { + const footerItems = organization?.footerItems ?? [] + if (footerItems.length === 0) break + OrganizationFooterComponent = ( + <WebFooter + heading={organization?.title ?? ''} + columns={footerItems} + background={ + isServiceWeb + ? theme.color.purple100 + : organization?.footerConfig?.background + } + color={ + isServiceWeb + ? theme.color.dark400 + : organization?.footerConfig?.textColor + } + /> + ) + } + break default: { const footerItems = organization?.footerItems ?? [] if (footerItems.length === 0) break diff --git a/apps/web/components/connected/WHODAS/Calculator.tsx b/apps/web/components/connected/WHODAS/Calculator.tsx index fc96577ef4f3..cb2294fda8c5 100644 --- a/apps/web/components/connected/WHODAS/Calculator.tsx +++ b/apps/web/components/connected/WHODAS/Calculator.tsx @@ -7,6 +7,7 @@ import { } from 'react' import { useIntl } from 'react-intl' import round from 'lodash/round' +import { parseAsInteger, useQueryState } from 'next-usequerystate' import { Box, @@ -235,7 +236,15 @@ interface WHODASCalculatorProps { } export const WHODASCalculator = ({ slice }: WHODASCalculatorProps) => { - const [stepIndex, setStepIndex] = useState(0) + const [stepIndex, setStepIndex] = useQueryState( + 'stepIndex', + parseAsInteger + .withOptions({ + history: 'push', + clearOnDefault: true, + }) + .withDefault(0), + ) const formRef = useRef<HTMLDivElement | null>(null) const steps = (slice.json?.steps ?? []) as Step[] const initialRender = useRef(true) @@ -266,6 +275,7 @@ export const WHODASCalculator = ({ slice }: WHODASCalculatorProps) => { useEffect(() => { if (initialRender.current) { + setStepIndex(0) // Reset step index on initial render initialRender.current = false return } @@ -273,7 +283,7 @@ export const WHODASCalculator = ({ slice }: WHODASCalculatorProps) => { behavior: 'smooth', top: formRef.current?.offsetTop ?? 0, }) - }, [stepIndex]) + }, [setStepIndex, stepIndex]) if (showResults) { let totalScore = 0 diff --git a/charts/islandis/values.dev.yaml b/charts/islandis/values.dev.yaml index 3ff1c6643bbd..e0f5e81d1937 100644 --- a/charts/islandis/values.dev.yaml +++ b/charts/islandis/values.dev.yaml @@ -3184,6 +3184,7 @@ user-notification: REDIS_URL_NODE_01: '["clustercfg.general-redis-cluster-group.5fzau3.euw1.cache.amazonaws.com:6379"]' REDIS_USE_SSL: 'true' SERVERSIDE_FEATURES_ON: '' + SERVICE_PORTAL_BFF_LOGIN_URL: 'https://beta.dev01.devland.is/bff/login' SERVICE_PORTAL_CLICK_ACTION_URL: 'https://island.is/minarsidur' USER_PROFILE_CLIENT_URL: 'http://web-service-portal-api.service-portal.svc.cluster.local' XROAD_BASE_PATH: 'http://securityserver.dev01.devland.is' @@ -3387,6 +3388,7 @@ user-notification-worker: REDIS_URL_NODE_01: '["clustercfg.general-redis-cluster-group.5fzau3.euw1.cache.amazonaws.com:6379"]' REDIS_USE_SSL: 'true' SERVERSIDE_FEATURES_ON: '' + SERVICE_PORTAL_BFF_LOGIN_URL: 'https://beta.dev01.devland.is/bff/login' SERVICE_PORTAL_CLICK_ACTION_URL: 'https://island.is/minarsidur' USER_PROFILE_CLIENT_URL: 'http://web-service-portal-api.service-portal.svc.cluster.local' XROAD_BASE_PATH: 'http://securityserver.dev01.devland.is' diff --git a/charts/islandis/values.prod.yaml b/charts/islandis/values.prod.yaml index 3d4924f04ccd..d08668a62457 100644 --- a/charts/islandis/values.prod.yaml +++ b/charts/islandis/values.prod.yaml @@ -3063,6 +3063,7 @@ user-notification: REDIS_URL_NODE_01: '["clustercfg.general-redis-cluster-group.whakos.euw1.cache.amazonaws.com:6379"]' REDIS_USE_SSL: 'true' SERVERSIDE_FEATURES_ON: 'driving-license-use-v1-endpoint-for-v2-comms' + SERVICE_PORTAL_BFF_LOGIN_URL: 'https://island.is/bff/login' SERVICE_PORTAL_CLICK_ACTION_URL: 'https://island.is/minarsidur' USER_PROFILE_CLIENT_URL: 'http://web-service-portal-api.service-portal.svc.cluster.local' XROAD_BASE_PATH: 'http://securityserver.island.is' @@ -3266,6 +3267,7 @@ user-notification-worker: REDIS_URL_NODE_01: '["clustercfg.general-redis-cluster-group.whakos.euw1.cache.amazonaws.com:6379"]' REDIS_USE_SSL: 'true' SERVERSIDE_FEATURES_ON: 'driving-license-use-v1-endpoint-for-v2-comms' + SERVICE_PORTAL_BFF_LOGIN_URL: 'https://island.is/bff/login' SERVICE_PORTAL_CLICK_ACTION_URL: 'https://island.is/minarsidur' USER_PROFILE_CLIENT_URL: 'http://web-service-portal-api.service-portal.svc.cluster.local' XROAD_BASE_PATH: 'http://securityserver.island.is' diff --git a/charts/islandis/values.staging.yaml b/charts/islandis/values.staging.yaml index 311d39020959..2cd78ddb6714 100644 --- a/charts/islandis/values.staging.yaml +++ b/charts/islandis/values.staging.yaml @@ -2922,6 +2922,7 @@ user-notification: REDIS_URL_NODE_01: '["clustercfg.general-redis-cluster-group.ab9ckb.euw1.cache.amazonaws.com:6379"]' REDIS_USE_SSL: 'true' SERVERSIDE_FEATURES_ON: '' + SERVICE_PORTAL_BFF_LOGIN_URL: 'https://beta.staging01.devland.is/bff/login' SERVICE_PORTAL_CLICK_ACTION_URL: 'https://island.is/minarsidur' USER_PROFILE_CLIENT_URL: 'http://web-service-portal-api.service-portal.svc.cluster.local' XROAD_BASE_PATH: 'http://securityserver.staging01.devland.is' @@ -3125,6 +3126,7 @@ user-notification-worker: REDIS_URL_NODE_01: '["clustercfg.general-redis-cluster-group.ab9ckb.euw1.cache.amazonaws.com:6379"]' REDIS_USE_SSL: 'true' SERVERSIDE_FEATURES_ON: '' + SERVICE_PORTAL_BFF_LOGIN_URL: 'https://beta.staging01.devland.is/bff/login' SERVICE_PORTAL_CLICK_ACTION_URL: 'https://island.is/minarsidur' USER_PROFILE_CLIENT_URL: 'http://web-service-portal-api.service-portal.svc.cluster.local' XROAD_BASE_PATH: 'http://securityserver.staging01.devland.is' diff --git a/charts/judicial-system/values.dev.yaml b/charts/judicial-system/values.dev.yaml index dd58423affdc..c5d0f700d69b 100644 --- a/charts/judicial-system/values.dev.yaml +++ b/charts/judicial-system/values.dev.yaml @@ -225,6 +225,7 @@ judicial-system-backend: NOVA_PASSWORD: '/k8s/judicial-system/NOVA_PASSWORD' NOVA_URL: '/k8s/judicial-system/NOVA_URL' NOVA_USERNAME: '/k8s/judicial-system/NOVA_USERNAME' + POLICE_INSTITUTIONS_EMAILS: '/k8s/judicial-system/POLICE_INSTITUTIONS_EMAILS' PRISON_ADMIN_EMAIL: '/k8s/judicial-system/PRISON_ADMIN_EMAIL' PRISON_ADMIN_INDICTMENT_EMAILS: '/k8s/judicial-system/PRISON_ADMIN_INDICTMENT_EMAILS' PRISON_EMAIL: '/k8s/judicial-system/PRISON_EMAIL' diff --git a/charts/judicial-system/values.prod.yaml b/charts/judicial-system/values.prod.yaml index dc74c1e64d61..e0747b1254c4 100644 --- a/charts/judicial-system/values.prod.yaml +++ b/charts/judicial-system/values.prod.yaml @@ -30,7 +30,7 @@ judicial-system-api: BACKEND_URL: 'http://web-judicial-system-backend' CONTENTFUL_ENVIRONMENT: 'master' CONTENTFUL_HOST: 'cdn.contentful.com' - HIDDEN_FEATURES: '' + HIDDEN_FEATURES: 'MULTIPLE_INDICTMENT_SUBTYPES' IDENTITY_SERVER_ISSUER_URL: 'https://innskra.island.is' LOG_LEVEL: 'info' NODE_OPTIONS: '--max-old-space-size=460 -r dd-trace/init' @@ -225,6 +225,7 @@ judicial-system-backend: NOVA_PASSWORD: '/k8s/judicial-system/NOVA_PASSWORD' NOVA_URL: '/k8s/judicial-system/NOVA_URL' NOVA_USERNAME: '/k8s/judicial-system/NOVA_USERNAME' + POLICE_INSTITUTIONS_EMAILS: '/k8s/judicial-system/POLICE_INSTITUTIONS_EMAILS' PRISON_ADMIN_EMAIL: '/k8s/judicial-system/PRISON_ADMIN_EMAIL' PRISON_ADMIN_INDICTMENT_EMAILS: '/k8s/judicial-system/PRISON_ADMIN_INDICTMENT_EMAILS' PRISON_EMAIL: '/k8s/judicial-system/PRISON_EMAIL' diff --git a/charts/judicial-system/values.staging.yaml b/charts/judicial-system/values.staging.yaml index 3dacb3f6fb4a..e6b543a61ce6 100644 --- a/charts/judicial-system/values.staging.yaml +++ b/charts/judicial-system/values.staging.yaml @@ -30,7 +30,7 @@ judicial-system-api: BACKEND_URL: 'http://web-judicial-system-backend' CONTENTFUL_ENVIRONMENT: 'test' CONTENTFUL_HOST: 'cdn.contentful.com' - HIDDEN_FEATURES: '' + HIDDEN_FEATURES: 'MULTIPLE_INDICTMENT_SUBTYPES' IDENTITY_SERVER_ISSUER_URL: 'https://identity-server.staging01.devland.is' LOG_LEVEL: 'info' NODE_OPTIONS: '--max-old-space-size=460 -r dd-trace/init' @@ -225,6 +225,7 @@ judicial-system-backend: NOVA_PASSWORD: '/k8s/judicial-system/NOVA_PASSWORD' NOVA_URL: '/k8s/judicial-system/NOVA_URL' NOVA_USERNAME: '/k8s/judicial-system/NOVA_USERNAME' + POLICE_INSTITUTIONS_EMAILS: '/k8s/judicial-system/POLICE_INSTITUTIONS_EMAILS' PRISON_ADMIN_EMAIL: '/k8s/judicial-system/PRISON_ADMIN_EMAIL' PRISON_ADMIN_INDICTMENT_EMAILS: '/k8s/judicial-system/PRISON_ADMIN_INDICTMENT_EMAILS' PRISON_EMAIL: '/k8s/judicial-system/PRISON_EMAIL' diff --git a/charts/services/judicial-system-api/values.prod.yaml b/charts/services/judicial-system-api/values.prod.yaml index 2143dcd8565b..05f04f9d7278 100644 --- a/charts/services/judicial-system-api/values.prod.yaml +++ b/charts/services/judicial-system-api/values.prod.yaml @@ -30,7 +30,7 @@ env: BACKEND_URL: 'http://web-judicial-system-backend' CONTENTFUL_ENVIRONMENT: 'master' CONTENTFUL_HOST: 'cdn.contentful.com' - HIDDEN_FEATURES: '' + HIDDEN_FEATURES: 'MULTIPLE_INDICTMENT_SUBTYPES' IDENTITY_SERVER_ISSUER_URL: 'https://innskra.island.is' LOG_LEVEL: 'info' NODE_OPTIONS: '--max-old-space-size=460 -r dd-trace/init' diff --git a/charts/services/judicial-system-api/values.staging.yaml b/charts/services/judicial-system-api/values.staging.yaml index 3d6002b0a864..7533d12090a6 100644 --- a/charts/services/judicial-system-api/values.staging.yaml +++ b/charts/services/judicial-system-api/values.staging.yaml @@ -30,7 +30,7 @@ env: BACKEND_URL: 'http://web-judicial-system-backend' CONTENTFUL_ENVIRONMENT: 'test' CONTENTFUL_HOST: 'cdn.contentful.com' - HIDDEN_FEATURES: '' + HIDDEN_FEATURES: 'MULTIPLE_INDICTMENT_SUBTYPES' IDENTITY_SERVER_ISSUER_URL: 'https://identity-server.staging01.devland.is' LOG_LEVEL: 'info' NODE_OPTIONS: '--max-old-space-size=460 -r dd-trace/init' diff --git a/charts/services/judicial-system-backend/values.dev.yaml b/charts/services/judicial-system-backend/values.dev.yaml index 8aeb2656041e..df2ab994c0bc 100644 --- a/charts/services/judicial-system-backend/values.dev.yaml +++ b/charts/services/judicial-system-backend/values.dev.yaml @@ -138,6 +138,7 @@ secrets: NOVA_PASSWORD: '/k8s/judicial-system/NOVA_PASSWORD' NOVA_URL: '/k8s/judicial-system/NOVA_URL' NOVA_USERNAME: '/k8s/judicial-system/NOVA_USERNAME' + POLICE_INSTITUTIONS_EMAILS: '/k8s/judicial-system/POLICE_INSTITUTIONS_EMAILS' PRISON_ADMIN_EMAIL: '/k8s/judicial-system/PRISON_ADMIN_EMAIL' PRISON_ADMIN_INDICTMENT_EMAILS: '/k8s/judicial-system/PRISON_ADMIN_INDICTMENT_EMAILS' PRISON_EMAIL: '/k8s/judicial-system/PRISON_EMAIL' diff --git a/charts/services/judicial-system-backend/values.prod.yaml b/charts/services/judicial-system-backend/values.prod.yaml index 2807d821b9d9..d0047fef3de8 100644 --- a/charts/services/judicial-system-backend/values.prod.yaml +++ b/charts/services/judicial-system-backend/values.prod.yaml @@ -138,6 +138,7 @@ secrets: NOVA_PASSWORD: '/k8s/judicial-system/NOVA_PASSWORD' NOVA_URL: '/k8s/judicial-system/NOVA_URL' NOVA_USERNAME: '/k8s/judicial-system/NOVA_USERNAME' + POLICE_INSTITUTIONS_EMAILS: '/k8s/judicial-system/POLICE_INSTITUTIONS_EMAILS' PRISON_ADMIN_EMAIL: '/k8s/judicial-system/PRISON_ADMIN_EMAIL' PRISON_ADMIN_INDICTMENT_EMAILS: '/k8s/judicial-system/PRISON_ADMIN_INDICTMENT_EMAILS' PRISON_EMAIL: '/k8s/judicial-system/PRISON_EMAIL' diff --git a/charts/services/judicial-system-backend/values.staging.yaml b/charts/services/judicial-system-backend/values.staging.yaml index 7221f2acfd51..30787a83c52d 100644 --- a/charts/services/judicial-system-backend/values.staging.yaml +++ b/charts/services/judicial-system-backend/values.staging.yaml @@ -138,6 +138,7 @@ secrets: NOVA_PASSWORD: '/k8s/judicial-system/NOVA_PASSWORD' NOVA_URL: '/k8s/judicial-system/NOVA_URL' NOVA_USERNAME: '/k8s/judicial-system/NOVA_USERNAME' + POLICE_INSTITUTIONS_EMAILS: '/k8s/judicial-system/POLICE_INSTITUTIONS_EMAILS' PRISON_ADMIN_EMAIL: '/k8s/judicial-system/PRISON_ADMIN_EMAIL' PRISON_ADMIN_INDICTMENT_EMAILS: '/k8s/judicial-system/PRISON_ADMIN_INDICTMENT_EMAILS' PRISON_EMAIL: '/k8s/judicial-system/PRISON_EMAIL' diff --git a/charts/services/user-notification-worker/values.dev.yaml b/charts/services/user-notification-worker/values.dev.yaml index 2a0ec43d43f3..ee126139b967 100644 --- a/charts/services/user-notification-worker/values.dev.yaml +++ b/charts/services/user-notification-worker/values.dev.yaml @@ -49,6 +49,7 @@ env: REDIS_URL_NODE_01: '["clustercfg.general-redis-cluster-group.5fzau3.euw1.cache.amazonaws.com:6379"]' REDIS_USE_SSL: 'true' SERVERSIDE_FEATURES_ON: '' + SERVICE_PORTAL_BFF_LOGIN_URL: 'https://beta.dev01.devland.is/bff/login' SERVICE_PORTAL_CLICK_ACTION_URL: 'https://island.is/minarsidur' USER_PROFILE_CLIENT_URL: 'http://web-service-portal-api.service-portal.svc.cluster.local' XROAD_BASE_PATH: 'http://securityserver.dev01.devland.is' diff --git a/charts/services/user-notification-worker/values.prod.yaml b/charts/services/user-notification-worker/values.prod.yaml index 6ab822a0aa50..dc8b5e5d2697 100644 --- a/charts/services/user-notification-worker/values.prod.yaml +++ b/charts/services/user-notification-worker/values.prod.yaml @@ -49,6 +49,7 @@ env: REDIS_URL_NODE_01: '["clustercfg.general-redis-cluster-group.whakos.euw1.cache.amazonaws.com:6379"]' REDIS_USE_SSL: 'true' SERVERSIDE_FEATURES_ON: 'driving-license-use-v1-endpoint-for-v2-comms' + SERVICE_PORTAL_BFF_LOGIN_URL: 'https://island.is/bff/login' SERVICE_PORTAL_CLICK_ACTION_URL: 'https://island.is/minarsidur' USER_PROFILE_CLIENT_URL: 'http://web-service-portal-api.service-portal.svc.cluster.local' XROAD_BASE_PATH: 'http://securityserver.island.is' diff --git a/charts/services/user-notification-worker/values.staging.yaml b/charts/services/user-notification-worker/values.staging.yaml index 4d38939c4667..ce46946924d2 100644 --- a/charts/services/user-notification-worker/values.staging.yaml +++ b/charts/services/user-notification-worker/values.staging.yaml @@ -49,6 +49,7 @@ env: REDIS_URL_NODE_01: '["clustercfg.general-redis-cluster-group.ab9ckb.euw1.cache.amazonaws.com:6379"]' REDIS_USE_SSL: 'true' SERVERSIDE_FEATURES_ON: '' + SERVICE_PORTAL_BFF_LOGIN_URL: 'https://beta.staging01.devland.is/bff/login' SERVICE_PORTAL_CLICK_ACTION_URL: 'https://island.is/minarsidur' USER_PROFILE_CLIENT_URL: 'http://web-service-portal-api.service-portal.svc.cluster.local' XROAD_BASE_PATH: 'http://securityserver.staging01.devland.is' diff --git a/charts/services/user-notification/values.dev.yaml b/charts/services/user-notification/values.dev.yaml index 2a4c2df02f1d..f6ba4ec5469a 100644 --- a/charts/services/user-notification/values.dev.yaml +++ b/charts/services/user-notification/values.dev.yaml @@ -46,6 +46,7 @@ env: REDIS_URL_NODE_01: '["clustercfg.general-redis-cluster-group.5fzau3.euw1.cache.amazonaws.com:6379"]' REDIS_USE_SSL: 'true' SERVERSIDE_FEATURES_ON: '' + SERVICE_PORTAL_BFF_LOGIN_URL: 'https://beta.dev01.devland.is/bff/login' SERVICE_PORTAL_CLICK_ACTION_URL: 'https://island.is/minarsidur' USER_PROFILE_CLIENT_URL: 'http://web-service-portal-api.service-portal.svc.cluster.local' XROAD_BASE_PATH: 'http://securityserver.dev01.devland.is' diff --git a/charts/services/user-notification/values.prod.yaml b/charts/services/user-notification/values.prod.yaml index 00cf09f12ae3..bb75838fa119 100644 --- a/charts/services/user-notification/values.prod.yaml +++ b/charts/services/user-notification/values.prod.yaml @@ -46,6 +46,7 @@ env: REDIS_URL_NODE_01: '["clustercfg.general-redis-cluster-group.whakos.euw1.cache.amazonaws.com:6379"]' REDIS_USE_SSL: 'true' SERVERSIDE_FEATURES_ON: 'driving-license-use-v1-endpoint-for-v2-comms' + SERVICE_PORTAL_BFF_LOGIN_URL: 'https://island.is/bff/login' SERVICE_PORTAL_CLICK_ACTION_URL: 'https://island.is/minarsidur' USER_PROFILE_CLIENT_URL: 'http://web-service-portal-api.service-portal.svc.cluster.local' XROAD_BASE_PATH: 'http://securityserver.island.is' diff --git a/charts/services/user-notification/values.staging.yaml b/charts/services/user-notification/values.staging.yaml index 9a9c1025e3bc..843f3f779e41 100644 --- a/charts/services/user-notification/values.staging.yaml +++ b/charts/services/user-notification/values.staging.yaml @@ -46,6 +46,7 @@ env: REDIS_URL_NODE_01: '["clustercfg.general-redis-cluster-group.ab9ckb.euw1.cache.amazonaws.com:6379"]' REDIS_USE_SSL: 'true' SERVERSIDE_FEATURES_ON: '' + SERVICE_PORTAL_BFF_LOGIN_URL: 'https://beta.staging01.devland.is/bff/login' SERVICE_PORTAL_CLICK_ACTION_URL: 'https://island.is/minarsidur' USER_PROFILE_CLIENT_URL: 'http://web-service-portal-api.service-portal.svc.cluster.local' XROAD_BASE_PATH: 'http://securityserver.staging01.devland.is' diff --git a/infra/helm/libs/api-template/templates/deployment.yaml b/infra/helm/libs/api-template/templates/deployment.yaml index e1ff2f6676a3..dd7d9fe970f0 100644 --- a/infra/helm/libs/api-template/templates/deployment.yaml +++ b/infra/helm/libs/api-template/templates/deployment.yaml @@ -1,9 +1,9 @@ {{- if .Values.enabled }} -{{- $fullName := include "api-template.fullname" . -}} +{{- $fullName := include "api-template.name" . -}} apiVersion: apps/v1 kind: Deployment metadata: - name: {{ include "api-template.fullname" . }} + name: {{ include "api-template.name" . }} {{- if .Values.namespace }} namespace: {{ .Values.namespace }} {{- end }} @@ -11,7 +11,7 @@ metadata: {{- include "api-template.labels" . | nindent 4 }} tags.datadoghq.com/env: {{ .Values.global.env.name }} tags.datadoghq.com/service: {{ include "api-template.name" . }} - tags.datadoghq.com/version: {{ .Values.image.tag | default .Values.global.image.tag }} + tags.datadoghq.com/version: {{ .Values.image.tag }} spec: replicas: {{ .Values.replicaCount.default }} strategy: @@ -25,7 +25,7 @@ spec: {{- include "api-template.selectorLabels" . | nindent 8 }} tags.datadoghq.com/env: {{ .Values.global.env.name }} tags.datadoghq.com/service: {{ include "api-template.name" . }} - tags.datadoghq.com/version: {{ .Values.image.tag | default .Values.global.image.tag }} + tags.datadoghq.com/version: {{ .Values.image.tag }} annotations: prometheus.io/scrape: "true" prometheus.io/path: /metrics @@ -78,7 +78,7 @@ spec: - name: {{ .name | default "migration" }} securityContext: {{- toYaml $.Values.securityContext | nindent 12 }} - image: "{{ if .image }}{{ .image }}{{ else }}{{ $.Values.image.repository }}:{{ $.Values.image.tag | default $.Values.global.image.tag }}{{ end }}" + image: "{{ if .image }}{{ .image }}{{ else }}{{ $.Values.image.repository }}:{{ $.Values.image.tag }}{{ end }}" command: {{ .command | toJson }} args: {{ .args | toJson }} env: @@ -98,7 +98,7 @@ spec: key: {{ $key }} {{- end }} - name: APP_VERSION - value: {{ $.Values.image.tag | default $.Values.global.image.tag }} + value: {{ $.Values.image.tag }} - name: DD_ENV valueFrom: fieldRef: @@ -132,7 +132,7 @@ spec: - name: {{ .Chart.Name }} securityContext: {{- toYaml .Values.securityContext | nindent 12 }} - image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Values.global.image.tag }}" + image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" imagePullPolicy: {{ .Values.image.pullPolicy }} {{- if .Values.command }} command: {{ .Values.command | toJson }} @@ -169,7 +169,7 @@ spec: key: {{ $key }} {{- end }} - name: APP_VERSION - value: {{ .Values.image.tag | default .Values.global.image.tag }} + value: {{ .Values.image.tag }} - name: DD_ENV valueFrom: fieldRef: diff --git a/infra/helm/libs/api-template/templates/hpa.yaml b/infra/helm/libs/api-template/templates/hpa.yaml index a8e0b5ebfffb..ec3fabece354 100644 --- a/infra/helm/libs/api-template/templates/hpa.yaml +++ b/infra/helm/libs/api-template/templates/hpa.yaml @@ -1,9 +1,10 @@ {{- if .Values.enabled }} {{- $fullName := include "api-template.fullname" . -}} +{{- $appName := include "api-template.name" . -}} apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: - name: {{ include "api-template.fullname" . }} + name: {{ $appName }} {{- if .Values.namespace }} namespace: {{ .Values.namespace }} {{- end }} @@ -13,7 +14,7 @@ spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment - name: {{ include "api-template.fullname" . }} + name: {{ $appName }} minReplicas: {{ .Values.hpa.scaling.replicas.min }} maxReplicas: {{ .Values.hpa.scaling.replicas.max }} behavior: @@ -37,7 +38,7 @@ spec: name: nginx_ingress_controller_requests_rate selector: matchLabels: - ingress: "{{ $fullName }}-{{ $name }}" + ingress: "{{ $name }}" target: type: AverageValue averageValue: {{ $.Values.hpa.scaling.metric.nginxRequestsIrate }} diff --git a/infra/helm/libs/api-template/templates/ingress.yaml b/infra/helm/libs/api-template/templates/ingress.yaml index 3b53d924d9b7..366293c1948d 100644 --- a/infra/helm/libs/api-template/templates/ingress.yaml +++ b/infra/helm/libs/api-template/templates/ingress.yaml @@ -1,5 +1,6 @@ {{- if .Values.enabled }} {{- $fullName := include "api-template.fullname" . -}} +{{- $appName := include "api-template.name" . -}} {{- $labels := include "api-template.labels" . -}} {{- $svcPort := $.Values.service.port -}} {{- range $name, $ingress := omit .Values.ingress "behindCloudfront" }} @@ -10,7 +11,7 @@ apiVersion: extensions/v1beta1 {{- end }} kind: Ingress metadata: - name: "{{ $fullName }}-{{ $name }}" + name: "{{ $appName }}-{{ $name }}" {{- if $.Values.namespace }} namespace: {{ $.Values.namespace }} {{- end }} @@ -38,7 +39,7 @@ spec: - pathType: Prefix backend: service: - name: {{ $fullName }} + name: {{ $appName }} port: number: {{ $svcPort }} {{- if eq (kindOf .) "map" }} diff --git a/infra/helm/libs/api-template/templates/namespaces.yaml b/infra/helm/libs/api-template/templates/namespaces.yaml new file mode 100644 index 000000000000..4a4755e99d09 --- /dev/null +++ b/infra/helm/libs/api-template/templates/namespaces.yaml @@ -0,0 +1,8 @@ +{{- if .Values.enabled }} +{{- if .Values.namespace}} +apiVersion: v1 +kind: Namespace +metadata: + name: {{ .Values.namespace }} +{{- end }} +{{- end }} diff --git a/infra/helm/libs/api-template/templates/pdb.yaml b/infra/helm/libs/api-template/templates/pdb.yaml index 3b3534fd8eb4..3204babe69f3 100644 --- a/infra/helm/libs/api-template/templates/pdb.yaml +++ b/infra/helm/libs/api-template/templates/pdb.yaml @@ -7,7 +7,7 @@ apiVersion: policy/v1 kind: PodDisruptionBudget metadata: name: {{ $serviceName }}-pdb - namespace: {{ $namespace }} + namespace: {{ $namespace }} spec: {{- if hasKey .Values.podDisruptionBudget "minAvailable" }} minAvailable: {{ .Values.podDisruptionBudget.minAvailable }} diff --git a/infra/helm/libs/api-template/templates/secrets.yaml b/infra/helm/libs/api-template/templates/secrets.yaml index 602dc8afb508..2cb9fe9f680a 100644 --- a/infra/helm/libs/api-template/templates/secrets.yaml +++ b/infra/helm/libs/api-template/templates/secrets.yaml @@ -6,7 +6,7 @@ metadata: {{- if .Values.namespace }} namespace: {{ .Values.namespace }} {{- end }} - name: {{ include "api-template.fullname" . }} + name: {{ include "api-template.name" . }} spec: backendType: systemManager data: @@ -23,7 +23,7 @@ metadata: {{- if .Values.namespace }} namespace: {{ .Values.namespace }} {{- end }} - name: {{ include "api-template.fullname" . }}-init-container + name: {{ include "api-template.name" . }}-init-container spec: backendType: systemManager data: diff --git a/infra/helm/libs/api-template/templates/service.yaml b/infra/helm/libs/api-template/templates/service.yaml index 5b7e61223d1b..25bbde4f30c2 100644 --- a/infra/helm/libs/api-template/templates/service.yaml +++ b/infra/helm/libs/api-template/templates/service.yaml @@ -2,7 +2,7 @@ apiVersion: v1 kind: Service metadata: - name: {{ include "api-template.fullname" . }} + name: {{ include "api-template.name" . }} {{- if .Values.namespace }} namespace: {{ .Values.namespace }} {{- end }} diff --git a/infra/helm/libs/cronjob-template/templates/cronjob.yaml b/infra/helm/libs/cronjob-template/templates/cronjob.yaml index a3892dce80ce..9a10878ca56f 100644 --- a/infra/helm/libs/cronjob-template/templates/cronjob.yaml +++ b/infra/helm/libs/cronjob-template/templates/cronjob.yaml @@ -1,5 +1,6 @@ +{{- if .Values.schedule }} {{- if .Values.enabled }} -{{- $fullName := include "cronjob-template.fullname" . -}} +{{- $fullName := include "api-template.fullname" . -}} apiVersion: batch/v1 kind: CronJob metadata: @@ -12,7 +13,7 @@ metadata: chart: "{{ $.Chart.Name }}-{{ $.Chart.Version | replace "+" "_" }}" tags.datadoghq.com/env: {{ .Values.global.env.name }} tags.datadoghq.com/service: {{ include "cronjob-template.name" . }} - tags.datadoghq.com/version: {{ .Values.image.tag | default .Values.global.image.tag }} + tags.datadoghq.com/version: {{ .Values.image.tag }} spec: concurrencyPolicy: {{ .Values.concurrencyPolicy | default "Allow" }} failedJobsHistoryLimit: {{ .Values.failedJobsHistoryLimit | default 1 }} @@ -52,13 +53,13 @@ spec: chart: "{{ $.Chart.Name }}-{{ $.Chart.Version | replace "+" "_" }}" tags.datadoghq.com/env: {{ .Values.global.env.name }} tags.datadoghq.com/service: {{ include "cronjob-template.name" . }} - tags.datadoghq.com/version: {{ .Values.image.tag | default .Values.global.image.tag }} + tags.datadoghq.com/version: {{ .Values.image.tag }} spec: serviceAccountName: {{ include "cronjob-template.serviceAccountName" $ }} securityContext: {{- toYaml $.Values.podSecurityContext | nindent 12 }} containers: - - image: "{{ $.Values.image.repository }}:{{ $.Values.image.tag | default .Values.global.image.tag }}" + - image: "{{ $.Values.image.repository }}:{{ $.Values.image.tag }}" name: {{ .Chart.Name }} securityContext: {{- toYaml $.Values.securityContext | nindent 14 }} @@ -92,7 +93,7 @@ spec: key: {{ $key }} {{- end }} - name: APP_VERSION - value: {{ .Values.image.tag | default .Values.global.image.tag }} + value: {{ .Values.image.tag }} - name: DD_ENV valueFrom: fieldRef: @@ -145,3 +146,4 @@ spec: {{- end }} restartPolicy: {{ .Values.restartPolicy | default "Never" }} {{- end }} +{{- end }} diff --git a/libs/application/template-api-modules/src/lib/modules/templates/inheritance-report/utils/mappers.ts b/libs/application/template-api-modules/src/lib/modules/templates/inheritance-report/utils/mappers.ts index b248ea6462c9..b4e0e2ab0f8e 100644 --- a/libs/application/template-api-modules/src/lib/modules/templates/inheritance-report/utils/mappers.ts +++ b/libs/application/template-api-modules/src/lib/modules/templates/inheritance-report/utils/mappers.ts @@ -265,6 +265,7 @@ export const expandAnswers = ( rent: answers?.funeralCost?.rent ?? '', food: answers?.funeralCost?.food ?? '', tombstone: answers?.funeralCost?.tombstone ?? '', + service: answers?.funeralCost?.service ?? '', hasOther: answers?.funeralCost?.hasOther ?? [], other: answers?.funeralCost?.other ?? '', otherDetails: answers?.funeralCost?.otherDetails ?? '', diff --git a/libs/application/templates/inao/financial-statement-cemetery/README.md b/libs/application/templates/inao/financial-statement-cemetery/README.md index 759b46c0bb58..d8c8a06c518e 100644 --- a/libs/application/templates/inao/financial-statement-cemetery/README.md +++ b/libs/application/templates/inao/financial-statement-cemetery/README.md @@ -1,6 +1,8 @@ # financial-statement-cemetery -This application turns in financial statements for cemeteries to the The Icelandic National Audit Office (Ríkisendurskoðun, inao for short). +This application turns in financial statements for cemeteries to the The Icelandic National Audit Office (Ríkisendurskoðun, INAO for short). Cemeteries are required to turn in their financial statements before the 1. of June each year. + +To test this application in the dev environment, you can log in as Gervimaður Færeyjar (010-2399) and swap to the procure of Fjárfestingafélagið Blámi. Blámi is registered as a cemetery with the INAO dev service and should be able to enter and go through the application. ## Running unit tests diff --git a/libs/application/templates/inao/financial-statement-cemetery/src/dataProviders/index.ts b/libs/application/templates/inao/financial-statement-cemetery/src/dataProviders/index.ts index 91d817999f92..dd52c79ad7f2 100644 --- a/libs/application/templates/inao/financial-statement-cemetery/src/dataProviders/index.ts +++ b/libs/application/templates/inao/financial-statement-cemetery/src/dataProviders/index.ts @@ -3,7 +3,7 @@ import { UserProfileApi } from '@island.is/application/types' export { NationalRegistryUserApi, - IdentityApi as IndentityApiProvider, + IdentityApi as IdentityApiProvider, } from '@island.is/application/types' export const CurrentUserTypeProvider = defineTemplateApi({ diff --git a/libs/application/templates/inao/financial-statement-cemetery/src/fields/CemeteryEquities/index.tsx b/libs/application/templates/inao/financial-statement-cemetery/src/fields/CemeteryEquities/index.tsx deleted file mode 100644 index e16606e47af3..000000000000 --- a/libs/application/templates/inao/financial-statement-cemetery/src/fields/CemeteryEquities/index.tsx +++ /dev/null @@ -1,326 +0,0 @@ -import { useState, useEffect } from 'react' -import { - AlertBanner, - Box, - GridColumn, - GridContainer, - GridRow, - Text, -} from '@island.is/island-ui/core' -import debounce from 'lodash/debounce' -import { useFormContext } from 'react-hook-form' -import { useLocale } from '@island.is/localization' -import { FieldBaseProps } from '@island.is/application/types' -import { InputController } from '@island.is/shared/form-fields' -import { m } from '../../lib/messages' -import { Total } from '../KeyNumbers' -import { getErrorViaPath, getValueViaPath } from '@island.is/application/core' -import { - CAPITALNUMBERS, - CEMETERYEQUITIESANDLIABILITIESIDS, - INPUTCHANGEINTERVAL, - OPERATINGCOST, - VALIDATOR, -} from '../../utils/constants' -import { useTotals } from '../../hooks/useTotals' -import { getTotal } from '../../utils/helpers' - -export const CemeteryEquities = ({ - application, - setBeforeSubmitCallback, -}: FieldBaseProps) => { - const answers = application.answers - const { formatMessage } = useLocale() - const { - clearErrors, - formState: { errors }, - setValue, - getValues, - setError, - } = useFormContext() - - const operatingCostTotal = Number( - getValueViaPath(answers, OPERATINGCOST.total), - ) - - const capitalTotal = Number(getValueViaPath(answers, CAPITALNUMBERS.total)) - - useEffect(() => { - const total = operatingCostTotal + capitalTotal - setValue(CEMETERYEQUITIESANDLIABILITIESIDS.operationResult, total) - setTotalOperatingCost(total) - }, [operatingCostTotal, capitalTotal, setValue]) - - const [totalOperatingCost, setTotalOperatingCost] = useState(0) - const [equityTotal, setEquityTotal] = useState(0) - const [equityAndDebts, setEquityAndDebts] = useState(0) - - const [getTotalAssets, totalAssets] = useTotals( - CEMETERYEQUITIESANDLIABILITIESIDS.assetPrefix, - ) - const [getTotalLiabilities, totalLiabilities] = useTotals( - CEMETERYEQUITIESANDLIABILITIESIDS.liabilityPrefix, - ) - const [getTotalEquity, totalEquity] = useTotals( - CEMETERYEQUITIESANDLIABILITIESIDS.equityPrefix, - ) - - useEffect(() => { - setEquityTotal(totalEquity) - }, [totalEquity, totalOperatingCost]) - - useEffect(() => { - const total = totalEquity + totalLiabilities - setEquityAndDebts(total) - }, [totalEquity, totalLiabilities]) - - useEffect(() => { - clearErrors(VALIDATOR) - }, [totalEquity, totalLiabilities, totalAssets, clearErrors]) - - // we need to validate some info before allowing submission of the current screen data - // since we're comparing values from different objects, doing it via zod is not an option - setBeforeSubmitCallback && - setBeforeSubmitCallback(async () => { - const values = getValues() - const assets = getTotal(values, 'cemetryAsset') - const liabilties = getTotal(values, 'cemetryLiability') - const equities = getTotal(values, 'cemetryEquity') - - // assets should equal liabilties + equities - const isValid = liabilties + equities === assets - if (!isValid) { - setError(VALIDATOR, { - type: 'custom', - message: formatMessage(m.equityDebtsAssetsValidatorError), - }) - return [false, formatMessage(m.equityDebtsAssetsValidatorError)] - } - return [true, null] - }) - - return ( - <GridContainer> - <GridRow align="spaceBetween"> - <GridColumn span={['12/12', '12/12', '12/12', '6/12']}> - <Text paddingY={1} as="h2" variant="h4"> - {formatMessage(m.properties)} - </Text> - <Box paddingY={1}> - <InputController - id={CEMETERYEQUITIESANDLIABILITIESIDS.fixedAssetsTotal} - name={CEMETERYEQUITIESANDLIABILITIESIDS.fixedAssetsTotal} - error={ - errors && - getErrorViaPath( - errors, - CEMETERYEQUITIESANDLIABILITIESIDS.fixedAssetsTotal, - ) - } - onChange={debounce(() => { - getTotalAssets() - clearErrors(CEMETERYEQUITIESANDLIABILITIESIDS.fixedAssetsTotal) - }, INPUTCHANGEINTERVAL)} - label={formatMessage(m.fixedAssetsTotal)} - backgroundColor="blue" - rightAlign - currency - /> - </Box> - <Box paddingY={1}> - <InputController - id={CEMETERYEQUITIESANDLIABILITIESIDS.currentAssets} - name={CEMETERYEQUITIESANDLIABILITIESIDS.currentAssets} - onChange={debounce(() => { - getTotalAssets() - clearErrors(CEMETERYEQUITIESANDLIABILITIESIDS.currentAssets) - }, INPUTCHANGEINTERVAL)} - label={formatMessage(m.currentAssets)} - error={ - errors && - getErrorViaPath( - errors, - CEMETERYEQUITIESANDLIABILITIESIDS.currentAssets, - ) - } - backgroundColor="blue" - rightAlign - currency - /> - </Box> - <Total - name={CEMETERYEQUITIESANDLIABILITIESIDS.assetTotal} - total={totalAssets} - label={formatMessage(m.totalAssets)} - /> - </GridColumn> - <GridColumn span={['12/12', '12/12', '12/12', '6/12']}> - <Text paddingY={1} as="h2" variant="h4"> - {formatMessage(m.debtsAndEquity)} - </Text> - <Box paddingY={1}> - <InputController - id={CEMETERYEQUITIESANDLIABILITIESIDS.longTerm} - name={CEMETERYEQUITIESANDLIABILITIESIDS.longTerm} - onChange={debounce(() => { - getTotalLiabilities() - clearErrors(CEMETERYEQUITIESANDLIABILITIESIDS.longTerm) - }, INPUTCHANGEINTERVAL)} - error={ - errors && - getErrorViaPath( - errors, - CEMETERYEQUITIESANDLIABILITIESIDS.longTerm, - ) - } - label={formatMessage(m.longTerm)} - backgroundColor="blue" - rightAlign - currency - /> - </Box> - <Box paddingY={1}> - <InputController - id={CEMETERYEQUITIESANDLIABILITIESIDS.shortTerm} - name={CEMETERYEQUITIESANDLIABILITIESIDS.shortTerm} - onChange={debounce(() => { - getTotalLiabilities() - clearErrors(CEMETERYEQUITIESANDLIABILITIESIDS.shortTerm) - }, INPUTCHANGEINTERVAL)} - error={ - errors && - getErrorViaPath( - errors, - CEMETERYEQUITIESANDLIABILITIESIDS.shortTerm, - ) - } - label={formatMessage(m.shortTerm)} - backgroundColor="blue" - rightAlign - currency - /> - </Box> - <Total - name={CEMETERYEQUITIESANDLIABILITIESIDS.liabilityTotal} - total={totalLiabilities} - label={formatMessage(m.totalDebts)} - /> - <Box paddingBottom={1} paddingTop={2}> - <InputController - id={ - CEMETERYEQUITIESANDLIABILITIESIDS.equityAtTheBeginningOfTheYear - } - name={ - CEMETERYEQUITIESANDLIABILITIESIDS.equityAtTheBeginningOfTheYear - } - onChange={debounce(() => { - getTotalEquity() - clearErrors( - CEMETERYEQUITIESANDLIABILITIESIDS.equityAtTheBeginningOfTheYear, - ) - }, INPUTCHANGEINTERVAL)} - error={ - errors && - getErrorViaPath( - errors, - CEMETERYEQUITIESANDLIABILITIESIDS.equityAtTheBeginningOfTheYear, - ) - } - label={formatMessage(m.equityAtTheBeginningOfTheYear)} - backgroundColor="blue" - rightAlign - currency - /> - </Box> - <Box paddingY={1}> - <InputController - id={ - CEMETERYEQUITIESANDLIABILITIESIDS.revaluationDueToPriceChanges - } - name={ - CEMETERYEQUITIESANDLIABILITIESIDS.revaluationDueToPriceChanges - } - onChange={debounce(() => { - getTotalEquity() - clearErrors( - CEMETERYEQUITIESANDLIABILITIESIDS.revaluationDueToPriceChanges, - ) - }, INPUTCHANGEINTERVAL)} - error={ - errors && - getErrorViaPath( - errors, - CEMETERYEQUITIESANDLIABILITIESIDS.revaluationDueToPriceChanges, - ) - } - label={formatMessage(m.revaluationDueToPriceChanges)} - backgroundColor="blue" - rightAlign - currency - /> - </Box> - <Box paddingY={1}> - <InputController - id={CEMETERYEQUITIESANDLIABILITIESIDS.reevaluateOther} - name={CEMETERYEQUITIESANDLIABILITIESIDS.reevaluateOther} - onChange={debounce(() => { - getTotalEquity() - clearErrors(CEMETERYEQUITIESANDLIABILITIESIDS.reevaluateOther) - }, INPUTCHANGEINTERVAL)} - error={ - errors && - getErrorViaPath( - errors, - CEMETERYEQUITIESANDLIABILITIESIDS.reevaluateOther, - ) - } - label={formatMessage(m.reevaluateOther)} - backgroundColor="blue" - rightAlign - currency - /> - </Box> - <Box paddingY={1}> - <InputController - id={CEMETERYEQUITIESANDLIABILITIESIDS.operationResult} - name={CEMETERYEQUITIESANDLIABILITIESIDS.operationResult} - readOnly - error={ - errors && - getErrorViaPath( - errors, - CEMETERYEQUITIESANDLIABILITIESIDS.operationResult, - ) - } - label={formatMessage(m.operationResult)} - backgroundColor="blue" - rightAlign - currency - /> - </Box> - <Total - name={CEMETERYEQUITIESANDLIABILITIESIDS.equityTotal} - total={equityTotal} - label={formatMessage(m.totalEquity)} - /> - <Box paddingY={1}> - <Total - name={CEMETERYEQUITIESANDLIABILITIESIDS.totalEquityAndLiabilities} - total={equityAndDebts} - label={formatMessage(m.debtsAndCash)} - /> - </Box> - </GridColumn> - </GridRow> - {errors && errors.validator ? ( - <Box paddingY={2}> - <AlertBanner - title={formatMessage(m.equityErrorTitle)} - description={formatMessage(m.equityDebtsAssetsValidatorError)} - variant="error" - /> - </Box> - ) : null} - </GridContainer> - ) -} diff --git a/libs/application/templates/inao/financial-statement-cemetery/src/fields/CemeteryIncomeLimit/index.tsx b/libs/application/templates/inao/financial-statement-cemetery/src/fields/CemeteryIncomeLimit/index.tsx index a76d1ed2d9e0..3e78d43e4af1 100644 --- a/libs/application/templates/inao/financial-statement-cemetery/src/fields/CemeteryIncomeLimit/index.tsx +++ b/libs/application/templates/inao/financial-statement-cemetery/src/fields/CemeteryIncomeLimit/index.tsx @@ -20,8 +20,8 @@ export const CemeteryIncomeLimit = () => { }) useEffect(() => { - const limit = - data?.financialStatementCemeteryClientFinancialLimit?.toString() + const limit = data?.financialStatementsInaoClientFinancialLimit?.toString() + if (limit) { setValue(CEMETERYOPERATIONIDS.incomeLimit, limit) } diff --git a/libs/application/templates/inao/financial-statement-cemetery/src/fields/CemeteryOperation/CemeteryExpenses.tsx b/libs/application/templates/inao/financial-statement-cemetery/src/fields/CemeteryOperation/CemeteryExpenses.tsx deleted file mode 100644 index ef7aa2b9a900..000000000000 --- a/libs/application/templates/inao/financial-statement-cemetery/src/fields/CemeteryOperation/CemeteryExpenses.tsx +++ /dev/null @@ -1,168 +0,0 @@ -import { useEffect } from 'react' -import { useLocale } from '@island.is/localization' -import { Box } from '@island.is/island-ui/core' -import { InputController } from '@island.is/shared/form-fields' -import { getErrorViaPath, getValueViaPath } from '@island.is/application/core' -import { RecordObject } from '@island.is/application/types' -import debounce from 'lodash/debounce' -import { useFormContext } from 'react-hook-form' -import { m } from '../../lib/messages' - -import { FinancialStatementsInaoTaxInfo } from '@island.is/api/schema' -import { - CEMETERYOPERATIONIDS, - INPUTCHANGEINTERVAL, -} from '../../utils/constants' -type Props = { - data?: { - financialStatementsInaoTaxInfo: FinancialStatementsInaoTaxInfo[] - } | null - loading: boolean - getSum: () => void - errors: RecordObject<unknown> | undefined -} - -export const CemeteryExpenses = ({ data, loading, errors, getSum }: Props) => { - const { formatMessage } = useLocale() - const { clearErrors, setValue, getValues } = useFormContext() - const values = getValues() - - const donationsToCemeteryFund = getValueViaPath( - values, - CEMETERYOPERATIONIDS.donationsToCemeteryFund, - ) - - useEffect(() => { - if (data?.financialStatementsInaoTaxInfo) { - if (!donationsToCemeteryFund) { - setValue( - CEMETERYOPERATIONIDS.donationsToCemeteryFund, - data.financialStatementsInaoTaxInfo - ?.find((x) => x.key === 334) - ?.value?.toString() ?? '', - ) - } - } - getSum() - }, [data, getSum, setValue]) - - const onInputChange = debounce((fieldId: string) => { - getSum() - clearErrors(fieldId) - }, INPUTCHANGEINTERVAL) - - return ( - <> - <Box paddingY={1}> - <InputController - id={CEMETERYOPERATIONIDS.payroll} - name={CEMETERYOPERATIONIDS.payroll} - label={formatMessage(m.payroll)} - onChange={() => onInputChange(CEMETERYOPERATIONIDS.payroll)} - error={ - errors && getErrorViaPath(errors, CEMETERYOPERATIONIDS.payroll) - } - backgroundColor="blue" - rightAlign - currency - /> - </Box> - <Box paddingY={1}> - <InputController - id={CEMETERYOPERATIONIDS.funeralCost} - name={CEMETERYOPERATIONIDS.funeralCost} - label={formatMessage(m.funeralCost)} - onChange={() => onInputChange(CEMETERYOPERATIONIDS.funeralCost)} - error={ - errors && getErrorViaPath(errors, CEMETERYOPERATIONIDS.funeralCost) - } - backgroundColor="blue" - rightAlign - currency - /> - </Box> - <Box paddingY={1}> - <InputController - id={CEMETERYOPERATIONIDS.chapelExpense} - name={CEMETERYOPERATIONIDS.chapelExpense} - label={formatMessage(m.chapelExpense)} - onChange={() => onInputChange(CEMETERYOPERATIONIDS.chapelExpense)} - error={ - errors && - getErrorViaPath(errors, CEMETERYOPERATIONIDS.chapelExpense) - } - backgroundColor="blue" - rightAlign - currency - /> - </Box> - <Box paddingY={1}> - <InputController - id={CEMETERYOPERATIONIDS.donationsToCemeteryFund} - name={CEMETERYOPERATIONIDS.donationsToCemeteryFund} - label={formatMessage(m.donationsToCemeteryFund)} - loading={loading} - onChange={() => - onInputChange(CEMETERYOPERATIONIDS.donationsToCemeteryFund) - } - error={ - errors && - getErrorViaPath( - errors, - CEMETERYOPERATIONIDS.donationsToCemeteryFund, - ) - } - backgroundColor="blue" - rightAlign - currency - /> - </Box> - <Box paddingY={1}> - <InputController - id={CEMETERYOPERATIONIDS.donationsToOther} - name={CEMETERYOPERATIONIDS.donationsToOther} - label={formatMessage(m.donationsToOther)} - onChange={() => onInputChange(CEMETERYOPERATIONIDS.donationsToOther)} - error={ - errors && - getErrorViaPath(errors, CEMETERYOPERATIONIDS.donationsToOther) - } - backgroundColor="blue" - rightAlign - currency - /> - </Box> - <Box paddingY={1}> - <InputController - id={CEMETERYOPERATIONIDS.otherOperationCost} - name={CEMETERYOPERATIONIDS.otherOperationCost} - label={formatMessage(m.otherOperationCost)} - onChange={() => - onInputChange(CEMETERYOPERATIONIDS.otherOperationCost) - } - error={ - errors && - getErrorViaPath(errors, CEMETERYOPERATIONIDS.otherOperationCost) - } - backgroundColor="blue" - rightAlign - currency - /> - </Box> - <Box paddingY={1}> - <InputController - id={CEMETERYOPERATIONIDS.depreciation} - name={CEMETERYOPERATIONIDS.depreciation} - label={formatMessage(m.depreciation)} - onChange={() => onInputChange(CEMETERYOPERATIONIDS.depreciation)} - error={ - errors && getErrorViaPath(errors, CEMETERYOPERATIONIDS.depreciation) - } - backgroundColor="blue" - rightAlign - currency - /> - </Box> - </> - ) -} diff --git a/libs/application/templates/inao/financial-statement-cemetery/src/fields/CemeteryOperation/CemeteryIncome.tsx b/libs/application/templates/inao/financial-statement-cemetery/src/fields/CemeteryOperation/CemeteryIncome.tsx deleted file mode 100644 index 46179f7371da..000000000000 --- a/libs/application/templates/inao/financial-statement-cemetery/src/fields/CemeteryOperation/CemeteryIncome.tsx +++ /dev/null @@ -1,147 +0,0 @@ -import { useEffect } from 'react' -import { Box } from '@island.is/island-ui/core' -import { RecordObject } from '@island.is/application/types' -import debounce from 'lodash/debounce' -import { InputController } from '@island.is/shared/form-fields' -import { useLocale } from '@island.is/localization' -import { useFormContext } from 'react-hook-form' -import { getErrorViaPath, getValueViaPath } from '@island.is/application/core' -import { m } from '../../lib/messages' -import { FinancialStatementsInaoTaxInfo } from '@island.is/api/schema' -import { - CEMETERYOPERATIONIDS, - INPUTCHANGEINTERVAL, - TaxInfoTypes, -} from '../../utils/constants' - -type Props = { - data?: { - financialStatementsInaoTaxInfo: FinancialStatementsInaoTaxInfo[] - } | null - loading: boolean - getSum: () => void - errors: RecordObject<unknown> | undefined -} - -export const CemetryIncome = ({ data, loading, errors, getSum }: Props) => { - const { formatMessage } = useLocale() - const { clearErrors, getValues, setValue } = useFormContext() - - useEffect(() => { - const values = getValues() - - const careIncome = getValueViaPath(values, CEMETERYOPERATIONIDS.careIncome) - const burialRevenue = getValueViaPath( - values, - CEMETERYOPERATIONIDS.burialRevenue, - ) - const grantFromTheCemeteryFund = getValueViaPath( - values, - CEMETERYOPERATIONIDS.grantFromTheCemeteryFund, - ) - - if (data?.financialStatementsInaoTaxInfo) { - if (!careIncome) { - setValue( - CEMETERYOPERATIONIDS.careIncome, - data.financialStatementsInaoTaxInfo - ?.find((x) => x.key === TaxInfoTypes.CARE_INCOME) - ?.value?.toString() ?? '', - ) - } - if (!burialRevenue) { - setValue( - CEMETERYOPERATIONIDS.burialRevenue, - data.financialStatementsInaoTaxInfo - ?.find((x) => x.key === TaxInfoTypes.BURIAL_REVENUE) - ?.value?.toString() ?? '', - ) - } - if (!grantFromTheCemeteryFund) { - setValue( - CEMETERYOPERATIONIDS.grantFromTheCemeteryFund, - data.financialStatementsInaoTaxInfo - ?.find((x) => x.key === TaxInfoTypes.GRANT_FROM_THE_CEMETERY_FUND) - ?.value?.toString() ?? '', - ) - } - getSum() - } - }, [data, getSum, setValue]) - - const onInputChange = debounce((fieldId: string) => { - getSum() - clearErrors(fieldId) - }, INPUTCHANGEINTERVAL) - - return ( - <> - <Box paddingY={1}> - <InputController - id={CEMETERYOPERATIONIDS.careIncome} - loading={loading} - name={CEMETERYOPERATIONIDS.careIncome} - label={formatMessage(m.careIncome)} - onChange={() => onInputChange(CEMETERYOPERATIONIDS.careIncome)} - backgroundColor="blue" - currency - rightAlign - error={ - errors && getErrorViaPath(errors, CEMETERYOPERATIONIDS.careIncome) - } - /> - </Box> - <Box paddingY={1}> - <InputController - id={CEMETERYOPERATIONIDS.burialRevenue} - loading={loading} - name={CEMETERYOPERATIONIDS.burialRevenue} - label={formatMessage(m.burialRevenue)} - onChange={() => onInputChange(CEMETERYOPERATIONIDS.burialRevenue)} - backgroundColor="blue" - currency - rightAlign - error={ - errors && - getErrorViaPath(errors, CEMETERYOPERATIONIDS.burialRevenue) - } - /> - </Box> - <Box paddingY={1}> - <InputController - id={CEMETERYOPERATIONIDS.grantFromTheCemeteryFund} - loading={loading} - name={CEMETERYOPERATIONIDS.grantFromTheCemeteryFund} - label={formatMessage(m.grantFromTheCemeteryFund)} - onChange={() => - onInputChange(CEMETERYOPERATIONIDS.grantFromTheCemeteryFund) - } - backgroundColor="blue" - currency - rightAlign - error={ - errors && - getErrorViaPath( - errors, - CEMETERYOPERATIONIDS.grantFromTheCemeteryFund, - ) - } - /> - </Box> - <Box paddingY={1}> - <InputController - id={CEMETERYOPERATIONIDS.otherIncome} - name={CEMETERYOPERATIONIDS.otherIncome} - label={formatMessage(m.otherIncome)} - onChange={() => onInputChange(CEMETERYOPERATIONIDS.otherIncome)} - backgroundColor="blue" - currency - rightAlign - error={ - errors && getErrorViaPath(errors, CEMETERYOPERATIONIDS.otherIncome) - } - /> - </Box> - </> - ) -} diff --git a/libs/application/templates/inao/financial-statement-cemetery/src/fields/CemeteryOperation/index.tsx b/libs/application/templates/inao/financial-statement-cemetery/src/fields/CemeteryOperation/index.tsx deleted file mode 100644 index 424169cccdfa..000000000000 --- a/libs/application/templates/inao/financial-statement-cemetery/src/fields/CemeteryOperation/index.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import { useFormContext } from 'react-hook-form' -import { - GridColumn, - GridContainer, - GridRow, - Text, -} from '@island.is/island-ui/core' -import { Application } from '@island.is/application/types' -import { useLocale } from '@island.is/localization' -import { m } from '../../lib/messages' -import { Total } from '../KeyNumbers' -import { CemetryIncome } from './CemeteryIncome' -import { CemeteryExpenses } from './CemeteryExpenses' -import { CemeteryIncomeLimit } from '../CemeteryIncomeLimit/index' -import { useQuery } from '@apollo/client' -import { getValueViaPath } from '@island.is/application/core' -import { taxInfoQuery } from '../../graphql' -import { CEMETERYOPERATIONIDS, OPERATINGCOST } from '../../utils/constants' -import { useTotals } from '../../hooks/useTotals' - -export const CemeteryOperation = ({ - application, -}: { - application: Application -}) => { - const { answers } = application - - const operatingYear = - getValueViaPath(answers, 'conditionalAbout.operatingYear') ?? '' - - const { data, loading } = useQuery(taxInfoQuery, { - variables: { year: operatingYear }, - }) - - const { - formState: { errors }, - } = useFormContext() - const { formatMessage } = useLocale() - const [getTotalIncome, totalIncome] = useTotals( - CEMETERYOPERATIONIDS.prefixIncome, - ) - const [getTotalExpense, totalExpense] = useTotals( - CEMETERYOPERATIONIDS.prefixExpense, - ) - - return ( - <GridContainer> - <CemeteryIncomeLimit /> - <GridRow align="spaceBetween"> - <GridColumn span={['12/12', '12/12', '12/12', '6/12']}> - <Text paddingY={1} as="h2" variant="h4"> - {formatMessage(m.income)} - </Text> - <CemetryIncome - data={data} - loading={loading} - getSum={getTotalIncome} - errors={errors} - /> - <Total - name={CEMETERYOPERATIONIDS.totalIncome} - total={totalIncome} - label={formatMessage(m.totalIncome)} - /> - </GridColumn> - <GridColumn span={['12/12', '12/12', '12/12', '6/12']}> - <Text paddingY={1} as="h2" variant="h4"> - {formatMessage(m.expenses)} - </Text> - <CemeteryExpenses - data={data} - loading={loading} - getSum={getTotalExpense} - errors={errors} - /> - <Total - name={CEMETERYOPERATIONIDS.totalExpense} - total={totalExpense} - label={formatMessage(m.totalExpenses)} - /> - </GridColumn> - </GridRow> - <GridRow align="flexEnd"> - <GridColumn span={['12/12', '12/12', '12/12', '6/12']}> - <Total - name={OPERATINGCOST.total} - label={formatMessage(m.operatingCost)} - title={formatMessage(m.operatingCostBefore)} - total={totalIncome - totalExpense} - /> - </GridColumn> - </GridRow> - </GridContainer> - ) -} diff --git a/libs/application/templates/inao/financial-statement-cemetery/src/fields/CemeteryOverview/BottomBar.tsx b/libs/application/templates/inao/financial-statement-cemetery/src/fields/CemeteryOverview/BottomBar.tsx deleted file mode 100644 index a014701fc18c..000000000000 --- a/libs/application/templates/inao/financial-statement-cemetery/src/fields/CemeteryOverview/BottomBar.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { Box, Button, Divider } from '@island.is/island-ui/core' -import { useLocale } from '@island.is/localization' -import { m } from '../../lib/messages' - -type Props = { - onBackButtonClick: () => void - onSendButtonClick: () => void - loading?: boolean - sendText?: string -} - -const BottomBar = ({ - onBackButtonClick, - onSendButtonClick, - loading = false, - sendText, -}: Props) => { - const { formatMessage } = useLocale() - - return ( - <> - <Box paddingY={3}> - <Divider /> - </Box> - <Box display="flex" justifyContent="spaceBetween" paddingY={5}> - <Button variant="ghost" onClick={onBackButtonClick} disabled={loading}> - {formatMessage(m.goBack)} - </Button> - <Button icon="checkmark" onClick={onSendButtonClick} loading={loading}> - {sendText ? sendText : formatMessage(m.send)} - </Button> - </Box> - </> - ) -} - -export default BottomBar diff --git a/libs/application/templates/inao/financial-statement-cemetery/src/fields/CemeteryOverview/index.tsx b/libs/application/templates/inao/financial-statement-cemetery/src/fields/CemeteryOverview/index.tsx index 57040fecba5d..0f8c89f2c9f0 100644 --- a/libs/application/templates/inao/financial-statement-cemetery/src/fields/CemeteryOverview/index.tsx +++ b/libs/application/templates/inao/financial-statement-cemetery/src/fields/CemeteryOverview/index.tsx @@ -1,86 +1,134 @@ -import { useState } from 'react' -import { DefaultEvents, FieldBaseProps } from '@island.is/application/types' -import { getErrorViaPath, getValueViaPath } from '@island.is/application/core' - +import { Fragment } from 'react' +import { FieldBaseProps } from '@island.is/application/types' +import { getValueViaPath } from '@island.is/application/core' import { AlertBanner, Box, - Checkbox, Divider, GridColumn, GridRow, - InputError, Text, } from '@island.is/island-ui/core' -import { Controller, useFormContext } from 'react-hook-form' import { useLocale } from '@island.is/localization' import { format as formatNationalId } from 'kennitala' -import { useSubmitApplication } from '../../hooks/useSubmitApplication' import { m } from '../../lib/messages' import { FinancialStatementCemetery } from '../../lib/dataSchema' -import { currencyStringToNumber, formatCurrency } from '../../utils/helpers' +import { formatCurrency } from '../../utils/helpers' import { AboutOverview } from './AboutOverview' import { ValueLine } from './ValueLine' import { CapitalNumberOverview } from './CapitalNumbersOverview' import { BOARDMEMEBER } from '../../utils/constants' import { FileValueLine } from './FileValueLine' -import BottomBar from './BottomBar' import { columnStyle, sectionColumn, starterColumnStyle, } from './overviewStyles.css' -export const CemeteryOverview = ({ - application, - goToScreen, - refetch, -}: FieldBaseProps) => { - const { - formState: { errors }, - setError, - setValue, - } = useFormContext() +export const CemeteryOverview = ({ application }: FieldBaseProps) => { const { formatMessage } = useLocale() - const [approveOverview, setApproveOverview] = useState(false) - - const [submitApplication, { error: submitError, loading }] = - useSubmitApplication({ - application, - refetch, - event: DefaultEvents.SUBMIT, - }) const answers = application.answers as FinancialStatementCemetery - const fileName = answers.attachments?.file?.[0]?.name - const careTakerLimit = answers.cemeteryOperation?.incomeLimit ?? '0' - const cemeteryIncome = currencyStringToNumber(answers.cemeteryIncome?.total) - const fixedAssetsTotal = answers.cemeteryAsset?.fixedAssetsTotal - const longTermDebt = answers.cemeteryLiability?.longTerm - const email = getValueViaPath(answers, 'about.email') + const file = getValueViaPath<Array<File>>(answers, 'attachments.file') + const fileName = file?.[0]?.name + const incomeLimit = + getValueViaPath<string>(answers, 'cemeteryOperations.incomeLimit') ?? '0' + const email = getValueViaPath<string>(answers, 'about.email') const cemeteryCaretakers = answers.cemeteryCaretaker - const onBackButtonClick = () => { - if ( - Number(cemeteryIncome) < Number(careTakerLimit) && - fixedAssetsTotal === '0' && - longTermDebt === '0' - ) { - goToScreen && goToScreen('caretakers') - } else { - goToScreen && goToScreen('attachments.file') - } - } + const careIncome = getValueViaPath<string>( + answers, + 'cemeteryIncome.careIncome', + ) + const burialRevenue = getValueViaPath<string>( + answers, + 'cemeteryIncome.burialRevenue', + ) + const grantFromTheCemeteryFund = getValueViaPath<string>( + answers, + 'cemeteryIncome.grantFromTheCemeteryFund', + ) + const otherIncome = getValueViaPath<string>( + answers, + 'cemeteryIncome.otherIncome', + ) + const totalIncome = getValueViaPath<string>(answers, 'cemeteryIncome.total') + + const payroll = getValueViaPath<string>(answers, 'cemeteryExpense.payroll') + const funeralCost = getValueViaPath<string>( + answers, + 'cemeteryExpense.funeralCost', + ) + const chapelExpense = getValueViaPath<string>( + answers, + 'cemeteryExpense.chapelExpense', + ) + const donationsToCemeteryFund = getValueViaPath<string>( + answers, + 'cemeteryExpense.cemeteryFundExpense', + ) + const donationsToOther = getValueViaPath<string>( + answers, + 'cemeteryExpense.donationsToOther', + ) + const otherOperationCost = getValueViaPath<string>( + answers, + 'cemeteryExpense.otherOperationCost', + ) + const depreciation = getValueViaPath<string>( + answers, + 'cemeteryExpense.depreciation', + ) + const totalExpenses = getValueViaPath<string>( + answers, + 'cemeteryExpense.total', + ) - const onSendButtonClick = () => { - if (approveOverview) { - submitApplication() - } else { - setError('applicationApprove', { - type: 'error', - }) - } - } + const fixedAssetsTotal = getValueViaPath<string>( + answers, + 'cemeteryAsset.fixedAssetsTotal', + ) + const currentAssets = getValueViaPath<string>( + answers, + 'cemeteryAsset.currentAssets', + ) + const totalAssets = getValueViaPath<string>(answers, 'assetsTotal') + + const longTerm = getValueViaPath<string>( + answers, + 'cemeteryLiability.longTerm', + ) + const shortTerm = getValueViaPath<string>( + answers, + 'cemeteryLiability.shortTerm', + ) + const totalLiabilities = getValueViaPath<string>( + answers, + 'equityAndLiabilitiesTotals.liabilitiesTotal', + ) + + const equityAtTheBeginningOfTheYear = getValueViaPath<string>( + answers, + 'cemeteryEquity.equityAtTheBeginningOfTheYear', + ) + const revaluationDueToPriceChanges = getValueViaPath<string>( + answers, + 'cemeteryEquity.revaluationDueToPriceChanges', + ) + const reevaluateOther = getValueViaPath<string>( + answers, + 'cemeteryEquity.reevaluateOther', + ) + const operationResult = getValueViaPath<string>( + answers, + 'cemeteryEquity.operationResult', + ) + const totalEquity = getValueViaPath<string>(answers, 'cemeteryEquity.total') + + const debtsAndCash = getValueViaPath<string>( + answers, + 'equityAndLiabilitiesTotals.equityAndLiabilitiesTotal', + ) return ( <Box marginBottom={2}> @@ -104,26 +152,24 @@ export const CemeteryOverview = ({ </Box> <ValueLine label={m.careIncome} - value={formatCurrency(answers.cemeteryIncome?.careIncome)} + value={formatCurrency(careIncome)} /> <ValueLine label={m.burialRevenue} - value={formatCurrency(answers.cemeteryIncome?.burialRevenue)} + value={formatCurrency(burialRevenue)} /> <ValueLine label={m.grantFromTheCemeteryFund} - value={formatCurrency( - answers.cemeteryIncome?.grantFromTheCemeteryFund, - )} + value={formatCurrency(grantFromTheCemeteryFund)} /> <ValueLine label={m.otherIncome} - value={formatCurrency(answers.cemeteryIncome?.otherIncome)} + value={formatCurrency(otherIncome)} /> <ValueLine isTotal label={m.totalIncome} - value={formatCurrency(answers.cemeteryIncome?.total)} + value={formatCurrency(totalIncome)} /> </GridColumn> <GridColumn span={['12/12', '6/12']} className={sectionColumn}> @@ -132,42 +178,35 @@ export const CemeteryOverview = ({ {formatMessage(m.expenses)} </Text> </Box> - <ValueLine - label={m.payroll} - value={formatCurrency(answers.cemeteryExpense?.payroll)} - /> + <ValueLine label={m.payroll} value={formatCurrency(payroll)} /> <ValueLine label={m.funeralCost} - value={formatCurrency(answers.cemeteryExpense?.funeralCost)} + value={formatCurrency(funeralCost)} /> <ValueLine label={m.chapelExpense} - value={formatCurrency(answers.cemeteryExpense?.chapelExpense)} + value={formatCurrency(chapelExpense)} /> <ValueLine label={m.donationsToCemeteryFund} - value={formatCurrency( - answers.cemeteryExpense?.cemeteryFundExpense, - )} + value={formatCurrency(donationsToCemeteryFund)} /> <ValueLine label={m.donationsToOther} - value={formatCurrency(answers.cemeteryExpense?.donationsToOther)} + value={formatCurrency(donationsToOther)} /> <ValueLine label={m.otherOperationCost} - value={formatCurrency( - answers.cemeteryExpense?.otherOperationCost, - )} + value={formatCurrency(otherOperationCost)} /> <ValueLine label={m.depreciation} - value={formatCurrency(answers.cemeteryExpense?.depreciation)} + value={formatCurrency(depreciation)} /> <ValueLine isTotal label={m.totalExpenses} - value={formatCurrency(answers.cemeteryExpense.total)} + value={formatCurrency(totalExpenses)} /> </GridColumn> </GridRow> @@ -193,16 +232,16 @@ export const CemeteryOverview = ({ </Box> <ValueLine label={m.fixedAssetsTotal} - value={formatCurrency(answers.cemeteryAsset?.fixedAssetsTotal)} + value={formatCurrency(fixedAssetsTotal)} /> <ValueLine label={m.currentAssets} - value={formatCurrency(answers.cemeteryAsset?.currentAssets)} + value={formatCurrency(currentAssets)} /> <ValueLine label={m.totalAssets} - value={formatCurrency(answers.cemeteryAsset.total)} + value={formatCurrency(totalAssets)} isTotal /> </GridColumn> @@ -213,18 +252,12 @@ export const CemeteryOverview = ({ {formatMessage(m.debts)} </Text> </Box> - <ValueLine - label={m.longTerm} - value={formatCurrency(answers.cemeteryLiability?.longTerm)} - /> - <ValueLine - label={m.shortTerm} - value={formatCurrency(answers.cemeteryLiability?.shortTerm)} - /> + <ValueLine label={m.longTerm} value={formatCurrency(longTerm)} /> + <ValueLine label={m.shortTerm} value={formatCurrency(shortTerm)} /> <ValueLine isTotal label={m.totalLiabilities} - value={formatCurrency(answers.cemeteryLiability?.total)} + value={formatCurrency(totalLiabilities)} /> <Box paddingTop={3} paddingBottom={2}> <Text variant="h4" as="h4"> @@ -233,39 +266,35 @@ export const CemeteryOverview = ({ </Box> <ValueLine label={m.equityAtTheBeginningOfTheYear} - value={formatCurrency( - answers.cemeteryEquity?.equityAtTheBeginningOfTheYear, - )} + value={formatCurrency(equityAtTheBeginningOfTheYear)} /> <ValueLine label={m.revaluationDueToPriceChanges} - value={formatCurrency( - answers.cemeteryEquity?.revaluationDueToPriceChanges, - )} + value={formatCurrency(revaluationDueToPriceChanges)} /> <ValueLine label={m.reevaluateOther} - value={formatCurrency(answers.cemeteryEquity?.reevaluateOther)} + value={formatCurrency(reevaluateOther)} /> <ValueLine label={m.operationResult} - value={formatCurrency(answers.cemeteryEquity?.operationResult)} + value={formatCurrency(operationResult)} /> <ValueLine isTotal label={m.totalEquity} - value={formatCurrency(answers.cemeteryEquity?.total)} + value={formatCurrency(totalEquity)} /> <ValueLine isTotal label={m.debtsAndCash} - value={formatCurrency(answers.equityAndLiabilities?.total)} + value={formatCurrency(debtsAndCash)} /> </GridColumn> </GridRow> </Box> <Divider /> - {parseInt(answers.cemeteryIncome?.total, 10) < Number(careTakerLimit) && + {Number(totalIncome) < Number(incomeLimit) && cemeteryCaretakers?.length > 0 ? ( <> <Box className={starterColumnStyle}> @@ -273,8 +302,8 @@ export const CemeteryOverview = ({ {formatMessage(m.cemeteryBoardmembers)} </Text> </Box> - {cemeteryCaretakers.map((careTaker) => ( - <> + {cemeteryCaretakers.map((careTaker, i) => ( + <Fragment key={i}> <Box className={columnStyle}> <GridRow> <GridColumn span={['12/12', '6/12']}> @@ -303,48 +332,19 @@ export const CemeteryOverview = ({ </GridRow> </Box> <Divider /> - </> + </Fragment> ))} </> ) : null} {fileName ? ( <> - <FileValueLine label={answers.attachments?.file?.[0]?.name} /> + <FileValueLine label={file?.[0]?.name} /> <Divider /> </> ) : null} - <Box paddingY={3}> - <Text variant="h3" as="h3"> - {formatMessage(m.overview)} - </Text> - </Box> - - <Box background="blue100"> - <Controller - name="applicationApprove" - defaultValue={approveOverview} - rules={{ required: true }} - render={({ field: { onChange, value } }) => { - return ( - <Checkbox - onChange={(e) => { - onChange(e.target.checked) - setApproveOverview(e.target.checked) - setValue('applicationApprove' as string, e.target.checked) - }} - checked={value} - name="applicationApprove" - id="applicationApprove" - label={formatMessage(m.overviewCorrect)} - large - /> - ) - }} - /> - </Box> - {Number(cemeteryIncome) < Number(careTakerLimit) && + {Number(totalIncome) < Number(incomeLimit) && fixedAssetsTotal === '0' && - longTermDebt === '0' ? ( + longTerm === '0' ? ( <Box paddingTop={4}> <AlertBanner title={`${formatMessage(m.SignatureMessage)}`} @@ -355,24 +355,6 @@ export const CemeteryOverview = ({ /> </Box> ) : null} - {errors && getErrorViaPath(errors, 'applicationApprove') ? ( - <InputError errorMessage={formatMessage(m.errorApproval)} /> - ) : null} - {submitError ? ( - <Box paddingY={2}> - <AlertBanner - title={formatMessage(m.submitErrorTitle)} - description={formatMessage(m.submitErrorMessage)} - variant="error" - dismissable - /> - </Box> - ) : null} - <BottomBar - loading={loading} - onSendButtonClick={onSendButtonClick} - onBackButtonClick={onBackButtonClick} - /> </Box> ) } diff --git a/libs/application/templates/inao/financial-statement-cemetery/src/fields/KeyNumbers/index.tsx b/libs/application/templates/inao/financial-statement-cemetery/src/fields/KeyNumbers/index.tsx deleted file mode 100644 index d565106e5e7b..000000000000 --- a/libs/application/templates/inao/financial-statement-cemetery/src/fields/KeyNumbers/index.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { useEffect } from 'react' -import { Box, Text } from '@island.is/island-ui/core' -import { InputController } from '@island.is/shared/form-fields' -import { useFormContext } from 'react-hook-form' - -type PropTypes = { name: string; total: number; label: string; title?: string } - -export const Total = ({ name, total, label, title }: PropTypes) => { - const { setValue } = useFormContext() - - useEffect(() => { - setValue(name, total.toString()) - }, [total]) - - return ( - <Box paddingY={3}> - {title ? ( - <Text as="h4" variant="h4" paddingBottom={1}> - {title} - </Text> - ) : null} - - <InputController - id={name} - name={name} - label={label} - rightAlign - readOnly - backgroundColor="blue" - currency - /> - </Box> - ) -} diff --git a/libs/application/templates/inao/financial-statement-cemetery/src/fields/KeyNumbersCapital/index.tsx b/libs/application/templates/inao/financial-statement-cemetery/src/fields/KeyNumbersCapital/index.tsx deleted file mode 100644 index 23ce3e66e903..000000000000 --- a/libs/application/templates/inao/financial-statement-cemetery/src/fields/KeyNumbersCapital/index.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import React, { useState, useEffect } from 'react' -import { - Box, - GridColumn, - GridContainer, - GridRow, -} from '@island.is/island-ui/core' -import { getValueViaPath } from '@island.is/application/core' -import debounce from 'lodash/debounce' -import { useFormContext } from 'react-hook-form' -import { useLocale } from '@island.is/localization' -import { InputController } from '@island.is/shared/form-fields' -import { m } from '../../lib/messages' -import { Total } from '../KeyNumbers' - -import { getErrorViaPath } from '@island.is/application/core' -import { CAPITALNUMBERS, INPUTCHANGEINTERVAL } from '../../utils/constants' - -export const KeyNumbersCapital = () => { - const { formatMessage } = useLocale() - const [totalCapital, setTotalCapital] = useState(0) - const { - clearErrors, - formState: { errors }, - getValues, - } = useFormContext() - - const getTotalCapital = () => { - const values = getValues() - - const income = getValueViaPath(values, CAPITALNUMBERS.capitalIncome) || '0' - const expense = getValueViaPath(values, CAPITALNUMBERS.capitalCost) || '0' - const total = Number(income) - Number(expense) - setTotalCapital(total) - } - - useEffect(() => { - getTotalCapital() - }, [getTotalCapital]) - - return ( - <GridContainer> - <GridRow align="spaceBetween"> - <GridColumn span={['12/12', '12/12', '12/12', '6/12']}> - <Box paddingY={1}> - <InputController - id={CAPITALNUMBERS.capitalIncome} - name={CAPITALNUMBERS.capitalIncome} - error={ - errors && getErrorViaPath(errors, CAPITALNUMBERS.capitalIncome) - } - onChange={debounce(() => { - getTotalCapital() - clearErrors(CAPITALNUMBERS.capitalIncome) - }, INPUTCHANGEINTERVAL)} - label={formatMessage(m.capitalIncome)} - backgroundColor="blue" - rightAlign - currency - /> - </Box> - </GridColumn> - <GridColumn span={['12/12', '12/12', '12/12', '6/12']}> - <Box paddingY={1}> - <InputController - id={CAPITALNUMBERS.capitalCost} - name={CAPITALNUMBERS.capitalCost} - onChange={debounce(() => { - getTotalCapital() - clearErrors(CAPITALNUMBERS.capitalCost) - }, INPUTCHANGEINTERVAL)} - label={formatMessage(m.capitalCost)} - error={ - errors && getErrorViaPath(errors, CAPITALNUMBERS.capitalCost) - } - backgroundColor="blue" - rightAlign - currency - /> - </Box> - </GridColumn> - </GridRow> - <GridRow> - <GridColumn span={['12/12', '12/12', '12/12', '6/12']}> - <Total - name={CAPITALNUMBERS.total} - total={totalCapital} - label={formatMessage(m.totalCapital)} - /> - </GridColumn> - </GridRow> - </GridContainer> - ) -} diff --git a/libs/application/templates/inao/financial-statement-cemetery/src/fields/OperatingYear/index.tsx b/libs/application/templates/inao/financial-statement-cemetery/src/fields/OperatingYear/index.tsx deleted file mode 100644 index 87dfb926490b..000000000000 --- a/libs/application/templates/inao/financial-statement-cemetery/src/fields/OperatingYear/index.tsx +++ /dev/null @@ -1,84 +0,0 @@ -import { - AlertMessage, - Box, - ContentBlock, - SkeletonLoader, -} from '@island.is/island-ui/core' -import { useLocale } from '@island.is/localization' -import { m } from '../../lib/messages' - -import * as styles from './styles.css' -import { useFormContext } from 'react-hook-form' -import { getErrorViaPath } from '@island.is/application/core' -import { SelectController } from '@island.is/shared/form-fields' -import { useQuery } from '@apollo/client' - -import { getAuditConfig } from '../../graphql' -import { - getConfigInfoForKey, - possibleOperatingYears, -} from '../../utils/helpers' -import { - ABOUTIDS, - CemeteriesBackwardLimit, - CemeteriesYearAllowed, -} from '../../utils/constants' - -export const OperatingYear = () => { - const { data, loading, error } = useQuery(getAuditConfig) - const { formatMessage } = useLocale() - const { - formState: { errors }, - } = useFormContext() - - if (loading) { - return ( - <Box width="half" className={styles.selectSpace}> - <SkeletonLoader height={70} /> - </Box> - ) - } - - if (error || data?.financialStatementsInaoConfig?.length <= 0) { - return ( - <ContentBlock> - <AlertMessage - type="error" - title={formatMessage(m.fetchErrorTitle)} - message={formatMessage(m.fetchErrorMsg)} - /> - </ContentBlock> - ) - } - - const { financialStatementsInaoConfig } = data - - const backwardsYearLimit = getConfigInfoForKey( - financialStatementsInaoConfig, - CemeteriesBackwardLimit, - ) - - const countYearBackwardsFrom = getConfigInfoForKey( - financialStatementsInaoConfig, - CemeteriesYearAllowed, - ) - - const operatingYear = possibleOperatingYears( - backwardsYearLimit, - countYearBackwardsFrom, - ) - - return ( - <Box width="half" className={styles.selectSpace}> - <SelectController - id={ABOUTIDS.operatingYear} - name={ABOUTIDS.operatingYear} - backgroundColor="blue" - label={formatMessage(m.operatingYear)} - placeholder={formatMessage(m.selectOperatingYear)} - error={errors && getErrorViaPath(errors, ABOUTIDS.operatingYear)} - options={operatingYear} - /> - </Box> - ) -} diff --git a/libs/application/templates/inao/financial-statement-cemetery/src/fields/OperatingYear/styles.css.ts b/libs/application/templates/inao/financial-statement-cemetery/src/fields/OperatingYear/styles.css.ts deleted file mode 100644 index c5dd8724f088..000000000000 --- a/libs/application/templates/inao/financial-statement-cemetery/src/fields/OperatingYear/styles.css.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { style } from '@vanilla-extract/css' - -export const selectSpace = style({ - paddingRight: '10px', -}) diff --git a/libs/application/templates/inao/financial-statement-cemetery/src/fields/PowerOfAttorney/index.tsx b/libs/application/templates/inao/financial-statement-cemetery/src/fields/PowerOfAttorney/index.tsx index cdf31e9ad78f..f4dcf6f77248 100644 --- a/libs/application/templates/inao/financial-statement-cemetery/src/fields/PowerOfAttorney/index.tsx +++ b/libs/application/templates/inao/financial-statement-cemetery/src/fields/PowerOfAttorney/index.tsx @@ -11,9 +11,7 @@ import { import { InputController } from '@island.is/shared/form-fields' import { useLocale } from '@island.is/localization' import { IdentityInput, Query } from '@island.is/api/schema' - import { m } from '../../lib/messages' - import { FieldBaseProps } from '@island.is/application/types' import { getErrorViaPath } from '@island.is/application/core' import { IdentityQuery } from '../../graphql' diff --git a/libs/application/templates/inao/financial-statement-cemetery/src/fields/Success/index.tsx b/libs/application/templates/inao/financial-statement-cemetery/src/fields/Success/index.tsx deleted file mode 100644 index e8a6adc55f21..000000000000 --- a/libs/application/templates/inao/financial-statement-cemetery/src/fields/Success/index.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import { - Box, - ContentBlock, - ActionCard, - AlertMessage, -} from '@island.is/island-ui/core' -import { useLocale } from '@island.is/localization' -import { CustomField, FieldBaseProps } from '@island.is/application/types' -import format from 'date-fns/format' -import { m } from '../../lib/messages' -import { FinancialStatementCemetery } from '../../lib/dataSchema' -import { - getCurrentUserType, - isCemetryUnderFinancialLimit, -} from '../../utils/helpers' - -interface PropTypes extends FieldBaseProps { - field: CustomField -} - -export const Success = ({ application }: PropTypes) => { - const { answers, externalData } = application - const applicationAnswers = application.answers as FinancialStatementCemetery - const { formatMessage } = useLocale() - - const getDescriptionText = () => { - const currentDate = format(new Date(), "dd.MM.yyyy 'kl.' kk:mm") - return `${formatMessage(m.operatingYearMsgFirst)} ${ - applicationAnswers.conditionalAbout.operatingYear - } - ${formatMessage(m.individualReceivedMsgSecond)} ${currentDate}` - } - - const shouldShowDigitalSigningMessage = () => { - return isCemetryUnderFinancialLimit(answers, externalData) - } - - return ( - <Box paddingTop={2}> - <Box marginTop={2} marginBottom={5}> - <ContentBlock> - <AlertMessage - type="success" - title={formatMessage(m.returned)} - message={getDescriptionText()} - /> - </ContentBlock> - {shouldShowDigitalSigningMessage() && ( - <Box paddingTop={2}> - <AlertMessage - type="info" - title={formatMessage(m.digitalSignatureTitle)} - message={formatMessage(m.digitalSignatureMessage, { - email: applicationAnswers.about.email, - })} - /> - </Box> - )} - <Box paddingTop={2}> - <ActionCard - heading="" - text={formatMessage(m.myPagesLinkText)} - cta={{ - label: formatMessage(m.continue), - onClick: () => window.open('/minarsidur/postholf', '_blank'), - }} - backgroundColor="blue" - /> - </Box> - </Box> - </Box> - ) -} diff --git a/libs/application/templates/inao/financial-statement-cemetery/src/fields/index.ts b/libs/application/templates/inao/financial-statement-cemetery/src/fields/index.ts index 38e7219bec44..9903bafb3734 100644 --- a/libs/application/templates/inao/financial-statement-cemetery/src/fields/index.ts +++ b/libs/application/templates/inao/financial-statement-cemetery/src/fields/index.ts @@ -1,8 +1,4 @@ export { CemeteryCaretaker } from './CemeteryCareteker' -export { CemeteryOperation } from './CemeteryOperation' -export { KeyNumbersCapital } from './KeyNumbersCapital' -export { CemeteryEquities } from './CemeteryEquities' -export { OperatingYear } from './OperatingYear' export { PowerOfAttorneyFields } from './PowerOfAttorney' export { CemeteryOverview } from './CemeteryOverview' -export { Success } from './Success' +export { CemeteryIncomeLimit } from './CemeteryIncomeLimit' diff --git a/libs/application/templates/inao/financial-statement-cemetery/src/forms/applicationForm/cemeteryKeyNumbersSection/capitalNumberSubSection.ts b/libs/application/templates/inao/financial-statement-cemetery/src/forms/applicationForm/cemeteryKeyNumbersSection/capitalNumberSubSection.ts index 549bf3b78658..86bc4ae3eaa0 100644 --- a/libs/application/templates/inao/financial-statement-cemetery/src/forms/applicationForm/cemeteryKeyNumbersSection/capitalNumberSubSection.ts +++ b/libs/application/templates/inao/financial-statement-cemetery/src/forms/applicationForm/cemeteryKeyNumbersSection/capitalNumberSubSection.ts @@ -1,10 +1,12 @@ import { - buildCustomField, + buildDisplayField, buildMultiField, buildSubSection, + buildTextField, } from '@island.is/application/core' import { m } from '../../../lib/messages' import { CAPITALNUMBERS } from '../../../utils/constants' +import { sumCapitalNumbers } from '../../../utils/helpers' export const capitalNumberSubSection = buildSubSection({ id: 'keynumbers.capitalNumbers', @@ -15,12 +17,27 @@ export const capitalNumberSubSection = buildSubSection({ title: m.capitalNumbersSectionTitle, description: m.fillOutAppopriate, children: [ - buildCustomField({ - id: 'capitalNumberField', + buildTextField({ + id: CAPITALNUMBERS.capitalIncome, + title: m.capitalIncome, + width: 'half', + variant: 'currency', + rightAlign: true, + }), + buildTextField({ + id: CAPITALNUMBERS.capitalCost, + title: m.capitalCost, + width: 'half', + variant: 'currency', + rightAlign: true, + }), + buildDisplayField({ + id: CAPITALNUMBERS.total, title: '', - description: '', - component: 'KeyNumbersCapital', - childInputIds: Object.values(CAPITALNUMBERS), + label: m.totalCapital, + value: sumCapitalNumbers, + variant: 'currency', + rightAlign: true, }), ], }), diff --git a/libs/application/templates/inao/financial-statement-cemetery/src/forms/applicationForm/cemeteryKeyNumbersSection/equityAndLiabilitySubSection.ts b/libs/application/templates/inao/financial-statement-cemetery/src/forms/applicationForm/cemeteryKeyNumbersSection/equityAndLiabilitySubSection.ts index f5cb6fdf8f6e..1e0f6a2987f2 100644 --- a/libs/application/templates/inao/financial-statement-cemetery/src/forms/applicationForm/cemeteryKeyNumbersSection/equityAndLiabilitySubSection.ts +++ b/libs/application/templates/inao/financial-statement-cemetery/src/forms/applicationForm/cemeteryKeyNumbersSection/equityAndLiabilitySubSection.ts @@ -1,10 +1,24 @@ import { - buildCustomField, + buildAlertMessageField, + buildDescriptionField, + buildDisplayField, buildMultiField, buildSubSection, + buildTextField, } from '@island.is/application/core' import { m } from '../../../lib/messages' -import { CEMETERYEQUITIESANDLIABILITIESIDS } from '../../../utils/constants' +import { + CEMETERYEQUITIESANDLIABILITIESIDS, + EQUITYANDLIABILITIESTOTALS, +} from '../../../utils/constants' +import { + operationResult, + showEquitiesAndLiabilitiesAlert, + sumAssets, + sumLiabilities, + sumTotalEquity, + sumTotalEquityAndLiabilities, +} from '../../../utils/helpers' export const equityAndLiabilitiesSubSection = buildSubSection({ id: 'keyNumbers.cemetryEquitiesAndLiabilities', @@ -15,12 +29,129 @@ export const equityAndLiabilitiesSubSection = buildSubSection({ title: m.keyNumbersDebt, description: m.fillOutAppopriate, children: [ - buildCustomField({ - id: 'cemeteryEquitiesAndLiabilities', - title: m.keyNumbersDebt, - description: m.fillOutAppopriate, - component: 'CemeteryEquities', - childInputIds: Object.values(CEMETERYEQUITIESANDLIABILITIESIDS), + // Assets + buildDescriptionField({ + id: 'assetsDescription', + title: m.properties, + titleVariant: 'h3', + }), + buildTextField({ + id: CEMETERYEQUITIESANDLIABILITIESIDS.fixedAssetsTotal, + title: m.fixedAssetsTotal, + width: 'half', + variant: 'currency', + rightAlign: true, + }), + buildTextField({ + id: CEMETERYEQUITIESANDLIABILITIESIDS.currentAssets, + title: m.currentAssets, + width: 'half', + variant: 'currency', + rightAlign: true, + }), + buildDisplayField({ + id: EQUITYANDLIABILITIESTOTALS.assetsTotal, + title: '', + label: m.totalAssets, + value: sumAssets, + variant: 'currency', + rightAlign: true, + }), + + // Debt + buildDescriptionField({ + id: 'debtsDescription', + title: m.debts, + titleVariant: 'h3', + }), + buildTextField({ + id: CEMETERYEQUITIESANDLIABILITIESIDS.longTerm, + title: m.longTerm, + width: 'half', + variant: 'currency', + rightAlign: true, + }), + buildTextField({ + id: CEMETERYEQUITIESANDLIABILITIESIDS.shortTerm, + title: m.shortTerm, + width: 'half', + variant: 'currency', + rightAlign: true, + }), + buildDisplayField({ + id: EQUITYANDLIABILITIESTOTALS.liabilitiesTotal, + title: '', + label: m.totalLiabilities, + value: sumLiabilities, + variant: 'currency', + rightAlign: true, + }), + + // Equity + buildDescriptionField({ + id: 'equityDescription', + title: m.equity, + titleVariant: 'h3', + }), + buildTextField({ + id: CEMETERYEQUITIESANDLIABILITIESIDS.equityAtTheBeginningOfTheYear, + title: m.equityAtTheBeginningOfTheYear, + width: 'half', + variant: 'currency', + rightAlign: true, + }), + buildTextField({ + id: CEMETERYEQUITIESANDLIABILITIESIDS.revaluationDueToPriceChanges, + title: m.revaluationDueToPriceChanges, + width: 'half', + variant: 'currency', + rightAlign: true, + }), + buildTextField({ + id: CEMETERYEQUITIESANDLIABILITIESIDS.reevaluateOther, + title: m.reevaluateOther, + width: 'half', + variant: 'currency', + rightAlign: true, + }), + buildDisplayField({ + id: CEMETERYEQUITIESANDLIABILITIESIDS.operationResult, + title: '', + label: m.operationResult, + value: operationResult, + variant: 'currency', + rightAlign: true, + width: 'half', + }), + buildDisplayField({ + id: CEMETERYEQUITIESANDLIABILITIESIDS.equityTotal, + title: '', + label: m.totalEquity, + value: sumTotalEquity, + variant: 'currency', + rightAlign: true, + }), + + // Debts and Equity + buildDescriptionField({ + id: 'debtsAndEquityDescription', + title: m.debtsAndCash, + titleVariant: 'h3', + }), + buildDisplayField({ + id: EQUITYANDLIABILITIESTOTALS.equityAndLiabilitiesTotal, + title: '', + value: sumTotalEquityAndLiabilities, + variant: 'currency', + rightAlign: true, + }), + + buildAlertMessageField({ + condition: showEquitiesAndLiabilitiesAlert, + id: 'equityAndLiabilityError', + title: m.equityErrorTitle, + message: m.equityDebtsAssetsValidatorError, + alertType: 'error', }), ], }), diff --git a/libs/application/templates/inao/financial-statement-cemetery/src/forms/applicationForm/cemeteryKeyNumbersSection/opperatingCostSubSection.ts b/libs/application/templates/inao/financial-statement-cemetery/src/forms/applicationForm/cemeteryKeyNumbersSection/opperatingCostSubSection.ts index 66ef7c08bc52..34329fa8ee15 100644 --- a/libs/application/templates/inao/financial-statement-cemetery/src/forms/applicationForm/cemeteryKeyNumbersSection/opperatingCostSubSection.ts +++ b/libs/application/templates/inao/financial-statement-cemetery/src/forms/applicationForm/cemeteryKeyNumbersSection/opperatingCostSubSection.ts @@ -1,10 +1,18 @@ import { buildCustomField, + buildDescriptionField, + buildDisplayField, buildMultiField, buildSubSection, + buildTextField, } from '@island.is/application/core' -import { CEMETERYOPERATIONIDS } from '../../../utils/constants' +import { CEMETERYOPERATIONIDS, OPERATINGCOST } from '../../../utils/constants' import { m } from '../../../lib/messages' +import { + sumExpenses, + sumIncome, + sumOperatingResults, +} from '../../../utils/helpers' export const opperatingCostSubSection = buildSubSection({ id: 'operatingCost', @@ -16,10 +24,129 @@ export const opperatingCostSubSection = buildSubSection({ description: m.fillOutAppopriate, children: [ buildCustomField({ - id: 'cemetryKeyNumbers', + id: 'cemetryIncomeLimit', + title: '', + component: 'CemeteryIncomeLimit', + }), + // Income + buildDescriptionField({ + id: 'incomeDescription', + title: m.income, + titleVariant: 'h3', + }), + buildTextField({ + id: CEMETERYOPERATIONIDS.careIncome, + title: m.careIncome, + width: 'half', + variant: 'currency', + rightAlign: true, + }), + buildTextField({ + id: CEMETERYOPERATIONIDS.burialRevenue, + title: m.burialRevenue, + width: 'half', + variant: 'currency', + rightAlign: true, + }), + buildTextField({ + id: CEMETERYOPERATIONIDS.grantFromTheCemeteryFund, + title: m.grantFromTheCemeteryFund, + width: 'half', + variant: 'currency', + rightAlign: true, + }), + buildTextField({ + id: CEMETERYOPERATIONIDS.otherIncome, + title: m.otherIncome, + width: 'half', + variant: 'currency', + rightAlign: true, + }), + buildDisplayField({ + id: CEMETERYOPERATIONIDS.totalIncome, + title: '', + label: m.totalIncome, + value: sumIncome, + variant: 'currency', + rightAlign: true, + }), + + // Expenses + buildDescriptionField({ + id: 'expensesDescription', + title: m.expenses, + titleVariant: 'h3', + }), + buildTextField({ + id: CEMETERYOPERATIONIDS.payroll, + title: m.payroll, + width: 'half', + variant: 'currency', + rightAlign: true, + }), + buildTextField({ + id: CEMETERYOPERATIONIDS.funeralCost, + title: m.funeralCost, + width: 'half', + variant: 'currency', + rightAlign: true, + }), + buildTextField({ + id: CEMETERYOPERATIONIDS.chapelExpense, + title: m.chapelExpense, + width: 'half', + variant: 'currency', + rightAlign: true, + }), + buildTextField({ + id: CEMETERYOPERATIONIDS.donationsToCemeteryFund, + title: m.donationsToCemeteryFund, + width: 'half', + variant: 'currency', + rightAlign: true, + }), + buildTextField({ + id: CEMETERYOPERATIONIDS.donationsToOther, + title: m.donationsToOther, + width: 'half', + variant: 'currency', + rightAlign: true, + }), + buildTextField({ + id: CEMETERYOPERATIONIDS.otherOperationCost, + title: m.otherOperationCost, + width: 'half', + variant: 'currency', + rightAlign: true, + }), + buildTextField({ + id: CEMETERYOPERATIONIDS.depreciation, + title: m.depreciation, + width: 'half', + variant: 'currency', + rightAlign: true, + }), + buildDisplayField({ + id: CEMETERYOPERATIONIDS.totalExpense, + title: '', + label: m.totalExpenses, + value: sumExpenses, + variant: 'currency', + rightAlign: true, + }), + + // Operating results + buildDescriptionField({ + id: 'operatingResultsDescription', + title: m.operatingCost, + titleVariant: 'h3', + }), + buildDisplayField({ + id: OPERATINGCOST.total, title: '', - component: 'CemeteryOperation', - childInputIds: Object.values(CEMETERYOPERATIONIDS), + value: sumOperatingResults, + variant: 'currency', + rightAlign: true, }), ], }), diff --git a/libs/application/templates/inao/financial-statement-cemetery/src/forms/applicationForm/clientInfoSection/index.ts b/libs/application/templates/inao/financial-statement-cemetery/src/forms/applicationForm/clientInfoSection/index.ts index 387a2072098f..9916366fd934 100644 --- a/libs/application/templates/inao/financial-statement-cemetery/src/forms/applicationForm/clientInfoSection/index.ts +++ b/libs/application/templates/inao/financial-statement-cemetery/src/forms/applicationForm/clientInfoSection/index.ts @@ -1,4 +1,5 @@ import { + buildAsyncSelectField, buildCustomField, buildDescriptionField, buildMultiField, @@ -9,6 +10,9 @@ import { Application, UserProfile } from '@island.is/application/types' import { m } from '../../../lib/messages' import { ABOUTIDS } from '../../../utils/constants' import { Identity } from '@island.is/api/schema' +import { getAuditConfig } from '../../../graphql' +import { AuditConfig } from '../../../types/types' +import { getYearOptions } from '../../../utils/helpers' export const clientInfoSection = buildSection({ id: 'info', @@ -19,15 +23,21 @@ export const clientInfoSection = buildSection({ title: m.info, description: m.reviewContact, children: [ - buildDescriptionField({ + buildAsyncSelectField({ id: ABOUTIDS.operatingYear, - title: '', + title: m.operatingYear, + placeholder: m.selectOperatingYear, + width: 'half', + loadOptions: async ({ apolloClient }) => { + const { data } = await apolloClient.query<AuditConfig>({ + query: getAuditConfig, + }) + return getYearOptions(data) + }, }), - buildCustomField({ - id: 'OperatingYear', - childInputIds: [ABOUTIDS.operatingYear], + buildDescriptionField({ + id: 'about.description', title: '', - component: 'OperatingYear', }), buildTextField({ id: 'about.nationalId', @@ -49,7 +59,7 @@ export const clientInfoSection = buildSection({ }, }), buildDescriptionField({ - id: ABOUTIDS.powerOfAttorneyName, + id: 'about.description2', title: '', }), buildCustomField({ diff --git a/libs/application/templates/inao/financial-statement-cemetery/src/forms/applicationForm/conclusionSection/conclusionSection.ts b/libs/application/templates/inao/financial-statement-cemetery/src/forms/applicationForm/conclusionSection/conclusionSection.ts new file mode 100644 index 000000000000..1ca0e1529209 --- /dev/null +++ b/libs/application/templates/inao/financial-statement-cemetery/src/forms/applicationForm/conclusionSection/conclusionSection.ts @@ -0,0 +1,19 @@ +import { buildFormConclusionSection } from '@island.is/application/ui-forms' +import { m } from '../../../lib/messages' +import { getValueViaPath } from '@island.is/application/core' + +export const conclusionSection = buildFormConclusionSection({ + multiFieldTitle: m.received, + alertTitle: m.returned, + alertMessage: (application) => { + const year = getValueViaPath<string>( + application.answers, + 'conditionalAbout.operatingYear', + ) + return { + ...m.conclusionAlertMessage, + values: { value1: year }, + } + }, + accordion: false, +}) diff --git a/libs/application/templates/inao/financial-statement-cemetery/src/forms/applicationForm/index.ts b/libs/application/templates/inao/financial-statement-cemetery/src/forms/applicationForm/index.ts index bf4875117557..736e879139ee 100644 --- a/libs/application/templates/inao/financial-statement-cemetery/src/forms/applicationForm/index.ts +++ b/libs/application/templates/inao/financial-statement-cemetery/src/forms/applicationForm/index.ts @@ -7,12 +7,13 @@ import { cemeteryFinancialStatementSection } from './cemeteryFinancialStatementS import Logo from '../../components/Logo' import { clientInfoSection } from './clientInfoSection' import { overviewSection } from './overviewSection' +import { conclusionSection } from './conclusionSection/conclusionSection' export const FinancialStatementCemeteryForm: Form = buildForm({ id: 'FinancialStatementCemeteryForm', title: m.applicationTitle, mode: FormModes.DRAFT, - renderLastScreenButton: false, + renderLastScreenButton: true, logo: Logo, children: [ clientInfoSection, @@ -20,5 +21,6 @@ export const FinancialStatementCemeteryForm: Form = buildForm({ cemeteryCaretekerSection, cemeteryFinancialStatementSection, overviewSection, + conclusionSection, ], }) diff --git a/libs/application/templates/inao/financial-statement-cemetery/src/forms/applicationForm/overviewSection/index.ts b/libs/application/templates/inao/financial-statement-cemetery/src/forms/applicationForm/overviewSection/index.ts index 3d1cb64989b8..1bd658de4cfc 100644 --- a/libs/application/templates/inao/financial-statement-cemetery/src/forms/applicationForm/overviewSection/index.ts +++ b/libs/application/templates/inao/financial-statement-cemetery/src/forms/applicationForm/overviewSection/index.ts @@ -1,9 +1,51 @@ -import { buildSection } from '@island.is/application/core' +import { + buildCheckboxField, + buildCustomField, + buildMultiField, + buildSection, + buildSubmitField, + YES, +} from '@island.is/application/core' import { m } from '../../../lib/messages' -import { overviewMultiField } from './oveviewMultiField' +import { DefaultEvents } from '@island.is/application/types' export const overviewSection = buildSection({ id: 'overviewSection', title: m.overviewSectionTitle, - children: [overviewMultiField], + children: [ + buildMultiField({ + id: 'overview', + title: m.yearlyOverview, + description: m.review, + children: [ + buildCustomField({ + id: 'overviewCemetryField', + title: '', + doesNotRequireAnswer: true, + component: 'CemeteryOverview', + }), + buildCheckboxField({ + id: 'approveOverview', + title: '', + options: [ + { + label: m.overviewCorrect, + value: YES, + }, + ], + }), + buildSubmitField({ + id: 'overview.submit', + title: '', + actions: [ + { + event: DefaultEvents.SUBMIT, + name: m.send, + type: 'primary', + }, + ], + }), + ], + }), + ], }) diff --git a/libs/application/templates/inao/financial-statement-cemetery/src/forms/applicationForm/overviewSection/oveviewMultiField.ts b/libs/application/templates/inao/financial-statement-cemetery/src/forms/applicationForm/overviewSection/oveviewMultiField.ts deleted file mode 100644 index 4f93c70dd20a..000000000000 --- a/libs/application/templates/inao/financial-statement-cemetery/src/forms/applicationForm/overviewSection/oveviewMultiField.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { buildCustomField, buildMultiField } from '@island.is/application/core' -import { m } from '../../../lib/messages' - -export const overviewMultiField = buildMultiField({ - id: 'overview', - title: m.yearlyOverview, - description: m.review, - children: [ - buildCustomField({ - id: 'overviewCemetryField', - title: '', - doesNotRequireAnswer: true, - component: 'CemeteryOverview', - }), - ], -}) diff --git a/libs/application/templates/inao/financial-statement-cemetery/src/forms/done/conclusionSection.ts b/libs/application/templates/inao/financial-statement-cemetery/src/forms/done/conclusionSection.ts deleted file mode 100644 index 532136f509bc..000000000000 --- a/libs/application/templates/inao/financial-statement-cemetery/src/forms/done/conclusionSection.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { - buildCustomField, - buildMultiField, - buildSection, -} from '@island.is/application/core' -import { m } from '../../lib/messages' - -export const conclusionSection = buildSection({ - id: 'conclusionSection', - title: '', - children: [ - buildMultiField({ - id: 'conclusion', - title: m.received, - children: [ - buildCustomField({ - id: 'overview', - component: 'Success', - title: m.applicationAccept, - }), - ], - }), - ], -}) diff --git a/libs/application/templates/inao/financial-statement-cemetery/src/forms/done/index.ts b/libs/application/templates/inao/financial-statement-cemetery/src/forms/done/index.ts index e4ea0c21bc77..5c2dd8c0f237 100644 --- a/libs/application/templates/inao/financial-statement-cemetery/src/forms/done/index.ts +++ b/libs/application/templates/inao/financial-statement-cemetery/src/forms/done/index.ts @@ -1,11 +1,13 @@ import { buildForm } from '@island.is/application/core' import { Form, FormModes } from '@island.is/application/types' -import { conclusionSection } from './conclusionSection' import { m } from '../../lib/messages' +import { conclusionSection } from '../applicationForm/conclusionSection/conclusionSection' +import Logo from '../../components/Logo' export const done: Form = buildForm({ id: 'done', title: m.applicationAccept, mode: FormModes.COMPLETED, + logo: Logo, children: [conclusionSection], }) diff --git a/libs/application/templates/inao/financial-statement-cemetery/src/forms/notAllowed/index.ts b/libs/application/templates/inao/financial-statement-cemetery/src/forms/notAllowed/index.ts index 64b658b27a8e..bc8aa7c1ae11 100644 --- a/libs/application/templates/inao/financial-statement-cemetery/src/forms/notAllowed/index.ts +++ b/libs/application/templates/inao/financial-statement-cemetery/src/forms/notAllowed/index.ts @@ -1,10 +1,26 @@ -import { buildForm } from '@island.is/application/core' -import { notAllowedSection } from './notAllowedSection' +import { + buildDescriptionField, + buildForm, + buildSection, +} from '@island.is/application/core' import Logo from '../../components/Logo' +import { m } from '../../lib/messages' export const notAllowedForm = buildForm({ id: 'notAllowedForm', title: '', logo: Logo, - children: [notAllowedSection], + children: [ + buildSection({ + id: 'notAllowedSection', + title: '', + children: [ + buildDescriptionField({ + id: 'notAllowedDescription', + title: m.notAllowedTitle, + description: m.notAllowedDescription, + }), + ], + }), + ], }) diff --git a/libs/application/templates/inao/financial-statement-cemetery/src/forms/notAllowed/notAllowedSection.ts b/libs/application/templates/inao/financial-statement-cemetery/src/forms/notAllowed/notAllowedSection.ts deleted file mode 100644 index 5c8359dd2a4b..000000000000 --- a/libs/application/templates/inao/financial-statement-cemetery/src/forms/notAllowed/notAllowedSection.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { - buildDescriptionField, - buildSection, -} from '@island.is/application/core' -import { m } from '../../lib/messages' - -export const notAllowedSection = buildSection({ - id: 'notAllowedSection', - title: '', - children: [ - buildDescriptionField({ - id: 'notAllowedDescription', - title: m.notAllowedTitle, - description: m.notAllowedDescription, - }), - ], -}) diff --git a/libs/application/templates/inao/financial-statement-cemetery/src/forms/prerequisites/index.ts b/libs/application/templates/inao/financial-statement-cemetery/src/forms/prerequisites/index.ts index e8f8d9631b70..4e69a430c00e 100644 --- a/libs/application/templates/inao/financial-statement-cemetery/src/forms/prerequisites/index.ts +++ b/libs/application/templates/inao/financial-statement-cemetery/src/forms/prerequisites/index.ts @@ -1,7 +1,19 @@ -import { buildForm } from '@island.is/application/core' -import { Form, FormModes } from '@island.is/application/types' +import { + buildDataProviderItem, + buildExternalDataProvider, + buildForm, + buildSection, + buildSubmitField, + coreMessages, +} from '@island.is/application/core' +import { DefaultEvents, Form, FormModes } from '@island.is/application/types' import Logo from '../../components/Logo' -import { prerequisitesSection } from './prerequsitesSection' +import { + CurrentUserTypeProvider, + IdentityApiProvider, + UserInfoApi, +} from '../../dataProviders' +import { m } from '../../lib/messages' export const PrerequisitesForm: Form = buildForm({ id: 'PrerequisitesForm', @@ -9,5 +21,47 @@ export const PrerequisitesForm: Form = buildForm({ mode: FormModes.NOT_STARTED, renderLastScreenButton: true, logo: Logo, - children: [prerequisitesSection], + children: [ + buildSection({ + id: 'ExternalDataSection', + title: '', + children: [ + buildExternalDataProvider({ + id: 'approveExternalData', + title: m.dataCollectionTitleUserCemetery, + checkboxLabel: m.dataCollectionCheckboxLabel, + dataProviders: [ + buildDataProviderItem({ + provider: IdentityApiProvider, + title: m.dataCollectionNationalRegistryTitle, + subTitle: m.dataCollectionNationalRegistrySubtitle, + }), + buildDataProviderItem({ + provider: UserInfoApi, + title: m.dataCollectionUserProfileTitle, + subTitle: m.dataCollectionUserProfileSubtitle, + }), + buildDataProviderItem({ + provider: CurrentUserTypeProvider, + title: m.dataCollectionUserFinancialInfoTitle, + subTitle: m.dataCollectionUserFinancialInfo, + }), + ], + submitField: buildSubmitField({ + id: 'submit', + placement: 'footer', + title: '', + refetchApplicationAfterSubmit: true, + actions: [ + { + event: DefaultEvents.SUBMIT, + name: coreMessages.buttonNext, + type: 'primary', + }, + ], + }), + }), + ], + }), + ], }) diff --git a/libs/application/templates/inao/financial-statement-cemetery/src/forms/prerequisites/prerequsitesSection.ts b/libs/application/templates/inao/financial-statement-cemetery/src/forms/prerequisites/prerequsitesSection.ts deleted file mode 100644 index 6b9bed52110a..000000000000 --- a/libs/application/templates/inao/financial-statement-cemetery/src/forms/prerequisites/prerequsitesSection.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { - buildDataProviderItem, - buildExternalDataProvider, - buildSection, - buildSubmitField, - coreMessages, -} from '@island.is/application/core' -import { m } from '../../lib/messages' -import { - CurrentUserTypeProvider, - IndentityApiProvider, - UserInfoApi, -} from '../../dataProviders' -import { DefaultEvents } from '@island.is/application/types' - -export const prerequisitesSection = buildSection({ - id: 'ExternalDataSection', - title: '', - children: [ - buildExternalDataProvider({ - id: 'approveExternalData', - title: m.dataCollectionTitleUserCemetry, - checkboxLabel: m.dataCollectionCheckboxLabel, - dataProviders: [ - buildDataProviderItem({ - provider: IndentityApiProvider, - title: m.dataCollectionNationalRegistryTitle, - subTitle: m.dataCollectionNationalRegistrySubtitle, - }), - buildDataProviderItem({ - provider: UserInfoApi, - title: m.dataCollectionUserProfileTitle, - subTitle: m.dataCollectionUserProfileSubtitle, - }), - buildDataProviderItem({ - provider: CurrentUserTypeProvider, - title: m.dataCollectionUserFinancialInfoTitle, - subTitle: m.dataCollectionUserFinancialInfo, - }), - ], - submitField: buildSubmitField({ - id: 'submit', - placement: 'footer', - title: '', - refetchApplicationAfterSubmit: true, - actions: [ - { - event: DefaultEvents.SUBMIT, - name: coreMessages.buttonNext, - type: 'primary', - }, - ], - }), - }), - ], -}) diff --git a/libs/application/templates/inao/financial-statement-cemetery/src/lib/dataSchema.ts b/libs/application/templates/inao/financial-statement-cemetery/src/lib/dataSchema.ts index b177ee8daac2..97379678b103 100644 --- a/libs/application/templates/inao/financial-statement-cemetery/src/lib/dataSchema.ts +++ b/libs/application/templates/inao/financial-statement-cemetery/src/lib/dataSchema.ts @@ -3,7 +3,11 @@ import { m } from './messages' import * as kennitala from 'kennitala' import { parsePhoneNumberFromString } from 'libphonenumber-js' import { BOARDMEMEBER, CARETAKER } from '../utils/constants' -import { getBoardmembersAndCaretakers } from '../utils/helpers' +import { + isPositiveNumberInString, + getBoardmembersAndCaretakers, +} from '../utils/helpers' +import { YES } from '@island.is/application/types' const FileSchema = z.object({ name: z.string(), @@ -11,117 +15,191 @@ const FileSchema = z.object({ url: z.string().optional(), }) -const checkIfNegative = (inputNumber: string) => { - if (Number(inputNumber) < 0) { - return false - } else { - return true - } -} - -const conditionalAbout = z.object({ - operatingYear: z.string().refine((x) => !!x, { params: m.required }), -}) - const cemeteryOperation = z.object({ incomeLimit: z.string().optional(), }) -const cemeteryLiability = z.object({ - longTerm: z - .string() - .refine((x) => !!x, { params: m.required }) - .refine((x) => checkIfNegative(x), { params: m.negativeNumberError }), - shortTerm: z - .string() - .refine((x) => !!x, { params: m.required }) - .refine((x) => checkIfNegative(x), { params: m.negativeNumberError }), - total: z.string().refine((x) => !!x, { params: m.required }), +const conditionalAbout = z.object({ + operatingYear: z.string().refine((x) => !!x, { params: m.required }), }) -const cemeteryAsset = z.object({ - currentAssets: z - .string() - .refine((x) => !!x, { params: m.required }) - .refine((x) => checkIfNegative(x), { params: m.negativeNumberError }), - fixedAssetsTotal: z +const about = z.object({ + nationalId: z .string() - .refine((x) => !!x, { params: m.required }) - .refine((x) => checkIfNegative(x), { params: m.negativeNumberError }), - total: z.string(), + .refine((val) => (val ? kennitala.isValid(val) : false), { + params: m.nationalIdError, + }), + fullName: z.string().refine((x) => !!x, { params: m.required }), + powerOfAttorneyNationalId: z.string().optional(), + powerOfAttorneyName: z.string().optional(), + phoneNumber: z.string().refine( + (p) => { + const phoneNumber = parsePhoneNumberFromString(p, 'IS') + return phoneNumber && phoneNumber.isValid() + }, + { params: m.dataSchemePhoneNumber }, + ), + email: z.string().email(), }) +// Key numbers - Income and Expenses - Income const cemeteryIncome = z.object({ careIncome: z .string() .refine((x) => !!x, { params: m.required }) - .refine((x) => checkIfNegative(x), { params: m.negativeNumberError }), + .refine((x) => isPositiveNumberInString(x), { + params: m.negativeNumberError, + }), burialRevenue: z .string() .refine((x) => !!x, { params: m.required }) - .refine((x) => checkIfNegative(x), { params: m.negativeNumberError }), + .refine((x) => isPositiveNumberInString(x), { + params: m.negativeNumberError, + }), grantFromTheCemeteryFund: z .string() .refine((x) => !!x, { params: m.required }) - .refine((x) => checkIfNegative(x), { params: m.negativeNumberError }), + .refine((x) => isPositiveNumberInString(x), { + params: m.negativeNumberError, + }), otherIncome: z .string() .refine((x) => !!x, { params: m.required }) - .refine((x) => checkIfNegative(x), { params: m.negativeNumberError }), + .refine((x) => isPositiveNumberInString(x), { + params: m.negativeNumberError, + }), total: z.string(), }) +// Key numbers - Income and Expenses - Expenses const cemeteryExpense = z.object({ payroll: z .string() .refine((x) => !!x, { params: m.required }) - .refine((x) => checkIfNegative(x), { params: m.negativeNumberError }), + .refine((x) => isPositiveNumberInString(x), { + params: m.negativeNumberError, + }), funeralCost: z .string() .refine((x) => !!x, { params: m.required }) - .refine((x) => checkIfNegative(x), { params: m.negativeNumberError }), + .refine((x) => isPositiveNumberInString(x), { + params: m.negativeNumberError, + }), chapelExpense: z .string() .refine((x) => !!x, { params: m.required }) - .refine((x) => checkIfNegative(x), { params: m.negativeNumberError }), + .refine((x) => isPositiveNumberInString(x), { + params: m.negativeNumberError, + }), donationsToOther: z .string() .refine((x) => !!x, { params: m.required }) - .refine((x) => checkIfNegative(x), { params: m.negativeNumberError }), + .refine((x) => isPositiveNumberInString(x), { + params: m.negativeNumberError, + }), cemeteryFundExpense: z .string() .refine((x) => !!x, { params: m.required }) - .refine((x) => checkIfNegative(x), { params: m.negativeNumberError }), + .refine((x) => isPositiveNumberInString(x), { + params: m.negativeNumberError, + }), otherOperationCost: z .string() .refine((x) => !!x, { params: m.required }) - .refine((x) => checkIfNegative(x), { params: m.negativeNumberError }), + .refine((x) => isPositiveNumberInString(x), { + params: m.negativeNumberError, + }), depreciation: z .string() .refine((x) => !!x, { params: m.required }) - .refine((x) => checkIfNegative(x), { params: m.negativeNumberError }), + .refine((x) => isPositiveNumberInString(x), { + params: m.negativeNumberError, + }), total: z.string(), }) -const about = z.object({ - nationalId: z +// Key numbers - Capital numbers +const capitalNumbers = z.object({ + capitalIncome: z .string() - .refine((val) => (val ? kennitala.isValid(val) : false), { - params: m.nationalIdError, + .refine((x) => !!x, { params: m.required }) + .refine((x) => isPositiveNumberInString(x), { + params: m.negativeNumberError, }), - fullName: z.string().refine((x) => !!x, { params: m.required }), - powerOfAttorneyNationalId: z.string().optional(), - powerOfAttorneyName: z.string().optional(), - phoneNumber: z.string().refine( - (p) => { - const phoneNumber = parsePhoneNumberFromString(p, 'IS') - return phoneNumber && phoneNumber.isValid() - }, - { params: m.dataSchemePhoneNumber }, - ), - email: z.string().email(), + capitalCost: z + .string() + .refine((x) => !!x, { params: m.required }) + .refine((x) => isPositiveNumberInString(x), { + params: m.negativeNumberError, + }), + total: z.string(), }) +// Key numbers - Equity and Liability - Assets +const cemeteryAsset = z.object({ + currentAssets: z + .string() + .refine((x) => !!x, { params: m.required }) + .refine((x) => isPositiveNumberInString(x), { + params: m.negativeNumberError, + }), + fixedAssetsTotal: z + .string() + .refine((x) => !!x, { params: m.required }) + .refine((x) => isPositiveNumberInString(x), { + params: m.negativeNumberError, + }), +}) + +// Key numbers - Equity and Liability - Liabilities +const cemeteryLiability = z.object({ + longTerm: z + .string() + .refine((x) => !!x, { params: m.required }) + .refine((x) => isPositiveNumberInString(x), { + params: m.negativeNumberError, + }), + shortTerm: z + .string() + .refine((x) => !!x, { params: m.required }) + .refine((x) => isPositiveNumberInString(x), { + params: m.negativeNumberError, + }), +}) + +// Key numbers - Equity and Liability - Equity +const cemeteryEquity = z.object({ + equityAtTheBeginningOfTheYear: z + .string() + .refine((x) => !!x, { params: m.required }), + operationResult: z.string(), + revaluationDueToPriceChanges: z + .string() + .refine((x) => !!x, { params: m.required }) + .refine((x) => isPositiveNumberInString(x), { + params: m.negativeNumberError, + }), + reevaluateOther: z + .string() + .refine((x) => !!x, { params: m.required }) + .refine((x) => isPositiveNumberInString(x), { + params: m.negativeNumberError, + }), + total: z.string(), +}) + +// Key numbers - Equity and Liability - Equity and Liabilities totals +const equityAndLiabilitiesTotals = z + .object({ + assetsTotal: z.string(), + liabilitiesTotal: z.string(), + equityAndLiabilitiesTotal: z.string(), + }) + .refine((x) => x.assetsTotal === x.equityAndLiabilitiesTotal, { + message: 'equityAndLiabilities.total must match assets.total', + path: ['equityAndLiabilitiesTotals', 'equityAndLiabilitiesTotal'], + }) + const cemeteryCaretaker = z .array( z.object({ @@ -178,54 +256,23 @@ const cemeteryCaretaker = z { params: m.errormemberNotUnique }, ) -const cemeteryEquity = z.object({ - equityAtTheBeginningOfTheYear: z - .string() - .refine((x) => !!x, { params: m.required }), - operationResult: z.string(), - revaluationDueToPriceChanges: z - .string() - .refine((x) => !!x, { params: m.required }) - .refine((x) => checkIfNegative(x), { params: m.negativeNumberError }), - reevaluateOther: z - .string() - .refine((x) => !!x, { params: m.required }) - .refine((x) => checkIfNegative(x), { params: m.negativeNumberError }), - total: z.string(), -}) - -const equityAndLiabilities = z.object({ - total: z.string(), -}) - -const capitalNumbers = z.object({ - capitalIncome: z - .string() - .refine((x) => !!x, { params: m.required }) - .refine((x) => checkIfNegative(x), { params: m.negativeNumberError }), - capitalCost: z - .string() - .refine((x) => !!x, { params: m.required }) - .refine((x) => checkIfNegative(x), { params: m.negativeNumberError }), - total: z.string(), -}) - export const dataSchema = z.object({ - conditionalAbout, - about, approveExternalData: z.boolean().refine((v) => v), cemeteryOperation, - cemeteryAsset, - cemeteryLiability, + conditionalAbout, + about, cemeteryIncome, cemeteryExpense, + capitalNumbers, + cemeteryAsset, + cemeteryLiability, + cemeteryEquity, + equityAndLiabilitiesTotals, + cemeteryCaretaker, attachments: z.object({ file: z.array(FileSchema).nonempty(), }), - cemeteryCaretaker, - cemeteryEquity, - equityAndLiabilities, - capitalNumbers, + approveOverview: z.array(z.literal(YES)).length(1), }) export type FinancialStatementCemetery = z.TypeOf<typeof dataSchema> diff --git a/libs/application/templates/inao/financial-statement-cemetery/src/lib/financialStatementCemeteryTemplate.ts b/libs/application/templates/inao/financial-statement-cemetery/src/lib/financialStatementCemeteryTemplate.ts index a92fbc786cb5..0c851e4c2dd1 100644 --- a/libs/application/templates/inao/financial-statement-cemetery/src/lib/financialStatementCemeteryTemplate.ts +++ b/libs/application/templates/inao/financial-statement-cemetery/src/lib/financialStatementCemeteryTemplate.ts @@ -19,7 +19,7 @@ import { AuthDelegationType } from '@island.is/shared/types' import { dataSchema } from './dataSchema' import { CurrentUserTypeProvider, - IndentityApiProvider, + IdentityApiProvider, NationalRegistryUserApi, UserInfoApi, } from '../dataProviders' @@ -79,7 +79,7 @@ const FinancialStatementCemeteryTemplate: ApplicationTemplate< delete: true, api: [ CurrentUserTypeProvider, - IndentityApiProvider, + IdentityApiProvider, NationalRegistryUserApi, UserInfoApi, ], diff --git a/libs/application/templates/inao/financial-statement-cemetery/src/lib/messages.ts b/libs/application/templates/inao/financial-statement-cemetery/src/lib/messages.ts index a78cd7e4296a..7bce9e4fffe1 100644 --- a/libs/application/templates/inao/financial-statement-cemetery/src/lib/messages.ts +++ b/libs/application/templates/inao/financial-statement-cemetery/src/lib/messages.ts @@ -375,7 +375,7 @@ export const m = defineMessages({ defaultMessage: 'Gagnaöflun', description: 'Title for data collection section', }, - dataCollectionTitleUserCemetry: { + dataCollectionTitleUserCemetery: { id: 'fsc.application:applicationDataCollectionTitleUserCemetry', defaultMessage: 'Gagnaöflun vegna skila ársreiknings kirkjugarðs', description: 'Title for data collection section', @@ -682,4 +682,10 @@ export const m = defineMessages({ 'Ef þú telur að þessi kennitala ætti að vera skráð sem kirkjugarður þá bendum við þér á að hafa samband við Ríkisendurskoðun í síma 448 8800', description: 'Descriptionwhen user is not allowed to apply', }, + conclusionAlertMessage: { + id: 'fsc.application:conclusionAlertMessage', + defaultMessage: + 'Ársreikning fyrir rekstrarárið {value1} hefur verið skilað', + description: 'Conclusion alert message', + }, }) diff --git a/libs/application/templates/inao/financial-statement-cemetery/src/types/types.ts b/libs/application/templates/inao/financial-statement-cemetery/src/types/types.ts index 274aeaf840e3..53e29233513f 100644 --- a/libs/application/templates/inao/financial-statement-cemetery/src/types/types.ts +++ b/libs/application/templates/inao/financial-statement-cemetery/src/types/types.ts @@ -36,3 +36,13 @@ export enum Roles { APPLICANT = 'applicant', NOTALLOWED = 'notAllowed', } + +type InaoConfigItem = { + __typename: string + key: string + value: string +} + +export type AuditConfig = { + financialStatementsInaoConfig: Array<InaoConfigItem> +} diff --git a/libs/application/templates/inao/financial-statement-cemetery/src/utils/constants.ts b/libs/application/templates/inao/financial-statement-cemetery/src/utils/constants.ts index a7981b212bbc..2c0cb3bb6afe 100644 --- a/libs/application/templates/inao/financial-statement-cemetery/src/utils/constants.ts +++ b/libs/application/templates/inao/financial-statement-cemetery/src/utils/constants.ts @@ -63,16 +63,20 @@ export const CEMETERYEQUITIESANDLIABILITIESIDS = { equityPrefix: 'cemeteryEquity', currentAssets: 'cemeteryAsset.currentAssets', fixedAssetsTotal: 'cemeteryAsset.fixedAssetsTotal', - assetTotal: 'cemeteryAsset.total', longTerm: 'cemeteryLiability.longTerm', shortTerm: 'cemeteryLiability.shortTerm', - liabilityTotal: 'cemeteryLiability.total', equityAtTheBeginningOfTheYear: 'cemeteryEquity.equityAtTheBeginningOfTheYear', revaluationDueToPriceChanges: 'cemeteryEquity.revaluationDueToPriceChanges', reevaluateOther: 'cemeteryEquity.reevaluateOther', operationResult: 'cemeteryEquity.operationResult', equityTotal: 'cemeteryEquity.total', - totalEquityAndLiabilities: 'equityAndLiabilities.total', +} + +export const EQUITYANDLIABILITIESTOTALS = { + assetsTotal: 'equityAndLiabilitiesTotals.assetsTotal', + liabilitiesTotal: 'equityAndLiabilitiesTotals.liabilitiesTotal', + equityAndLiabilitiesTotal: + 'equityAndLiabilitiesTotals.equityAndLiabilitiesTotal', } export const OPERATINGCOST = { diff --git a/libs/application/templates/inao/financial-statement-cemetery/src/utils/helpers.ts b/libs/application/templates/inao/financial-statement-cemetery/src/utils/helpers.ts index c7b382ede33f..aec943d790e8 100644 --- a/libs/application/templates/inao/financial-statement-cemetery/src/utils/helpers.ts +++ b/libs/application/templates/inao/financial-statement-cemetery/src/utils/helpers.ts @@ -1,10 +1,21 @@ import { ExternalData, FormValue } from '@island.is/application/types' import { getValueViaPath } from '@island.is/application/core' -import { BOARDMEMEBER, CARETAKER, TOTAL } from './constants' +import { + BOARDMEMEBER, + CAPITALNUMBERS, + CARETAKER, + CemeteriesBackwardLimit, + CemeteriesYearAllowed, + CEMETERYEQUITIESANDLIABILITIESIDS, + CEMETERYOPERATIONIDS, + EQUITYANDLIABILITIESTOTALS, + OPERATINGCOST, + TOTAL, +} from './constants' import { FinancialStatementCemetery } from '../lib/dataSchema' import getYear from 'date-fns/getYear' import subYears from 'date-fns/subYears' -import { BoardMember, Config, FSIUSERTYPE } from '../types/types' +import { AuditConfig, BoardMember, Config, FSIUSERTYPE } from '../types/types' export const getTotal = (values: Record<string, string>, key: string) => { if (!values[key]) { @@ -27,23 +38,6 @@ export const currencyStringToNumber = (str: string) => { return parseInt(cleanString, 10) } -export const getCurrentUserType = ( - answers: FormValue, - externalData: ExternalData, -) => { - const fakeUserType: FSIUSERTYPE | undefined = getValueViaPath( - answers, - 'fakeData.options', - ) - - const currentUserType: FSIUSERTYPE | undefined = getValueViaPath( - externalData, - 'getUserType.data.value', - ) - - return fakeUserType ? fakeUserType : currentUserType -} - export const getBoardmembersAndCaretakers = (members: Array<BoardMember>) => { const careTakers = members ?.filter((member) => member.role === CARETAKER) @@ -55,30 +49,44 @@ export const getBoardmembersAndCaretakers = (members: Array<BoardMember>) => { return { careTakers, boardMembers } } -export const isCemetryUnderFinancialLimit = ( - answers: FormValue, - externalData: ExternalData, -) => { - const userType = getCurrentUserType(answers, externalData) - const applicationAnswers = answers as FinancialStatementCemetery - const careTakerLimit = - applicationAnswers.cemeteryOperation?.incomeLimit ?? '0' - const fixedAssetsTotal = applicationAnswers.cemeteryAsset?.fixedAssetsTotal - const isCemetry = userType === FSIUSERTYPE.CEMETRY - const totalIncome = isCemetry ? applicationAnswers.cemeteryIncome?.total : '0' - const longTermDebt = applicationAnswers.cemeteryLiability?.longTerm - const isUnderLimit = currencyStringToNumber(totalIncome) < careTakerLimit - if ( - isCemetry && - isUnderLimit && - fixedAssetsTotal === '0' && - longTermDebt === '0' - ) { +export const isCemetryUnderFinancialLimit = (answers: FormValue) => { + const totalIncome = + getValueViaPath<string>(answers, 'cemeteryIncome.total') || '0' + const incomeLimit = + getValueViaPath<string>(answers, 'cemeteryOperation.incomeLimit') || '0' + const fixedAssetsTotal = + getValueViaPath<string>(answers, 'cemeteryAsset.fixedAssetsTotal') || '0' + const longTermDebt = + getValueViaPath<string>(answers, 'cemeteryLiability.longTerm') || '0' + const isUnderLimit = Number(totalIncome) < Number(incomeLimit) + + if (isUnderLimit && fixedAssetsTotal === '0' && longTermDebt === '0') { return true } + return false } +export const getYearOptions = (data: AuditConfig) => { + let yearLimit: string | undefined + let countYearBackwardsFrom: string | undefined + data.financialStatementsInaoConfig.forEach((item) => { + if (item.key === CemeteriesBackwardLimit) { + yearLimit = item.value + } + + if (item.key === CemeteriesYearAllowed) { + countYearBackwardsFrom = item.value + } + }) + + if (!countYearBackwardsFrom) { + return [] + } + + return possibleOperatingYears(yearLimit || '1', countYearBackwardsFrom) +} + export const possibleOperatingYears = ( yearLimit: string, countYearBackwardsFrom: string, @@ -103,3 +111,218 @@ export const formatCurrency = (answer?: string) => { if (!answer) return '0. kr' return answer.replace(/\B(?=(\d{3})+(?!\d))/g, '.') + ' kr.' } + +export const isPositiveNumberInString = (input: string) => { + return Number(input) > 0 +} + +export const sumIncome = (answers: FormValue) => { + const careIncome = + getValueViaPath<string>(answers, CEMETERYOPERATIONIDS.careIncome) || '0' + const burialRevenue = + getValueViaPath<string>(answers, CEMETERYOPERATIONIDS.burialRevenue) || '0' + const grantFromTheCemeteryFund = + getValueViaPath<string>( + answers, + CEMETERYOPERATIONIDS.grantFromTheCemeteryFund, + ) || '0' + const otherIncome = + getValueViaPath<string>(answers, CEMETERYOPERATIONIDS.otherIncome) || '0' + + return `${ + Number(careIncome) + + Number(burialRevenue) + + Number(grantFromTheCemeteryFund) + + Number(otherIncome) + }` +} + +export const sumExpenses = (answers: FormValue) => { + const payroll = getValueViaPath<string>(answers, CEMETERYOPERATIONIDS.payroll) + const funeralCost = + getValueViaPath<string>(answers, CEMETERYOPERATIONIDS.funeralCost) || '0' + const chapelExpense = + getValueViaPath<string>(answers, CEMETERYOPERATIONIDS.chapelExpense) || '0' + const donationsToCemeteryFund = + getValueViaPath<string>( + answers, + CEMETERYOPERATIONIDS.donationsToCemeteryFund, + ) || '0' + const donationsToOther = + getValueViaPath<string>(answers, CEMETERYOPERATIONIDS.donationsToOther) || + '0' + const otherOperationCost = + getValueViaPath<string>(answers, CEMETERYOPERATIONIDS.otherOperationCost) || + '0' + const depreciation = + getValueViaPath<string>(answers, CEMETERYOPERATIONIDS.depreciation) || '0' + + return `${ + Number(payroll) + + Number(funeralCost) + + Number(chapelExpense) + + Number(donationsToCemeteryFund) + + Number(donationsToOther) + + Number(otherOperationCost) + + Number(depreciation) + }` +} + +export const sumOperatingResults = (answers: FormValue) => { + const income = + getValueViaPath<string>(answers, CEMETERYOPERATIONIDS.totalIncome) || '0' + const expenses = + getValueViaPath<string>(answers, CEMETERYOPERATIONIDS.totalExpense) || '0' + + return `${Number(income) - Number(expenses)}` +} + +export const sumCapitalNumbers = (answers: FormValue) => { + const capitalIncome = + getValueViaPath<string>(answers, CAPITALNUMBERS.capitalIncome) || '0' + const capitalCost = + getValueViaPath<string>(answers, CAPITALNUMBERS.capitalCost) || '0' + return `${Number(capitalIncome) - Number(capitalCost)}` +} + +export const sumAssets = (answers: FormValue) => { + const fixedAssetsTotal = + getValueViaPath<string>( + answers, + CEMETERYEQUITIESANDLIABILITIESIDS.fixedAssetsTotal, + ) || '0' + const currentAssets = + getValueViaPath<string>( + answers, + CEMETERYEQUITIESANDLIABILITIESIDS.currentAssets, + ) || '0' + return `${Number(fixedAssetsTotal) + Number(currentAssets)}` +} + +export const sumLiabilities = (answers: FormValue) => { + const longTerm = + getValueViaPath<string>( + answers, + CEMETERYEQUITIESANDLIABILITIESIDS.longTerm, + ) || '0' + const shortTerm = + getValueViaPath<string>( + answers, + CEMETERYEQUITIESANDLIABILITIESIDS.shortTerm, + ) || '0' + return `${Number(longTerm) + Number(shortTerm)}` +} + +export const operationResult = (answers: FormValue) => { + const operatingTotalCost = + getValueViaPath<string>(answers, OPERATINGCOST.total) || '0' + const capitalTotal = + getValueViaPath<string>(answers, CAPITALNUMBERS.total) || '0' + return `${Number(operatingTotalCost) + Number(capitalTotal)}` +} + +export const sumTotalEquity = (answers: FormValue) => { + const equityAtTheBeginningOfTheYear = + getValueViaPath<string>( + answers, + CEMETERYEQUITIESANDLIABILITIESIDS.equityAtTheBeginningOfTheYear, + ) || '0' + const revaluationDueToPriceChanges = + getValueViaPath<string>( + answers, + CEMETERYEQUITIESANDLIABILITIESIDS.revaluationDueToPriceChanges, + ) || '0' + const reevaluateOther = + getValueViaPath<string>( + answers, + CEMETERYEQUITIESANDLIABILITIESIDS.reevaluateOther, + ) || '0' + const operationResult = + getValueViaPath<string>( + answers, + CEMETERYEQUITIESANDLIABILITIESIDS.operationResult, + ) || '0' + + return `${ + Number(equityAtTheBeginningOfTheYear) + + Number(revaluationDueToPriceChanges) + + Number(reevaluateOther) + + Number(operationResult) + }` +} + +export const sumTotalEquityAndLiabilities = (answers: FormValue) => { + const liabilityTotal = + getValueViaPath<string>( + answers, + EQUITYANDLIABILITIESTOTALS.liabilitiesTotal, + ) || '0' + const totalEquity = + getValueViaPath<string>( + answers, + CEMETERYEQUITIESANDLIABILITIESIDS.equityTotal, + ) || '0' + + return `${Number(totalEquity) + Number(liabilityTotal)}` +} + +export const showEquitiesAndLiabilitiesAlert = (answers: FormValue) => { + const fixedAssetsTotal = + getValueViaPath<string>( + answers, + CEMETERYEQUITIESANDLIABILITIESIDS.fixedAssetsTotal, + ) || '0' + const currentAssets = + getValueViaPath<string>( + answers, + CEMETERYEQUITIESANDLIABILITIESIDS.currentAssets, + ) || '0' + const longTerm = + getValueViaPath<string>( + answers, + CEMETERYEQUITIESANDLIABILITIESIDS.longTerm, + ) || '0' + const shortTerm = + getValueViaPath<string>( + answers, + CEMETERYEQUITIESANDLIABILITIESIDS.shortTerm, + ) || '0' + const equityAtTheBeginningOfTheYear = + getValueViaPath<string>( + answers, + CEMETERYEQUITIESANDLIABILITIESIDS.equityAtTheBeginningOfTheYear, + ) || '0' + const revaluationDueToPriceChanges = + getValueViaPath<string>( + answers, + CEMETERYEQUITIESANDLIABILITIESIDS.revaluationDueToPriceChanges, + ) || '0' + const reevaluateOther = + getValueViaPath<string>( + answers, + CEMETERYEQUITIESANDLIABILITIESIDS.reevaluateOther, + ) || '0' + + const totalAssets = + getValueViaPath<string>(answers, EQUITYANDLIABILITIESTOTALS.assetsTotal) || + '0' + const totalEquityAndLiabilities = + getValueViaPath<string>( + answers, + EQUITYANDLIABILITIESTOTALS.equityAndLiabilitiesTotal, + ) || '0' + + if ( + !fixedAssetsTotal || + !currentAssets || + !longTerm || + !shortTerm || + !equityAtTheBeginningOfTheYear || + !revaluationDueToPriceChanges || + !reevaluateOther || + !operationResult + ) { + return false + } + return totalAssets !== totalEquityAndLiabilities +} diff --git a/libs/application/templates/inao/financial-statement-cemetery/tsconfig.json b/libs/application/templates/inao/financial-statement-cemetery/tsconfig.json index 52f7c83105fc..04538292f729 100644 --- a/libs/application/templates/inao/financial-statement-cemetery/tsconfig.json +++ b/libs/application/templates/inao/financial-statement-cemetery/tsconfig.json @@ -1,8 +1,10 @@ { - "extends": "../../../../tsconfig.base.json", "compilerOptions": { "jsx": "react-jsx", - "allowJs": false + "allowJs": false, + "esModuleInterop": false, + "allowSyntheticDefaultImports": true, + "strict": true }, "files": [], "include": [], @@ -13,5 +15,6 @@ { "path": "./tsconfig.spec.json" } - ] + ], + "extends": "../../../../../tsconfig.base.json" } diff --git a/libs/application/templates/inao/financial-statement-cemetery/tsconfig.lib.json b/libs/application/templates/inao/financial-statement-cemetery/tsconfig.lib.json index 4362bec84a9a..d17804e8805a 100644 --- a/libs/application/templates/inao/financial-statement-cemetery/tsconfig.lib.json +++ b/libs/application/templates/inao/financial-statement-cemetery/tsconfig.lib.json @@ -1,23 +1,13 @@ { "extends": "./tsconfig.json", "compilerOptions": { - "outDir": "../../../../dist/out-tsc", + "outDir": "../../../../../dist/out-tsc", "types": ["node"] }, "files": [ "../../../../../node_modules/@nx/react/typings/cssmodule.d.ts", "../../../../../node_modules/@nx/react/typings/image.d.ts" ], - "exclude": [ - "jest.config.ts", - "src/**/*.spec.ts", - "src/**/*.test.ts", - "src/**/*.spec.tsx", - "src/**/*.test.tsx", - "src/**/*.spec.js", - "src/**/*.test.js", - "src/**/*.spec.jsx", - "src/**/*.test.jsx" - ], + "exclude": ["/**/*.spec.ts", "/**/*.spec.tsx"], "include": ["src/**/*.js", "src/**/*.jsx", "src/**/*.ts", "src/**/*.tsx"] } diff --git a/libs/application/templates/inao/financial-statement-individual-election/src/dataProviders/index.ts b/libs/application/templates/inao/financial-statement-individual-election/src/dataProviders/index.ts index 36303255e230..d537ed3dfa93 100644 --- a/libs/application/templates/inao/financial-statement-individual-election/src/dataProviders/index.ts +++ b/libs/application/templates/inao/financial-statement-individual-election/src/dataProviders/index.ts @@ -3,7 +3,7 @@ import { UserProfileApi } from '@island.is/application/types' export { NationalRegistryUserApi, - IdentityApi as IndentityApiProvider, + IdentityApi as IdentityApiProvider, } from '@island.is/application/types' export const CurrentUserTypeProvider = defineTemplateApi({ action: 'getUserType', diff --git a/libs/application/templates/inao/financial-statement-individual-election/src/forms/prerequisites/prerequisitesSection.ts b/libs/application/templates/inao/financial-statement-individual-election/src/forms/prerequisites/prerequisitesSection.ts index e9820806c3d6..eb7e898460aa 100644 --- a/libs/application/templates/inao/financial-statement-individual-election/src/forms/prerequisites/prerequisitesSection.ts +++ b/libs/application/templates/inao/financial-statement-individual-election/src/forms/prerequisites/prerequisitesSection.ts @@ -8,7 +8,7 @@ import { import { m } from '../../lib/utils/messages' import { CurrentUserTypeProvider, - IndentityApiProvider, + IdentityApiProvider, UserInfoApi, } from '../../dataProviders' import { DefaultEvents } from '@island.is/application/types' @@ -23,7 +23,7 @@ export const prerequisitesSection = buildSection({ checkboxLabel: m.dataCollectionCheckboxLabel, dataProviders: [ buildDataProviderItem({ - provider: IndentityApiProvider, + provider: IdentityApiProvider, title: m.dataCollectionNationalRegistryTitle, subTitle: m.dataCollectionNationalRegistrySubtitle, }), diff --git a/libs/application/templates/inao/financial-statement-individual-election/src/lib/financial-statement-individual-election.ts b/libs/application/templates/inao/financial-statement-individual-election/src/lib/financial-statement-individual-election.ts index 83e1e625a391..db241a94b205 100644 --- a/libs/application/templates/inao/financial-statement-individual-election/src/lib/financial-statement-individual-election.ts +++ b/libs/application/templates/inao/financial-statement-individual-election/src/lib/financial-statement-individual-election.ts @@ -17,7 +17,7 @@ import { import { CurrentUserTypeProvider, - IndentityApiProvider, + IdentityApiProvider, NationalRegistryUserApi, UserInfoApi, } from '../dataProviders' @@ -77,7 +77,7 @@ const FinancialStatementIndividualElectionTemplate: ApplicationTemplate< delete: true, api: [ CurrentUserTypeProvider, - IndentityApiProvider, + IdentityApiProvider, NationalRegistryUserApi, UserInfoApi, ], diff --git a/libs/application/templates/inao/financial-statement-political-party/README.md b/libs/application/templates/inao/financial-statement-political-party/README.md index 3961bb865298..e962ad62bf8e 100644 --- a/libs/application/templates/inao/financial-statement-political-party/README.md +++ b/libs/application/templates/inao/financial-statement-political-party/README.md @@ -1,6 +1,8 @@ # application-templates-inao-financial-statement-political-party -This library was generated with [Nx](https://nx.dev). +This application turns in financial statements for political parties to the The Icelandic National Audit Office (Ríkisendurskoðun, INAO for short). Political parties are required to turn in their financial statements before the 31. of October each year. + +To test this application in the dev environment, you can log in as Gervimaður Færeyjar (010-2399) and swap to the procure of 65° Arctic. Arctic is registered as a political party with the INAO dev service and should be able to enter and go through the application. ## Running unit tests diff --git a/libs/application/templates/inao/financial-statement-political-party/src/dataProviders/index.ts b/libs/application/templates/inao/financial-statement-political-party/src/dataProviders/index.ts index 36303255e230..d537ed3dfa93 100644 --- a/libs/application/templates/inao/financial-statement-political-party/src/dataProviders/index.ts +++ b/libs/application/templates/inao/financial-statement-political-party/src/dataProviders/index.ts @@ -3,7 +3,7 @@ import { UserProfileApi } from '@island.is/application/types' export { NationalRegistryUserApi, - IdentityApi as IndentityApiProvider, + IdentityApi as IdentityApiProvider, } from '@island.is/application/types' export const CurrentUserTypeProvider = defineTemplateApi({ action: 'getUserType', diff --git a/libs/application/templates/inao/financial-statement-political-party/src/forms/prerequsites/prerequsitesSection.ts b/libs/application/templates/inao/financial-statement-political-party/src/forms/prerequsites/prerequsitesSection.ts index 40dca0ccb508..c10d83771cc3 100644 --- a/libs/application/templates/inao/financial-statement-political-party/src/forms/prerequsites/prerequsitesSection.ts +++ b/libs/application/templates/inao/financial-statement-political-party/src/forms/prerequsites/prerequsitesSection.ts @@ -8,7 +8,7 @@ import { import { m } from '../../lib/messages' import { CurrentUserTypeProvider, - IndentityApiProvider, + IdentityApiProvider, UserInfoApi, } from '../../dataProviders' import { DefaultEvents } from '@island.is/application/types' @@ -23,7 +23,7 @@ export const prerequisitesSection = buildSection({ checkboxLabel: m.dataCollectionCheckboxLabel, dataProviders: [ buildDataProviderItem({ - provider: IndentityApiProvider, + provider: IdentityApiProvider, title: m.dataCollectionNationalRegistryTitle, subTitle: m.dataCollectionNationalRegistrySubtitle, }), diff --git a/libs/application/templates/inao/financial-statement-political-party/src/lib/financialStatementPoliticalPartyTemplate.ts b/libs/application/templates/inao/financial-statement-political-party/src/lib/financialStatementPoliticalPartyTemplate.ts index 627860da4841..3025138a3546 100644 --- a/libs/application/templates/inao/financial-statement-political-party/src/lib/financialStatementPoliticalPartyTemplate.ts +++ b/libs/application/templates/inao/financial-statement-political-party/src/lib/financialStatementPoliticalPartyTemplate.ts @@ -19,7 +19,7 @@ import { } from '@island.is/application/core' import { CurrentUserTypeProvider, - IndentityApiProvider, + IdentityApiProvider, NationalRegistryUserApi, UserInfoApi, } from '../dataProviders' @@ -81,7 +81,7 @@ const FinancialStatementPoliticalPartyTemplate: ApplicationTemplate< delete: true, api: [ CurrentUserTypeProvider, - IndentityApiProvider, + IdentityApiProvider, NationalRegistryUserApi, UserInfoApi, ], @@ -122,7 +122,7 @@ const FinancialStatementPoliticalPartyTemplate: ApplicationTemplate< delete: true, api: [ CurrentUserTypeProvider, - IndentityApiProvider, + IdentityApiProvider, NationalRegistryUserApi, UserInfoApi, ], diff --git a/libs/application/templates/inheritance-report/src/fields/CalculateShare/index.tsx b/libs/application/templates/inheritance-report/src/fields/CalculateShare/index.tsx index 3a2f0e735eff..c5737912c549 100644 --- a/libs/application/templates/inheritance-report/src/fields/CalculateShare/index.tsx +++ b/libs/application/templates/inheritance-report/src/fields/CalculateShare/index.tsx @@ -166,9 +166,9 @@ export const CalculateShare: FC<React.PropsWithChildren<FieldBaseProps>> = ({ } }) - const inventory: CalcShared = ( - [(answers.assets as unknown as EstateAssets)?.inventory] ?? [] - ).map((item) => { + const inventory: CalcShared = [ + (answers.assets as unknown as EstateAssets)?.inventory ?? [], + ].map((item) => { const value = valueToNumber(item.value) const deceasedShare = valueToNumber(item.deceasedShare) const { shareValue, deceasedShareValue } = getShareValue( @@ -185,9 +185,9 @@ export const CalculateShare: FC<React.PropsWithChildren<FieldBaseProps>> = ({ } }) - const money: CalcShared = ( - [(answers.assets as unknown as EstateAssets)?.money] ?? [] - ).map((item) => { + const money: CalcShared = [ + (answers.assets as unknown as EstateAssets)?.money ?? [], + ].map((item) => { const value = valueToNumber(item.value) const deceasedShare = valueToNumber(item.deceasedShare) const { shareValue, deceasedShareValue } = getShareValue( diff --git a/libs/application/templates/inheritance-report/src/fields/ReportFieldsRepeater/index.tsx b/libs/application/templates/inheritance-report/src/fields/ReportFieldsRepeater/index.tsx index 7529b650832a..08731a1c43ec 100644 --- a/libs/application/templates/inheritance-report/src/fields/ReportFieldsRepeater/index.tsx +++ b/libs/application/templates/inheritance-report/src/fields/ReportFieldsRepeater/index.tsx @@ -85,7 +85,9 @@ export const ReportFieldsRepeater: FC< } const total = values.reduce((acc: number, current: any, index: number) => { - const sumField2 = valueToNumber(current[props?.sumField2], ',') + const sumField2 = current.enabled + ? valueToNumber(current[props?.sumField2], ',') + : 0 let currentValue = valueToNumber( current.enabled ? current[props?.sumField] : 0, ',', diff --git a/libs/application/templates/inheritance-report/src/forms/sections/deceased.ts b/libs/application/templates/inheritance-report/src/forms/sections/deceased.ts index 501dfc9f920c..8cac5ea189ea 100644 --- a/libs/application/templates/inheritance-report/src/forms/sections/deceased.ts +++ b/libs/application/templates/inheritance-report/src/forms/sections/deceased.ts @@ -118,7 +118,7 @@ export const deceased = buildSection({ { name: 'customShare.customSpouseSharePercentage', placeholder: '50%', - label: m.deceasedShare.defaultMessage, + label: m.deceasedSharePercentage.defaultMessage, }, ), buildDescriptionField({ diff --git a/libs/application/templates/inheritance-report/src/forms/sections/prepaidInheritance/heirs.ts b/libs/application/templates/inheritance-report/src/forms/sections/prepaidInheritance/heirs.ts index 8403cdfb24e6..fc80ed76ab7a 100644 --- a/libs/application/templates/inheritance-report/src/forms/sections/prepaidInheritance/heirs.ts +++ b/libs/application/templates/inheritance-report/src/forms/sections/prepaidInheritance/heirs.ts @@ -84,7 +84,7 @@ export const prePaidHeirs = buildSection({ buildMultiField({ id: 'prePaidHeirsAdditionalInfo', title: m.heirAdditionalInfo, - description: m.heirAdditionalInfoDescription, + description: m.heirAdditionalInfoPrePaidDescription, children: [ buildTextField({ id: 'heirsAdditionalInfo', diff --git a/libs/application/templates/inheritance-report/src/lib/dataSchema.ts b/libs/application/templates/inheritance-report/src/lib/dataSchema.ts index 70b60e815a72..3e437681836f 100644 --- a/libs/application/templates/inheritance-report/src/lib/dataSchema.ts +++ b/libs/application/templates/inheritance-report/src/lib/dataSchema.ts @@ -498,6 +498,7 @@ export const inheritanceReportSchema = z.object({ rent: z.string().optional(), food: z.string().optional(), tombstone: z.string().optional(), + service: z.string().optional(), hasOther: z.array(z.enum([YES])).optional(), other: z.string().optional(), otherDetails: z.string().optional(), diff --git a/libs/application/templates/inheritance-report/src/lib/messages.ts b/libs/application/templates/inheritance-report/src/lib/messages.ts index 39003d0532a1..84f67d71a06a 100644 --- a/libs/application/templates/inheritance-report/src/lib/messages.ts +++ b/libs/application/templates/inheritance-report/src/lib/messages.ts @@ -1199,7 +1199,7 @@ export const m = defineMessages({ // Assets to share assetsToShareDescription: { - id: 'ir.application:assetsToShareDescription', + id: 'ir.application:assetsToShareDescription#markdown', defaultMessage: 'Frá dregst búshluti eftirlifandi maka samkvæmt reglum hjúskaparlaga nr. 31/1993.', description: '', @@ -1256,9 +1256,14 @@ export const m = defineMessages({ defaultMessage: 'Séreign', description: '', }, + deceasedSharePercentage: { + id: 'ir.application:deceasedSharePercentage', + defaultMessage: 'Búshluti maka', + description: '', + }, deceasedShare: { id: 'ir.application:deceasedShare', - defaultMessage: 'Búshluti maka', + defaultMessage: 'Hlutfall séreignar', description: '', }, spousesShareDescription: { @@ -1382,7 +1387,7 @@ export const m = defineMessages({ description: '', }, heirsAndPartitionDescription: { - id: 'ir.application:heirsAndPartitionDescription', + id: 'ir.application:heirsAndPartitionDescription#markdown', defaultMessage: 'Skrá skal netfang erfingja vegna tilkynninga skattstjóra skv. 9. og 10. gr. laga nr. 14/2004.', description: '', @@ -1664,6 +1669,12 @@ export const m = defineMessages({ 'Skýringar og athugasemdir erfingja og/eða þess sem skilar inn erfðafjárskýrslu.', description: '', }, + heirAdditionalInfoPrePaidDescription: { + id: 'ir.application:heirAdditionalInfoPrePaidDescription', + defaultMessage: + 'Skýringar og athugasemdir erfingja og/eða þess sem skilar inn erfðafjárskýrslu.', + description: '', + }, info: { id: 'ir.application:info', defaultMessage: 'Athugasemdir', diff --git a/libs/application/ui-shell/src/components/DelegationsScreen.tsx b/libs/application/ui-shell/src/components/DelegationsScreen.tsx index 440e528d1ccf..4dbaa0335037 100644 --- a/libs/application/ui-shell/src/components/DelegationsScreen.tsx +++ b/libs/application/ui-shell/src/components/DelegationsScreen.tsx @@ -16,7 +16,7 @@ import { Text, } from '@island.is/island-ui/core' import { useLocale } from '@island.is/localization' -import { useBff } from '@island.is/react-spa/bff' +import { useAuth } from '@island.is/react-spa/bff' import { useFeatureFlagClient } from '@island.is/react/feature-flags' import * as kennitala from 'kennitala' import { format as formatKennitala } from 'kennitala' @@ -50,7 +50,7 @@ export const DelegationsScreen = ({ }) const { formatMessage } = useLocale() const type = getTypeFromSlug(slug) - const { switchUser, userInfo: user } = useBff() + const { switchUser, userInfo: user } = useAuth() const featureFlagClient: FeatureFlagClient = useFeatureFlagClient() const navigate = useNavigate() diff --git a/libs/auth/react/.babelrc b/libs/auth/react/.babelrc deleted file mode 100644 index e05c199e361e..000000000000 --- a/libs/auth/react/.babelrc +++ /dev/null @@ -1,4 +0,0 @@ -{ - "presets": ["@nx/react/babel"], - "plugins": [] -} diff --git a/libs/auth/react/.eslintrc.json b/libs/auth/react/.eslintrc.json deleted file mode 100644 index 4f027ee445be..000000000000 --- a/libs/auth/react/.eslintrc.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": ["plugin:@nx/react", "../../../.eslintrc.json"], - "ignorePatterns": ["!**/*"], - "rules": {}, - "overrides": [ - { "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], "rules": {} }, - { "files": ["*.ts", "*.tsx"], "rules": {} }, - { "files": ["*.js", "*.jsx"], "rules": {} } - ] -} diff --git a/libs/auth/react/README.md b/libs/auth/react/README.md deleted file mode 100644 index 039c3d156ae7..000000000000 --- a/libs/auth/react/README.md +++ /dev/null @@ -1,106 +0,0 @@ -# @island.is/auth/react - -Manage authentication in React (non-next) single page applications. - -- Handles oidc-client and callback routes. -- Handles authentication flow with loading screen. -- Manages user context. -- Renews access tokens on demand (when calling APIs) instead of continuously. This helps us support an (eg) 1-8 hour IDS session, depending on how long the user is active. -- Preloads a new access token some time before it expires (when calling APIs). -- Monitor the IDS session and restart login flow if the user is not logged in anymore. - -## Usage - -### Configure - -In the startup of your app (e.g. `Main.tsx`) you need to configure some authentication parameters: - -```typescript -import { configure } from '@island.is/auth/react' -import { environment } from './environments' - -configure({ - // You should usually configure these: - authority: environment.identityServer.authority, - client_id: '@island.is/web', - scope: [ - 'openid', - 'profile', - 'api_resource.scope', - '@island.is/applications:read', - ], - // These can be overridden to control callback urls. - // These are the default values: - baseUrl: `${window.location.origin}`, - redirectPath: '/auth/callback', - redirectPathSilent: '/auth/callback-silent', -}) -``` - -### Authenticate - -The configure function also accepts all oidc-client UserManager settings. - -Then you can render the Authenticator component around your application to wrap it with user authentication. - -```typescript jsx -ReactDOM.render( - <Router> - <AuthProvider basePath="/some_base_path"> - <App /> - </AuthProvider> - </Router>, - document.getElementById('root'), -) -``` - -By default, it only renders its children after signing the user in. It will render a loading screen in the meantime. - -{% hint style="info" %} -Note: Authenticator must be rendered inside React Router to set up callback routes. -{% endhint %} - -### Get access token - -You can configure authentication for your GraphQL client like this: - -```typescript -import { - ApolloClient, - InMemoryCache, - HttpLink, - ApolloLink, -} from '@apollo/client' -import { authLink } from '@island.is/auth/react' - -const httpLink = new HttpLink(/* snip */) - -export const client = new ApolloClient({ - link: ApolloLink.from([authLink, httpLink]), - cache: new InMemoryCache(), -}) -``` - -You can also manually get the access token like this: - -```typescript -import { getAccessToken } from '@island.is/auth/react' - -const accessToken = await getAccessToken() -``` - -### Token renew and IDS session - -When you call `getAccessToken` or make requests with `authLink`, we renew the access token on demand if it has expired. We also preload a new access token if you are actively requesting the access token before it expires. - -Note that if the user has been inactive, they might experience a delay when they come back and call an API, while we renew the access token. - -Every time we renew the access token, the IDS session is extended. When this is written, the IDS maintains a 1 hour session that can be extended up to 8 hours. - -Be careful not to do continuous API requests on an interval when the user might not be active. - -Later we may implement an "updateActive" function that can be called to extend the IDS session in case the user is active but not calling any APIs. - -## Running unit tests - -Run `nx test auth-react` to execute the unit tests via [Jest](https://jestjs.io). diff --git a/libs/auth/react/babel-jest.config.json b/libs/auth/react/babel-jest.config.json deleted file mode 100644 index f83bce0d90ea..000000000000 --- a/libs/auth/react/babel-jest.config.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "presets": [ - [ - "@babel/preset-env", - { - "targets": { - "node": "current" - } - } - ], - "@babel/preset-typescript", - "@babel/preset-react" - ], - "plugins": ["@vanilla-extract/babel-plugin"] -} diff --git a/libs/auth/react/jest.config.ts b/libs/auth/react/jest.config.ts deleted file mode 100644 index 336f7f818324..000000000000 --- a/libs/auth/react/jest.config.ts +++ /dev/null @@ -1,16 +0,0 @@ -/* eslint-disable */ -export default { - displayName: 'auth-react', - preset: './jest.preset.js', - rootDir: '../../..', - roots: [__dirname], - setupFilesAfterEnv: [`${__dirname}/test/setup.ts`], - transform: { - '^.+\\.[tj]sx?$': [ - 'babel-jest', - { cwd: __dirname, configFile: `${__dirname}/babel-jest.config.json` }, - ], - }, - moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], - coverageDirectory: '<rootDir>/coverage/libs/auth/react', -} diff --git a/libs/auth/react/project.json b/libs/auth/react/project.json deleted file mode 100644 index 51a8e03a6a39..000000000000 --- a/libs/auth/react/project.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "auth-react", - "$schema": "../../../node_modules/nx/schemas/project-schema.json", - "sourceRoot": "libs/auth/react/src", - "projectType": "library", - "tags": ["lib:react-spa", "scope:react-spa"], - "targets": { - "lint": { - "executor": "@nx/eslint:lint" - }, - "test": { - "executor": "@nx/jest:jest", - "outputs": ["{workspaceRoot}/coverage/libs/auth/react"], - "options": { - "jestConfig": "libs/auth/react/jest.config.ts" - } - } - } -} diff --git a/libs/auth/react/src/index.ts b/libs/auth/react/src/index.ts deleted file mode 100644 index 99c01c660442..000000000000 --- a/libs/auth/react/src/index.ts +++ /dev/null @@ -1,14 +0,0 @@ -// Components -export * from './lib/auth/AuthProvider' -export * from './lib/auth/MockedAuthProvider' -export * from './lib/auth/AuthContext' - -// Lib -export * from './lib/userManager' -export * from './lib/authLink' -export { getAccessToken } from './lib/getAccessToken' - -// Types -export type { MockUser } from './lib/createMockUser' -export type { AuthSettings } from './lib/AuthSettings' -export * from './lib/createMockUser' diff --git a/libs/auth/react/src/lib/AuthSettings.spec.ts b/libs/auth/react/src/lib/AuthSettings.spec.ts deleted file mode 100644 index a2edf3423518..000000000000 --- a/libs/auth/react/src/lib/AuthSettings.spec.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { mergeAuthSettings } from './AuthSettings' - -describe('mergeAuthSettings', () => { - it('provides good defaults', () => { - // act - const settings = mergeAuthSettings({ - client_id: 'test-client', - authority: 'https://innskra.island.is', - }) - - // assert - expect(settings).toMatchInlineSnapshot(` - Object { - "authority": "https://innskra.island.is", - "automaticSilentRenew": false, - "baseUrl": "http://localhost", - "checkSessionPath": "/connect/sessioninfo", - "client_id": "test-client", - "loadUserInfo": true, - "mergeClaims": true, - "monitorSession": false, - "post_logout_redirect_uri": "http://localhost", - "redirectPath": "/auth/callback", - "redirectPathSilent": "/auth/callback-silent", - "response_type": "code", - "revokeTokenTypes": Array [ - "refresh_token", - ], - "revokeTokensOnSignout": true, - "silent_redirect_uri": "http://localhost/auth/callback-silent", - "userStore": WebStorageStateStore { - "_logger": Logger { - "_name": "WebStorageStateStore", - }, - "_prefix": "oidc.", - "_store": Storage {}, - }, - } - `) - }) - - it('creates uris from baseUrl and redirect paths', () => { - // act - const settings = mergeAuthSettings({ - authority: 'https://innskra.island.is', - client_id: 'test-client', - baseUrl: 'https://island.is', - redirectPath: '/auth', - redirectPathSilent: '/auth-silent', - }) - - // assert - expect(settings).toMatchObject({ - baseUrl: 'https://island.is', - post_logout_redirect_uri: 'https://island.is', - redirectPath: '/auth', - redirectPathSilent: '/auth-silent', - silent_redirect_uri: 'https://island.is/auth-silent', - }) - }) -}) diff --git a/libs/auth/react/src/lib/AuthSettings.ts b/libs/auth/react/src/lib/AuthSettings.ts deleted file mode 100644 index c35c7ee8c65a..000000000000 --- a/libs/auth/react/src/lib/AuthSettings.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { storageFactory } from '@island.is/shared/utils' -import { UserManagerSettings, WebStorageStateStore } from 'oidc-client-ts' - -export interface AuthSettings - extends Omit<UserManagerSettings, 'scope' | 'redirect_uri'> { - /* - * Used to create redirect uris. Should not end with slash. - * Default: window.location.origin - */ - baseUrl?: string - - /* - * Used to handle login callback and to build a default value for `redirect_uri` with baseUrl. Should be - * relative from baseUrl and start with a "/". - * Default: "/auth/callback" - */ - redirectPath?: string - - /** - * Used to handle login callback and to build a default value for `silent_redirect_uri` with baseUrl. - * Should be relative from baseUrl and start with a "/". - * Default: "/auth/callback-silent" - */ - redirectPathSilent?: string - - /** - * Used to support login flow triggered by the authorisation server or another party. Should be relative from baseUrl - * and start with a "/". - * More information: https://openid.net/specs/openid-connect-standard-1_0-21.html#client_Initiate_login - * Default: undefined - */ - initiateLoginPath?: string - - /** - * Prefix for storing user access tokens in session storage. - */ - userStorePrefix?: string - - /** - * Allow to pass the scope as an array. - */ - scope?: string[] - - /** - * Which URL to send the user to after switching users. - */ - switchUserRedirectUrl?: string - - /** - * Which PATH on the AUTHORITY to use for checking the session expiry. - */ - checkSessionPath?: string -} - -export const mergeAuthSettings = (settings: AuthSettings): AuthSettings => { - const baseUrl = settings.baseUrl ?? window.location.origin - const redirectPath = settings.redirectPath ?? '/auth/callback' - const redirectPathSilent = - settings.redirectPathSilent ?? '/auth/callback-silent' - - // Many Open ID Connect features only work when on the same domain as the IDS (with first party cookies) - const onIdsDomain = /(is|dev)land.is$/.test(window.location.origin) - - return { - baseUrl, - redirectPath, - redirectPathSilent, - automaticSilentRenew: false, - checkSessionPath: '/connect/sessioninfo', - silent_redirect_uri: `${baseUrl}${redirectPathSilent}`, - post_logout_redirect_uri: baseUrl, - response_type: 'code', - revokeTokenTypes: ['refresh_token'], - revokeTokensOnSignout: true, - loadUserInfo: true, - monitorSession: onIdsDomain, - userStore: new WebStorageStateStore({ - store: storageFactory(() => sessionStorage), - prefix: settings.userStorePrefix, - }), - mergeClaims: true, - ...settings, - } -} diff --git a/libs/auth/react/src/lib/auth/Auth.css.ts b/libs/auth/react/src/lib/auth/Auth.css.ts deleted file mode 100644 index 62d600b0be1b..000000000000 --- a/libs/auth/react/src/lib/auth/Auth.css.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { style } from '@vanilla-extract/css' - -export const fullScreen = style({ - height: '100vh', -}) diff --git a/libs/auth/react/src/lib/auth/Auth.state.ts b/libs/auth/react/src/lib/auth/Auth.state.ts deleted file mode 100644 index 74a1b1f9b785..000000000000 --- a/libs/auth/react/src/lib/auth/Auth.state.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { User } from '@island.is/shared/types' - -export type AuthState = - | 'logged-out' - | 'loading' - | 'logged-in' - | 'failed' - | 'switching' - | 'logging-out' - -export interface AuthReducerState { - userInfo: User | null - authState: AuthState - isAuthenticated: boolean -} - -export enum ActionType { - SIGNIN_START = 'SIGNIN_START', - SIGNIN_SUCCESS = 'SIGNIN_SUCCESS', - SIGNIN_FAILURE = 'SIGNIN_FAILURE', - LOGGING_OUT = 'LOGGING_OUT', - LOGGED_OUT = 'LOGGED_OUT', - USER_LOADED = 'USER_LOADED', - SWITCH_USER = 'SWITCH_USER', -} - -export interface Action { - type: ActionType - // eslint-disable-next-line @typescript-eslint/no-explicit-any - payload?: any -} - -export const initialState: AuthReducerState = { - userInfo: null, - authState: 'logged-out', - isAuthenticated: false, -} - -export const reducer = ( - state: AuthReducerState, - action: Action, -): AuthReducerState => { - switch (action.type) { - case ActionType.SIGNIN_START: - return { - ...state, - authState: 'loading', - } - case ActionType.SIGNIN_SUCCESS: - return { - ...state, - userInfo: action.payload, - - authState: 'logged-in', - isAuthenticated: true, - } - case ActionType.USER_LOADED: - return state.isAuthenticated - ? { - ...state, - userInfo: action.payload, - } - : state - case ActionType.SIGNIN_FAILURE: - return { - ...state, - authState: 'failed', - } - case ActionType.LOGGING_OUT: - return { - ...state, - authState: 'logging-out', - } - case ActionType.SWITCH_USER: - return { - ...state, - authState: 'switching', - } - case ActionType.LOGGED_OUT: - return { - ...initialState, - } - default: - return state - } -} diff --git a/libs/auth/react/src/lib/auth/AuthContext.tsx b/libs/auth/react/src/lib/auth/AuthContext.tsx deleted file mode 100644 index e6d65ea5d94a..000000000000 --- a/libs/auth/react/src/lib/auth/AuthContext.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import { createContext, useContext } from 'react' - -import { AuthReducerState, initialState } from './Auth.state' - -export interface AuthContextType extends AuthReducerState { - signIn(): void - signInSilent(): void - switchUser(nationalId?: string): void - signOut(): void - authority?: string -} - -export const defaultAuthContext = { - ...initialState, - signIn() { - // Intentionally empty - }, - signInSilent() { - // Intentionally empty - }, - switchUser(_nationalId?: string) { - // Intentionally empty - }, - signOut() { - // Intentionally empty - }, -} - -export const AuthContext = createContext<AuthContextType>(defaultAuthContext) - -const warnDeprecated = (hookName: string, alternative: string) => { - console.warn( - `[Deprecation Warning] "${hookName}" is being replaced by BFF auth pattern Please use "${alternative}" from "libs/react-spa/bff".`, - ) -} - -/** - * @deprecated Use useBff from `libs/react-spa/bff` instead. - */ -export const useAuth = () => { - warnDeprecated('useAuth', 'useBff') - - const context = useContext(AuthContext) - - if (!context) { - throw new Error('useAuth must be used within a AuthProvider') - } - - return context -} - -/** - * @deprecated Use useUserInfo from `libs/react-spa/bff` instead. - */ -export const useUserInfo = () => { - warnDeprecated('useUserInfo', 'useUserInfo') - const { userInfo } = useAuth() - - if (!userInfo) { - throw new Error('User info is not available. Is the user authenticated?') - } - - return userInfo -} diff --git a/libs/auth/react/src/lib/auth/AuthErrorScreen.tsx b/libs/auth/react/src/lib/auth/AuthErrorScreen.tsx deleted file mode 100644 index 27bcfe5aeb03..000000000000 --- a/libs/auth/react/src/lib/auth/AuthErrorScreen.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import React from 'react' -import { Box, Button, ProblemTemplate } from '@island.is/island-ui/core' - -import * as styles from './Auth.css' - -type AuthenticatorErrorScreenProps = { - /** - * Retry callback - */ - onRetry(): void -} - -// This screen is unfortunately not translated because at this point we don't -// have a user locale, nor an access token to fetch translations. -export const AuthErrorScreen = ({ onRetry }: AuthenticatorErrorScreenProps) => ( - <Box - display="flex" - justifyContent="center" - alignItems="center" - padding={[0, 6]} - className={styles.fullScreen} - > - <ProblemTemplate - variant="error" - expand - tag="Villa" - title="Innskráning mistókst" - message={ - <> - Vinsamlegast reyndu aftur síðar.{' '} - <Button variant="text" onClick={onRetry}> - Reyna aftur - </Button> - </> - } - /> - </Box> -) diff --git a/libs/auth/react/src/lib/auth/AuthProvider.mocked.spec.tsx b/libs/auth/react/src/lib/auth/AuthProvider.mocked.spec.tsx deleted file mode 100644 index 3cc638999238..000000000000 --- a/libs/auth/react/src/lib/auth/AuthProvider.mocked.spec.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import { FC } from 'react' -import { render, screen } from '@testing-library/react' -import { MemoryRouter } from 'react-router-dom' - -import { configureMock } from '../userManager' -import { useAuth } from './AuthContext' -import { AuthProvider } from './AuthProvider' - -const Wrapper: FC<React.PropsWithChildren<unknown>> = ({ children }) => ( - <MemoryRouter>{children}</MemoryRouter> -) -const Greeting = () => { - const { userInfo } = useAuth() - return <>Hello {userInfo?.profile.name}</> -} -const renderAuthenticator = () => - render( - <AuthProvider basePath="/basepath"> - <h2> - <Greeting /> - </h2> - </AuthProvider>, - { wrapper: Wrapper }, - ) - -describe('AuthProvider', () => { - const expectAuthenticated = (name: string) => - screen.findByText(`Hello ${name}`) - - it('authenticates with non-expired user', async () => { - // Arrange - configureMock({ - profile: { - name: 'John Doe', - }, - }) - - // Act - renderAuthenticator() - - // Assert - await expectAuthenticated('John Doe') - }) -}) diff --git a/libs/auth/react/src/lib/auth/AuthProvider.spec.tsx b/libs/auth/react/src/lib/auth/AuthProvider.spec.tsx deleted file mode 100644 index ab1d4a03049f..000000000000 --- a/libs/auth/react/src/lib/auth/AuthProvider.spec.tsx +++ /dev/null @@ -1,241 +0,0 @@ -import { act, render, screen, waitFor } from '@testing-library/react' -import { UserManagerEvents } from 'oidc-client-ts' -import { BrowserRouter } from 'react-router-dom' - -import { getAuthSettings, getUserManager } from '../userManager' -import { useAuth } from './AuthContext' -import { AuthProvider } from './AuthProvider' -import { createNationalId } from '@island.is/testing/fixtures' - -const BASE_PATH = '/basepath' -const INITIATE_LOGIN_PATH = '/login' - -jest.mock('../userManager') -const mockedGetUserManager = getUserManager as jest.Mock -const mockedGetAuthSettings = getAuthSettings as jest.Mock - -const Greeting = () => { - const { userInfo } = useAuth() - return <>Hello {userInfo?.profile.name}</> -} - -const renderAuthenticator = (route = BASE_PATH) => { - window.history.pushState({}, 'Test page', route) - - return render( - <AuthProvider basePath={BASE_PATH}> - <h2> - <Greeting /> - </h2> - </AuthProvider>, - { wrapper: BrowserRouter }, - ) -} - -type MinimalUser = { - expired?: boolean - profile?: { - name: string - } -} -type MinimalUserManager = { - events: { - addUserLoaded: (cb: UserManagerEvents) => void - addUserSignedOut: jest.Mock - removeUserLoaded: () => void - removeUserSignedOut: () => void - } - getUser: jest.Mock<Promise<MinimalUser>> - signinRedirect: jest.Mock - signinSilent: jest.Mock - signinRedirectCallback: jest.Mock - removeUser: jest.Mock -} - -describe('AuthProvider', () => { - let userManager: MinimalUserManager - - const expectSignin = () => - waitFor(() => { - if (userManager.signinRedirect.mock.calls.length === 0) { - throw new Error('... wait') - } - }) - - const expectAuthenticated = (name: string) => - screen.findByText(`Hello ${name}`) - - beforeEach(() => { - userManager = { - events: { - addUserLoaded: jest.fn(), - addUserSignedOut: jest.fn(), - removeUserLoaded: jest.fn(), - removeUserSignedOut: jest.fn(), - }, - getUser: jest.fn(), - signinRedirect: jest.fn(), - signinSilent: jest.fn(), - signinRedirectCallback: jest.fn(), - removeUser: jest.fn(), - } - mockedGetUserManager.mockReturnValue(userManager) - mockedGetAuthSettings.mockReturnValue({ - baseUrl: BASE_PATH, - initiateLoginPath: INITIATE_LOGIN_PATH, - redirectPath: '/callback', - redirectPathSilent: '/callback-silent', - }) - }) - - it('starts signin flow when no stored user', async () => { - // Act - renderAuthenticator() - - // Assert - await expectSignin() - }) - - it('should show a progress bar while authenticating', async () => { - // Act - const { getByRole } = renderAuthenticator() - - // Assert - getByRole('progressbar') - await expectSignin() - }) - - it('authenticates with non-expired user', async () => { - // Arrange - userManager.getUser.mockResolvedValue({ - expired: false, - profile: { - name: 'John', - }, - }) - - // Act - renderAuthenticator() - - // Assert - await expectAuthenticated('John') - }) - - it('removes user and starts signin flow if user is logged out', async () => { - // Arrange - userManager.getUser.mockResolvedValue({ - expired: false, - profile: { - name: 'John', - }, - }) - renderAuthenticator() - await expectAuthenticated('John') - expect(userManager.events.addUserSignedOut).toHaveBeenCalled() - const handler = userManager.events.addUserSignedOut.mock.calls[0][0] - - // Act - await act(async () => { - await handler() - }) - - // Assert - await expectSignin() - expect(userManager.removeUser).toHaveBeenCalled() - }) - - it('performs silent signin with expired user', async () => { - // Arrange - userManager.getUser.mockResolvedValue({ - expired: true, - }) - userManager.signinSilent.mockResolvedValue({ - profile: { - name: 'Doe', - }, - }) - - // Act - renderAuthenticator() - - // Assert - await expectAuthenticated('Doe') - expect(userManager.signinSilent).toHaveBeenCalled() - }) - - it('starts signin flow if silent signin fails', async () => { - // Arrange - userManager.getUser.mockResolvedValue({ - expired: true, - }) - userManager.signinSilent.mockRejectedValue(new Error('Not signed in')) - - // Act - renderAuthenticator() - - // Assert - await expectSignin() - expect(userManager.signinSilent).toHaveBeenCalled() - }) - - // prettier-ignore - it.each` - params - ${{ prompt: 'login' }} - ${{ prompt: 'select_account' }} - ${{ - login_hint: createNationalId('company'), - target_link_uri: `${BASE_PATH}/test`, - }} - `( - 'starts 3rd party initiated login flow with params $params', - async (params: { prompt?: string, login_hint?: string, target_link_uri?: string }) => { - // Arrange - const searchParams = new URLSearchParams(params) - - // Act - renderAuthenticator( - `${BASE_PATH}${INITIATE_LOGIN_PATH}?${searchParams.toString()}`, - ) - - // Assert - await expectSignin() - expect(userManager.signinRedirect).toHaveBeenCalledWith({ - state: params.target_link_uri?.slice(BASE_PATH.length) ?? '/', - login_hint: params.login_hint, - prompt: params.prompt, - }) - }, - ) - - it('shows error screen if signin has an error', async () => { - // Arrange - const testRoute = `${BASE_PATH}${mockedGetAuthSettings().redirectPath}` - userManager.signinRedirectCallback.mockRejectedValue( - new Error('Test error'), - ) - - // Act - renderAuthenticator(testRoute) - - // Assert - await screen.findByText('Innskráning mistókst') - await screen.findByRole('button', { name: 'Reyna aftur' }) - }) - - it('shows error screen if IDS is unavailable', async () => { - // Arrange - // When the OIDC client library fails to load the /.well-known/openid-configuration - // the signinRedirect methods rejects the Promise with an Error. - userManager.signinRedirect.mockRejectedValueOnce( - new Error('Internal Server Error'), - ) - - // Act - renderAuthenticator() - - // Assert - await screen.findByText('Innskráning mistókst') - await screen.findByRole('button', { name: 'Reyna aftur' }) - }) -}) diff --git a/libs/auth/react/src/lib/auth/AuthProvider.tsx b/libs/auth/react/src/lib/auth/AuthProvider.tsx deleted file mode 100644 index 8243291478b5..000000000000 --- a/libs/auth/react/src/lib/auth/AuthProvider.tsx +++ /dev/null @@ -1,333 +0,0 @@ -import React, { - useCallback, - useEffect, - useMemo, - useReducer, - ReactNode, - useState, -} from 'react' -import type { SigninRedirectArgs, User } from 'oidc-client-ts' - -import { useEffectOnce } from '@island.is/react-spa/shared' -import { isDefined } from '@island.is/shared/utils' -import { LoadingScreen } from '@island.is/react/components' - -import { getAuthSettings, getUserManager } from '../userManager' -import { ActionType, initialState, reducer } from './Auth.state' -import { AuthSettings } from '../AuthSettings' -import { AuthContext } from './AuthContext' -import { AuthErrorScreen } from './AuthErrorScreen' -import { CheckIdpSession } from './CheckIdpSession' - -interface AuthProviderProps { - /** - * If true, Authenticator automatically starts login flow and does not render children until user is fully logged in. - * If false, children are responsible for rendering a login button and loading indicator. - * Default: true - */ - autoLogin?: boolean - /** - * The base path of the application. - */ - basePath: string - children: ReactNode -} - -type GetReturnUrl = { - returnUrl: string -} & Pick<AuthSettings, 'redirectPath'> - -const isCurrentRoute = (url: string, path?: string) => - isDefined(path) && url.startsWith(path) - -const getReturnUrl = ({ redirectPath, returnUrl }: GetReturnUrl) => { - if (redirectPath && returnUrl.startsWith(redirectPath)) { - return '/' - } - - return returnUrl -} - -const getCurrentUrl = (basePath: string) => { - const url = `${window.location.pathname}${window.location.search}${window.location.hash}` - - if (url.startsWith(basePath)) { - return url.slice(basePath.length) - } - - return '/' -} - -export const AuthProvider = ({ - children, - autoLogin = true, - basePath, -}: AuthProviderProps) => { - const [state, dispatch] = useReducer(reducer, initialState) - const [error, setError] = useState<Error | undefined>() - const userManager = getUserManager() - const authSettings = getAuthSettings() - const monitorUserSession = !authSettings.scope?.includes('offline_access') - - const signinRedirect = useCallback( - async (args: SigninRedirectArgs) => { - try { - await userManager.signinRedirect(args) - // On success Nothing more happens here since browser will redirect to IDS. - } catch (error) { - // On error we set the error state to show the error screen which provides the users with a retry button. - console.error(error) - setError(error) - } - }, - [userManager, setError], - ) - - const signIn = useCallback( - async function signIn() { - dispatch({ - type: ActionType.SIGNIN_START, - }) - - return signinRedirect({ - state: getReturnUrl({ - returnUrl: getCurrentUrl(basePath), - redirectPath: authSettings.redirectPath, - }), - }) - }, - [dispatch, authSettings, basePath], - ) - - const signInSilent = useCallback( - async function signInSilent() { - let user = null - dispatch({ - type: ActionType.SIGNIN_START, - }) - try { - user = await userManager.signinSilent() - dispatch({ type: ActionType.SIGNIN_SUCCESS, payload: user }) - } catch (error) { - console.error('AuthProvider: Silent signin failed', error) - dispatch({ type: ActionType.SIGNIN_FAILURE }) - } - - return user - }, - [userManager, dispatch], - ) - - const switchUser = useCallback( - async function switchUser(nationalId?: string) { - const args = - nationalId !== undefined - ? { - login_hint: nationalId, - /** - * TODO: remove this. - * It is currently required to switch delegations, but we'd like - * the IDS to handle login_required and other potential road - * blocks. Now OidcSignIn is handling login_required. - */ - prompt: 'none', - } - : { - prompt: 'select_account', - } - - dispatch({ - type: ActionType.SWITCH_USER, - }) - - return signinRedirect({ - state: - authSettings.switchUserRedirectUrl ?? - getReturnUrl({ - returnUrl: getCurrentUrl(basePath), - redirectPath: authSettings.redirectPath, - }), - ...args, - }) - // Nothing more happens here since browser will redirect to IDS. - }, - [userManager, dispatch, authSettings, basePath], - ) - - const signOut = useCallback( - async function signOut() { - dispatch({ - type: ActionType.LOGGING_OUT, - }) - await userManager.signoutRedirect() - }, - [userManager, dispatch], - ) - - const checkLogin = useCallback( - async function checkLogin() { - dispatch({ - type: ActionType.SIGNIN_START, - }) - const storedUser = await userManager.getUser() - - // Check expiry. - if (storedUser && !storedUser.expired) { - dispatch({ - type: ActionType.SIGNIN_SUCCESS, - payload: storedUser, - }) - } else if (autoLogin) { - // If we find a user in SessionStorage, there's a fine chance that - // it's just an expired token, and we can silently log in. - if (storedUser && (await signInSilent())) { - return - } - - // If all else fails, redirect to the login page. - await signIn() - } else { - // When not performing autologin, silently check if there's an IDP session. - await signInSilent() - } - }, - [userManager, dispatch, signIn, signInSilent, autoLogin], - ) - - const hasUserInfo = state.userInfo !== null - useEffect(() => { - // Only add events when we have userInfo, to avoid race conditions with - // oidc hooks. - if (!hasUserInfo) { - return - } - - // This is raised when a new user state has been loaded with a silent login. - const userLoaded = (user: User) => { - dispatch({ - type: ActionType.USER_LOADED, - payload: user, - }) - } - - // This is raised when the user is signed out of the IDP. - const userSignedOut = async () => { - dispatch({ - type: ActionType.LOGGED_OUT, - }) - await userManager.removeUser() - - if (autoLogin) { - signIn() - } - } - - userManager.events.addUserLoaded(userLoaded) - userManager.events.addUserSignedOut(userSignedOut) - return () => { - userManager.events.removeUserLoaded(userLoaded) - userManager.events.removeUserSignedOut(userSignedOut) - } - }, [dispatch, userManager, signIn, autoLogin, hasUserInfo]) - - const init = async () => { - const currentUrl = getCurrentUrl(basePath) - - if (isCurrentRoute(currentUrl, authSettings.redirectPath)) { - try { - const user = await userManager.signinRedirectCallback( - window.location.href, - ) - - const url = typeof user.state === 'string' ? user.state : '/' - window.history.replaceState(null, '', basePath + url) - - dispatch({ - type: ActionType.SIGNIN_SUCCESS, - payload: user, - }) - } catch (e) { - if (e.error === 'login_required') { - // If trying to switch delegations and the IDS session is expired, we'll - // see this error. So we'll try a proper signin. - return signinRedirect({ state: e.state }) - } - console.error('Error in oidc callback', e) - setError(e) - } - } else if (isCurrentRoute(currentUrl, authSettings.redirectPathSilent)) { - const userManager = getUserManager() - userManager.signinSilentCallback().catch((e) => { - console.log(e) - setError(e) - }) - } else if (isCurrentRoute(currentUrl, authSettings.initiateLoginPath)) { - const userManager = getUserManager() - const searchParams = new URL(window.location.href).searchParams - - const loginHint = searchParams.get('login_hint') - const targetLinkUri = searchParams.get('target_link_uri') - const path = - targetLinkUri && - authSettings.baseUrl && - targetLinkUri.startsWith(authSettings.baseUrl) - ? targetLinkUri.slice(authSettings.baseUrl.length) - : '/' - let prompt = searchParams.get('prompt') - prompt = - prompt && ['login', 'select_account'].includes(prompt) ? prompt : null - - const args = { - state: path, - prompt: prompt ?? undefined, - login_hint: loginHint ?? undefined, - } - return signinRedirect(args) - } else { - checkLogin() - } - } - - useEffectOnce(() => { - init() - }) - - const context = useMemo( - () => ({ - ...state, - signIn, - signInSilent, - switchUser, - signOut, - authority: authSettings.authority, - }), - [state, signIn, signInSilent, switchUser, signOut, authSettings.authority], - ) - - const url = getCurrentUrl(basePath) - const isLoading = - !state.userInfo || - // We need to display loading screen if current route is the redirectPath or redirectPathSilent. - // This is because these paths are not part of our React Router routes. - isCurrentRoute(url, authSettings?.redirectPath) || - isCurrentRoute(url, authSettings?.redirectPathSilent) - - const onRetry = () => { - window.location.href = basePath - } - - return ( - <AuthContext.Provider value={context}> - {error ? ( - <AuthErrorScreen onRetry={onRetry} /> - ) : isLoading ? ( - <LoadingScreen ariaLabel="Er að vinna í innskráningu" /> - ) : ( - <> - {monitorUserSession && <CheckIdpSession />} - {children} - </> - )} - </AuthContext.Provider> - ) -} diff --git a/libs/auth/react/src/lib/auth/CheckIdpSession.tsx b/libs/auth/react/src/lib/auth/CheckIdpSession.tsx deleted file mode 100644 index 1890dbd1efd6..000000000000 --- a/libs/auth/react/src/lib/auth/CheckIdpSession.tsx +++ /dev/null @@ -1,189 +0,0 @@ -import addSeconds from 'date-fns/addSeconds' -import { useCallback, useEffect, useReducer, useRef } from 'react' -import { getAuthSettings, getUserManager } from '../userManager' - -const UserSessionMessageType = 'SessionInfo' - -interface UserSessionMessage { - // Type to use to filter postMessage messages - type: typeof UserSessionMessageType - - // Status of the message received from IDP. - status: 'Ok' | 'No Session' | 'Failure' - - // The time when the authenticated session expires. - expiresUtc?: string - - // Number of seconds until the session expires. - expiresIn?: number - - // Boolean flag to indicated if the Expires time is passed. - isExpired?: boolean -} - -interface UserSessionState { - /* The expected time when the user session is ending. */ - sessionEnd: Date | null - - /** - * An interval function that checks if the expected sessionEnd has passed. - * When set this indicates that the user has an active session. - */ - intervalHandle: ReturnType<typeof setInterval> | null - - /* The number of times we have tried to load the iframe to receive a new session info message. */ - retryCount: number -} - -const MAX_RETRIES = 2 -const ACTIVE_SESSION_DELAY = 5 * 1000 -const CHECK_SESSION_INTERVAL = 2 * 1000 - -const EMPTY_SESSION: UserSessionState = { - retryCount: 0, - sessionEnd: null, - intervalHandle: null, -} - -/** - * This component monitors if the user session is active on the Identity Provider (IDP). - * When it detects that the user session is expired it redirects to the sign-in page on the IDP. - * - * It loads a script from the IDP's 'connect/sessioninfo' endpoint into an iframe. - * The script uses the postMessage API to post UserSessionMessage, which contains - * details if the session is expired or after how many seconds it will expire. - * We use these details to register an interval to monitor the session expiration. - */ -export const CheckIdpSession = () => { - const userManager = getUserManager() - const authSettings = getAuthSettings() - const iframeSrc = `${authSettings.authority}${authSettings.checkSessionPath}` - const [iframeId, reloadIframe] = useReducer((id) => id + 1, 0) - const userSession = useRef<UserSessionState>({ ...EMPTY_SESSION }) - - const isActive = useCallback(() => { - // When intervalHandle is set it means we have registered - // a setInterval to monitor an active user session. - return !!userSession.current.intervalHandle - }, []) - - const hasBeenActive = useCallback(() => { - // When sessionEnd is set it means the has been active - // as we have an earlier UserSessionMessage. - return !!userSession.current.sessionEnd - }, []) - - const resetUserSession = useCallback(() => { - if (userSession.current.intervalHandle) { - clearInterval(userSession.current.intervalHandle) - } - userSession.current.intervalHandle = null - userSession.current.retryCount = 0 - // Intentionally not resetting sessionEnd as it - // indicates that the user has had session before. - }, []) - - const signInRedirect = useCallback(async () => { - await userManager.removeUser() - return window.location.reload() - }, [userManager]) - - const checkActiveSession = useCallback(() => { - setTimeout(() => { - const { retryCount } = userSession.current - - if (!isActive() && retryCount > MAX_RETRIES && hasBeenActive()) { - // We were unable to retrieve a message from the IDP after max retries and have a reason - // to believe that the session is expired (an earlier UserSessionMessage has expired). - // So we reload the window just to be safe. This causes one of three things to happen: - // - If the iframe is broken and the user does have a valid IDP session, they'll generally reload where they were. - // - If the iframe is broken and the user does not have a valid IDP session, they're sent to the login page. - // - If the user has a network problem, then they'll see a browser error screen, but at least any sensitive information is not visible any more. - window.location.reload() - } else if (!isActive() && retryCount < MAX_RETRIES) { - userSession.current.retryCount += 1 - // We are unable to retrieve a message from the IDP, - // so we reload the iframe to retry without reloading the window. - reloadIframe() - } - }, ACTIVE_SESSION_DELAY) - }, [isActive, hasBeenActive]) - - const messageHandler = useCallback( - async ({ data, origin }: MessageEvent): Promise<void> => { - const sessionInfo = data as UserSessionMessage - - // Check if the postMessage is meant for us - if ( - origin !== authSettings.authority || - sessionInfo.type !== UserSessionMessageType - ) { - return - } - - if (sessionInfo && sessionInfo.status === 'Ok') { - // SessionInfo was found, check if it is valid or expired - if (sessionInfo.isExpired) { - return signInRedirect() - } else if (!isActive() && sessionInfo.expiresIn !== undefined) { - userSession.current.sessionEnd = addSeconds( - new Date(), - sessionInfo.expiresIn, - ) - - userSession.current.intervalHandle = setInterval(() => { - const now = new Date() - - if ( - userSession.current.sessionEnd && - now > userSession.current.sessionEnd - ) { - // The expected session end has passed but the user might have extended their session. - // So we reset the session state and reload the iframe to query new session info from the IDP. - resetUserSession() - reloadIframe() - } - }, CHECK_SESSION_INTERVAL) - } - } else if ( - sessionInfo && - sessionInfo.status === 'No Session' && - hasBeenActive() - ) { - return signInRedirect() - } - - // Silent failure as we have failed to get sessionInfo but the user still might have valid session. - // So we only trigger the signInRedirect flow when we get definite response about expired session. - }, - [ - authSettings.authority, - signInRedirect, - isActive, - hasBeenActive, - resetUserSession, - ], - ) - - useEffect(() => { - window.addEventListener('message', messageHandler) - - return () => { - window.removeEventListener('message', messageHandler) - } - }, [messageHandler]) - - return ( - <iframe - // We use the key attribute to trigger new reload of the iframe - key={iframeId} - title="Check IDP session" - id="check-idp-session" - src={iframeSrc} - width={0} - height={0} - style={{ display: 'none' }} - onLoad={checkActiveSession} - /> - ) -} diff --git a/libs/auth/react/src/lib/auth/MockedAuthProvider.spec.tsx b/libs/auth/react/src/lib/auth/MockedAuthProvider.spec.tsx deleted file mode 100644 index c90e77a95cb0..000000000000 --- a/libs/auth/react/src/lib/auth/MockedAuthProvider.spec.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import React from 'react' -import { render } from '@testing-library/react' -import '@testing-library/jest-dom' -import { useAuth } from './AuthContext' -import { MockedAuthProvider } from './MockedAuthProvider' - -const TestConsumer = () => { - const { userInfo } = useAuth() - return <span>User: {userInfo ? userInfo.profile.name : 'Not logged in'}</span> -} - -describe('MockedAuthProvider', () => { - it('defaults to unauthenticated', () => { - const { getByText } = render( - <MockedAuthProvider> - <TestConsumer /> - </MockedAuthProvider>, - ) - expect(getByText(/^User:/)).toHaveTextContent('User: Not logged in') - }) - - it('provides good default values for user', () => { - const { getByText } = render( - <MockedAuthProvider user={{}}> - <TestConsumer /> - </MockedAuthProvider>, - ) - expect(getByText(/^User:/)).toHaveTextContent('User: Mock') - }) - - it('supports overriding user details', () => { - const { getByText } = render( - <MockedAuthProvider user={{ profile: { name: 'Peter' } }}> - <TestConsumer /> - </MockedAuthProvider>, - ) - expect(getByText(/^User:/)).toHaveTextContent('User: Peter') - }) -}) diff --git a/libs/auth/react/src/lib/auth/MockedAuthProvider.tsx b/libs/auth/react/src/lib/auth/MockedAuthProvider.tsx deleted file mode 100644 index 4da0e2de69e7..000000000000 --- a/libs/auth/react/src/lib/auth/MockedAuthProvider.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import React, { FC } from 'react' -import { AuthContext, defaultAuthContext } from './AuthContext' -import { createMockUser, MockUser } from '../createMockUser' - -interface MockedAuthenticatorProps { - user?: MockUser - signOut?: () => void - switchUser?: (nationalId?: string) => void -} - -export const MockedAuthProvider: FC< - React.PropsWithChildren<MockedAuthenticatorProps> -> = ({ children, signOut, switchUser, user }) => { - const userInfo = user ? createMockUser(user) : null - return ( - <AuthContext.Provider - value={{ - ...defaultAuthContext, - userInfo, - ...(switchUser && { switchUser }), - ...(signOut && { signOut }), - }} - > - {children} - </AuthContext.Provider> - ) -} diff --git a/libs/auth/react/src/lib/authLink.ts b/libs/auth/react/src/lib/authLink.ts deleted file mode 100644 index c097a99ec488..000000000000 --- a/libs/auth/react/src/lib/authLink.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { setContext } from '@apollo/client/link/context' -import { getAccessToken } from './getAccessToken' - -export const authLink = setContext(async (_, { headers }) => { - const token = await getAccessToken() - return { - headers: { - ...headers, - authorization: token ? `Bearer ${token}` : '', - }, - } -}) diff --git a/libs/auth/react/src/lib/createMockUser.ts b/libs/auth/react/src/lib/createMockUser.ts deleted file mode 100644 index d7b7c87f62eb..000000000000 --- a/libs/auth/react/src/lib/createMockUser.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { User } from '@island.is/shared/types' - -export interface MockUser extends Partial<Omit<User, 'profile'>> { - profile?: Partial<User['profile']> -} - -export const createMockUser = (user?: MockUser) => - ({ - profile: { - name: 'Mock', - locale: 'is', - nationalId: '0000000000', - ...user?.profile, - }, - expired: false, - expires_in: 9999, - scopes: [], - ...user, - } as User) diff --git a/libs/auth/react/src/lib/getAccessToken.spec.ts b/libs/auth/react/src/lib/getAccessToken.spec.ts deleted file mode 100644 index 56a67f0d76e2..000000000000 --- a/libs/auth/react/src/lib/getAccessToken.spec.ts +++ /dev/null @@ -1,152 +0,0 @@ -import { - getAccessToken, - MIN_EXPIRY_FOR_PRE_SIGNIN, - MIN_EXPIRY_FOR_RE_SIGNIN, -} from './getAccessToken' -import { getUserManager } from './userManager' - -jest.mock('./userManager') -const mockedGetUserManager = getUserManager as jest.Mock - -type MinimalUser = { - access_token: string - expires_in: number -} - -type MinimalUserManager = { - getUser: jest.Mock<Promise<MinimalUser | null>> - signinSilent: jest.Mock<Promise<MinimalUser | null>> -} - -describe('getAccessToken', () => { - let userManager: MinimalUserManager - - beforeEach(() => { - userManager = { - getUser: jest.fn(), - signinSilent: jest.fn(), - } - mockedGetUserManager.mockReturnValue(userManager) - }) - - it('gets access token from user', async () => { - // Arrange - const tokenValue = 'Test token' - userManager.getUser.mockResolvedValue({ - access_token: tokenValue, - expires_in: 1000, - }) - - // Act - const token = await getAccessToken() - - // Assert - expect(userManager.signinSilent).not.toHaveBeenCalled() - expect(token).toBe(tokenValue) - }) - - it("returns null if there's no user", async () => { - // Arrange - userManager.getUser.mockResolvedValue(null) - - // Act - const token = await getAccessToken() - - // Assert - expect(token).toBe(null) - }) - - it("renews token if it's expired", async () => { - // Arrange - const tokenValue = 'Test token' - userManager.getUser.mockResolvedValue({ - access_token: 'unused', - expires_in: -100, - }) - userManager.signinSilent.mockResolvedValue({ - access_token: tokenValue, - expires_in: 1000, - }) - - // Act - const token = await getAccessToken() - - // Assert - expect(userManager.signinSilent).toHaveBeenCalled() - expect(token).toBe(tokenValue) - }) - - it("renews token if it's almost expired", async () => { - // Arrange - const tokenValue = 'Test token' - userManager.getUser.mockResolvedValue({ - access_token: 'unused', - expires_in: MIN_EXPIRY_FOR_RE_SIGNIN - 1, - }) - userManager.signinSilent.mockResolvedValue({ - access_token: tokenValue, - expires_in: 1000, - }) - - // Act - const token = await getAccessToken() - - // Assert - expect(userManager.signinSilent).toHaveBeenCalled() - expect(token).toBe(tokenValue) - }) - - it("returns null if there's no user when silently renewing", async () => { - // Arrange - userManager.getUser.mockResolvedValue({ - access_token: 'unused', - expires_in: -100, - }) - userManager.signinSilent.mockResolvedValue(null) - - // Act - const token = await getAccessToken() - - // Assert - expect(token).toBe(null) - }) - - it("prefetches token if it's about to expire but returns old token", async () => { - // Arrange - const tokenValue = 'Test token' - userManager.getUser.mockResolvedValue({ - access_token: tokenValue, - expires_in: MIN_EXPIRY_FOR_PRE_SIGNIN - 1, - }) - userManager.signinSilent.mockResolvedValue(null) - - // Act - const token = await getAccessToken() - - // Assert - expect(userManager.signinSilent).toHaveBeenCalled() - expect(token).toBe(tokenValue) - }) - - it('only silently renews once at a time', async () => { - // Arrange - const tokenValue = 'Test token' - userManager.getUser.mockResolvedValue({ - access_token: 'unused', - expires_in: -100, - }) - userManager.signinSilent - .mockResolvedValueOnce({ access_token: tokenValue, expires_in: 1000 }) - .mockResolvedValueOnce(null) - - // Act - const promise1 = getAccessToken() - const promise2 = getAccessToken() - const [result1, result2] = await Promise.all([promise1, promise2]) - - // Assert - expect(userManager.signinSilent).toHaveBeenCalledTimes(1) - expect(result1).toBe(tokenValue) - expect(result2).toBe(tokenValue) - }) -}) diff --git a/libs/auth/react/src/lib/getAccessToken.ts b/libs/auth/react/src/lib/getAccessToken.ts deleted file mode 100644 index 6a5e15e8d7ff..000000000000 --- a/libs/auth/react/src/lib/getAccessToken.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { getUserManager } from './userManager' -import { tinyMemoize } from './utils/tinyMemoize' - -// Should not use access tokens that are expired or just about to expire. -// Get a new one just in case. -export const MIN_EXPIRY_FOR_RE_SIGNIN = 10 - -// If we call an API a couple of minutes before an access token, we assume -// the user is still around and actively using the client. Then we prefetch a -// new access token before it expires, to avoid request delays. -export const MIN_EXPIRY_FOR_PRE_SIGNIN = 120 - -const fetchNewToken = tinyMemoize(() => { - // This can fail if IDP session is finished. This is ignored here and dealt - // with in Authenticator. - return getUserManager() - .signinSilent() - .catch(() => null) -}) - -export const getAccessToken = async () => { - let user = await getUserManager().getUser() - if (!user) { - return null - } - - // Token is either expired, or just about to expire. We should get a new - // token either way. - if (!user.expires_in || user.expires_in < MIN_EXPIRY_FOR_RE_SIGNIN) { - user = await fetchNewToken() - if (!user) { - return null - } - } - - // We're still active but the token will expire soon. We'll make sure to - // prefetch a new token for later. - else if (!user.expires_in || user.expires_in < MIN_EXPIRY_FOR_PRE_SIGNIN) { - // Intentionally not awaited. We don't want to delay the current request. - fetchNewToken() - } - - return user.access_token -} diff --git a/libs/auth/react/src/lib/userManager.ts b/libs/auth/react/src/lib/userManager.ts deleted file mode 100644 index 1c685778018d..000000000000 --- a/libs/auth/react/src/lib/userManager.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { User, UserManager } from 'oidc-client-ts' - -import { AuthSettings, mergeAuthSettings } from './AuthSettings' -import { toStringScope } from './utils/toStringScope' -import { createMockUser, MockUser } from './createMockUser' - -let authSettings: AuthSettings | null = null -let userManager: UserManager | null = null - -export const getUserManager = (): UserManager => { - if (userManager === null) { - throw new Error('Tried to access user manager before calling configure') - } - return userManager -} - -export const getAuthSettings = (): AuthSettings => { - if (authSettings === null) { - throw new Error('Tried to access auth settings before calling configure') - } - return authSettings -} - -export const configure = (settings: AuthSettings) => { - authSettings = mergeAuthSettings(settings) - - userManager = new UserManager({ - ...authSettings, - scope: toStringScope(settings.scope), - redirect_uri: `${authSettings.baseUrl}${authSettings.redirectPath}`, - }) - - return userManager -} - -export const configureMock = (user?: MockUser) => { - authSettings = mergeAuthSettings({ - client_id: 'test-client', - authority: 'https://innskra.island.is', - }) - - const userInfo = createMockUser(user) - const empty = () => { - /* intentionally empty */ - } - - userManager = { - getUser(): Promise<User> { - return Promise.resolve(userInfo) - }, - signinSilent(): Promise<User> { - return Promise.resolve(userInfo) - }, - signinRedirect: empty, - events: { - addUserSignedOut: empty, - addUserLoaded: empty, - removeUserLoaded: empty, - removeUserSignedOut: empty, - }, - } as unknown as UserManager -} - -export { User, UserManager } diff --git a/libs/auth/react/src/lib/utils/tinyMemoize.spec.ts b/libs/auth/react/src/lib/utils/tinyMemoize.spec.ts deleted file mode 100644 index e427cfc099a6..000000000000 --- a/libs/auth/react/src/lib/utils/tinyMemoize.spec.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { tinyMemoize } from './tinyMemoize' - -type ResolveFn = (value?: number | PromiseLike<number>) => void - -describe('tinyMemoize', () => { - let resolve: ResolveFn - let slowFunction: jest.Mock - let memoizedFunction: () => Promise<number> - - beforeEach(() => { - // arrange - slowFunction = jest.fn( - () => - new Promise<number>((res) => { - resolve = res - }), - ) - memoizedFunction = tinyMemoize(slowFunction) - }) - - it("memoizes calls to an async function while it's running", async () => { - // arrange - const value = Math.random() - - // act - const promise1 = memoizedFunction() - const promise2 = memoizedFunction() - resolve(value) - const results = await Promise.all([promise1, promise2]) - - // assert - expect(slowFunction).toHaveBeenCalledTimes(1) - expect(promise1).toBe(promise2) - expect(results).toStrictEqual([value, value]) - }) - - it('stops memoizing as soon as the async function finishes', async () => { - // arrange - const value1 = Math.random() - const value2 = value1 + 0.1 - - // act - const promise1 = memoizedFunction() - resolve(value1) - const result1 = await promise1 - - const promise2 = memoizedFunction() - resolve(value2) - const result2 = await promise2 - - // assert - expect(slowFunction).toHaveBeenCalledTimes(2) - expect(promise1).not.toBe(promise2) - expect(result1).toEqual(value1) - expect(result2).toEqual(value2) - }) -}) diff --git a/libs/auth/react/src/lib/utils/tinyMemoize.ts b/libs/auth/react/src/lib/utils/tinyMemoize.ts deleted file mode 100644 index 90bf5e40bf20..000000000000 --- a/libs/auth/react/src/lib/utils/tinyMemoize.ts +++ /dev/null @@ -1,17 +0,0 @@ -/** - * Makes sure there's only one call to an async function active at any time. - */ -export const tinyMemoize = <T>(asyncFunction: () => Promise<T>) => { - let lastPromise: Promise<T> | null - return () => { - if (!lastPromise) { - lastPromise = asyncFunction() - lastPromise.finally(() => { - lastPromise = null - }) - return lastPromise - } - - return lastPromise - } -} diff --git a/libs/auth/react/src/lib/utils/toStringScope.spec.ts b/libs/auth/react/src/lib/utils/toStringScope.spec.ts deleted file mode 100644 index 928bfca6f130..000000000000 --- a/libs/auth/react/src/lib/utils/toStringScope.spec.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { toStringScope } from './toStringScope' - -describe('toStringScope', () => { - it('should return a string for an array of scope', () => { - expect(toStringScope(['scope1', 'scope2'])).toBe('scope1 scope2') - }) - - it('should return an empty string when no scope is defined', () => { - expect(toStringScope()).toBe('') - expect(toStringScope([])).toBe('') - }) -}) diff --git a/libs/auth/react/src/lib/utils/toStringScope.ts b/libs/auth/react/src/lib/utils/toStringScope.ts deleted file mode 100644 index 50730e02b5b4..000000000000 --- a/libs/auth/react/src/lib/utils/toStringScope.ts +++ /dev/null @@ -1,2 +0,0 @@ -export const toStringScope = (array?: string[]): string => - (array ?? []).join(' ') diff --git a/libs/auth/react/test/setup.ts b/libs/auth/react/test/setup.ts deleted file mode 100644 index d6e27b1da068..000000000000 --- a/libs/auth/react/test/setup.ts +++ /dev/null @@ -1 +0,0 @@ -import '@vanilla-extract/css/disableRuntimeStyles' diff --git a/libs/auth/react/tsconfig.json b/libs/auth/react/tsconfig.json deleted file mode 100644 index 45d3d172fde4..000000000000 --- a/libs/auth/react/tsconfig.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "extends": "../../../tsconfig.base.json", - "compilerOptions": { - "jsx": "react-jsx", - "allowJs": true - }, - "files": [], - "include": [], - "references": [ - { - "path": "./tsconfig.lib.json" - }, - { - "path": "./tsconfig.spec.json" - } - ] -} diff --git a/libs/auth/react/tsconfig.lib.json b/libs/auth/react/tsconfig.lib.json deleted file mode 100644 index 7da3e9d24116..000000000000 --- a/libs/auth/react/tsconfig.lib.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "outDir": "../../../dist/out-tsc", - "types": ["node"] - }, - "files": [ - "../../../node_modules/@nx/react/typings/cssmodule.d.ts", - "../../../node_modules/@nx/react/typings/image.d.ts" - ], - "exclude": [ - "**/*.spec.ts", - "**/*.test.ts", - "**/*.spec.tsx", - "**/*.test.tsx", - "jest.config.ts" - ], - "include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"] -} diff --git a/libs/auth/react/tsconfig.spec.json b/libs/auth/react/tsconfig.spec.json deleted file mode 100644 index e1535ba9d07c..000000000000 --- a/libs/auth/react/tsconfig.spec.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "outDir": "../../../dist/out-tsc", - "module": "commonjs", - "types": ["jest", "node"] - }, - "include": [ - "**/*.spec.ts", - "**/*.test.ts", - "**/*.spec.tsx", - "**/*.test.tsx", - "**/*.spec.js", - "**/*.test.js", - "**/*.spec.jsx", - "**/*.test.jsx", - "**/*.d.ts", - "jest.config.ts" - ] -} diff --git a/libs/clients/health-directorate/src/lib/clients/vaccinations/clientConfig.json b/libs/clients/health-directorate/src/lib/clients/vaccinations/clientConfig.json index a17038288d7a..37ea7918d8d7 100644 --- a/libs/clients/health-directorate/src/lib/clients/vaccinations/clientConfig.json +++ b/libs/clients/health-directorate/src/lib/clients/vaccinations/clientConfig.json @@ -597,7 +597,7 @@ "schema": { "type": "string" } }, { - "name": "age", + "name": "agePatient", "required": true, "in": "query", "schema": { "type": "number" } @@ -613,6 +613,12 @@ "required": true, "in": "query", "schema": { "type": "array", "items": { "type": "string" } } + }, + { + "name": "vaccineCodes", + "required": true, + "in": "query", + "schema": { "type": "array", "items": { "type": "string" } } } ], "responses": { @@ -820,6 +826,7 @@ "diseaseId": { "type": "string" }, "order": { "type": "number" }, "type": { "type": "object" }, + "vaccineCodes": { "type": "string" }, "cond1Type": { "type": "string" }, "cond1Min": { "type": "number" }, "cond1Max": { "type": "number" }, @@ -859,6 +866,7 @@ "description": { "type": "string" }, "isFeatured": { "type": "boolean" }, "isVisible": { "type": "boolean" }, + "hideIfNoVaccinations": { "type": "boolean" }, "vaccines": { "type": "array", "items": { "$ref": "#/components/schemas/VaccineDiseaseDto" } @@ -877,6 +885,7 @@ "name", "isFeatured", "isVisible", + "hideIfNoVaccinations", "vaccines", "rules", "translations" @@ -890,6 +899,7 @@ "description": { "type": "string" }, "isFeatured": { "type": "boolean" }, "isVisible": { "type": "boolean" }, + "hideIfNoVaccinations": { "type": "boolean" }, "vaccines": { "type": "array", "items": { "$ref": "#/components/schemas/VaccineDiseaseDto" } @@ -909,6 +919,7 @@ "properties": { "order": { "type": "number" }, "type": { "type": "object" }, + "vaccineCodes": { "type": "string" }, "cond1Type": { "type": "string" }, "cond1Min": { "type": "number" }, "cond1Max": { "type": "number" }, @@ -938,6 +949,7 @@ "diseaseId": { "type": "string" }, "order": { "type": "number" }, "type": { "type": "object" }, + "vaccineCodes": { "type": "string" }, "cond1Type": { "type": "string" }, "cond1Min": { "type": "number" }, "cond1Max": { "type": "number" }, diff --git a/libs/cms/src/lib/cms.elasticsearch.service.ts b/libs/cms/src/lib/cms.elasticsearch.service.ts index 6b2b5c5d96a3..e35685d85964 100644 --- a/libs/cms/src/lib/cms.elasticsearch.service.ts +++ b/libs/cms/src/lib/cms.elasticsearch.service.ts @@ -509,7 +509,9 @@ export class CmsElasticsearchService { }, ] - let queryString = input.queryString ? input.queryString.toLowerCase() : '' + let queryString = input.queryString + ? input.queryString.trim().toLowerCase() + : '' if (input.lang === 'is') { queryString = queryString.replace('`', '') @@ -520,6 +522,7 @@ export class CmsElasticsearchService { query: queryString + '*', fields: ['title^100', 'content'], analyze_wildcard: true, + default_operator: 'and', }, }) @@ -531,15 +534,8 @@ export class CmsElasticsearchService { order: SortDirection.DESC, }, }, - // Sort items with equal values by ascending title order - { 'title.sort': { order: SortDirection.ASC } }, ] - // Order by score first in case there is a query string - if (queryString.length > 0 && queryString !== '*') { - sort.unshift('_score') - } - if (input.tags && input.tags.length > 0 && input.tagGroups) { must = must.concat( generateGenericTagGroupQueries(input.tags, input.tagGroups), diff --git a/libs/cms/src/lib/search/importers/genericListItem.service.ts b/libs/cms/src/lib/search/importers/genericListItem.service.ts index a55d8e21905f..adf56bed139e 100644 --- a/libs/cms/src/lib/search/importers/genericListItem.service.ts +++ b/libs/cms/src/lib/search/importers/genericListItem.service.ts @@ -51,6 +51,10 @@ export class GenericListItemSyncService ) } + for (const tag of mapped.filterTags ?? []) { + contentSections.push(tag.title) + } + const content = contentSections.join(' ') const tags: MappedData['tags'] = diff --git a/libs/cms/src/lib/search/importers/teamList.service.ts b/libs/cms/src/lib/search/importers/teamList.service.ts index 86fc4699cf9b..57cf25f54878 100644 --- a/libs/cms/src/lib/search/importers/teamList.service.ts +++ b/libs/cms/src/lib/search/importers/teamList.service.ts @@ -31,9 +31,18 @@ export class TeamListSyncService implements CmsSyncProvider<ITeamList> { const memberEntry = teamListEntry.fields.teamMembers?.find( (m) => m.sys.id === member.id, ) - const content = memberEntry?.fields?.intro - ? documentToPlainTextString(memberEntry.fields.intro) - : '' + const contentSection: string[] = [] + + contentSection.push( + memberEntry?.fields?.intro + ? documentToPlainTextString(memberEntry.fields.intro) + : '', + ) + if (member.title) { + contentSection.push(member.title) + } + + const content = contentSection.join(' ') teamMembers.push({ _id: member.id, title: member.name, diff --git a/libs/infra-tracing/src/lib/code-owner.spec.ts b/libs/infra-tracing/src/lib/code-owner.spec.ts index ed860dbd7a97..3aed2bbb1d55 100644 --- a/libs/infra-tracing/src/lib/code-owner.spec.ts +++ b/libs/infra-tracing/src/lib/code-owner.spec.ts @@ -1,12 +1,12 @@ -import { setCodeOwner } from './code-owner' +import { withCodeOwner } from './code-owner' import { CodeOwners } from '@island.is/shared/constants' -import { logger } from '@island.is/logging' +import { logger, withLoggingContext } from '@island.is/logging' import tracer from 'dd-trace' jest.mock('dd-trace') jest.mock('@island.is/logging') -describe('setCodeOwner', () => { +describe('withCodeOwner', () => { let mockSpan: { setTag: jest.Mock } let mockScope: jest.Mock @@ -19,31 +19,63 @@ describe('setCodeOwner', () => { })) ;(tracer.scope as jest.Mock) = mockScope ;(logger.warn as jest.Mock).mockClear() + ;(withLoggingContext as jest.Mock).mockImplementation( + (_, callback, ...args) => callback(...args), + ) }) - it('should set code owner tag on active span', () => { + it('should set code owner tag on active span and call the callback', () => { + // Arrange + const mockCallback = jest.fn() + const mockArgs = ['arg1', 'arg2'] + // Act - setCodeOwner(CodeOwners.Core) + withCodeOwner(CodeOwners.Core, mockCallback, ...mockArgs) // Assert expect(mockSpan.setTag).toHaveBeenCalledWith('codeOwner', CodeOwners.Core) + expect(withLoggingContext).toHaveBeenCalledWith( + { codeOwner: CodeOwners.Core }, + mockCallback, + ...mockArgs, + ) + expect(mockCallback).toHaveBeenCalledWith(...mockArgs) expect(logger.warn).not.toHaveBeenCalled() }) - it('should log warning when no active span exists', () => { + it('should log warning when no active span exists and still call the callback', () => { // Arrange mockScope = jest.fn(() => ({ active: () => null, })) ;(tracer.scope as jest.Mock) = mockScope + const mockCallback = jest.fn() + const mockArgs = ['arg1', 'arg2'] // Act - setCodeOwner(CodeOwners.Core) + withCodeOwner(CodeOwners.Core, mockCallback, ...mockArgs) // Assert expect(logger.warn).toHaveBeenCalledWith( 'Setting code owner "core" with no active dd-trace span', - { stack: expect.any(String) }, ) + expect(withLoggingContext).toHaveBeenCalledWith( + { codeOwner: CodeOwners.Core }, + mockCallback, + ...mockArgs, + ) + expect(mockCallback).toHaveBeenCalledWith(...mockArgs) + }) + + it('should return the callback result', () => { + // Arrange + const expectedResult = { foo: 'bar' } + const mockCallback = jest.fn().mockReturnValue(expectedResult) + + // Act + const result = withCodeOwner(CodeOwners.Core, mockCallback) + + // Assert + expect(result).toBe(expectedResult) }) }) diff --git a/libs/infra-tracing/src/lib/code-owner.ts b/libs/infra-tracing/src/lib/code-owner.ts index 7d4d6352fe52..f0d085dbf4dc 100644 --- a/libs/infra-tracing/src/lib/code-owner.ts +++ b/libs/infra-tracing/src/lib/code-owner.ts @@ -1,22 +1,26 @@ -import { logger } from '@island.is/logging' +import { logger, withLoggingContext } from '@island.is/logging' import { CodeOwners } from '@island.is/shared/constants' import tracer from 'dd-trace' /** - * Sets a code owner for the current dd-trace span. + * Sets a code owner for the current dd-trace span and all nested log entries. * * The assumption here is that each trace / request has only one "dynamic" * code owner. This way we skip cluttering the trace with extra spans. */ -export const setCodeOwner = (codeOwner: CodeOwners) => { +export const withCodeOwner = <R, TArgs extends unknown[]>( + codeOwner: CodeOwners, + callback: (...args: TArgs) => R, + ...args: TArgs +) => { const span = tracer.scope().active() if (span) { span.setTag('codeOwner', codeOwner) - } else { - const stack = new Error().stack + } else if (process.env.NODE_ENV !== 'development') { logger.warn( `Setting code owner "${codeOwner}" with no active dd-trace span`, - { stack }, ) } + + return withLoggingContext({ codeOwner }, callback, ...args) } diff --git a/libs/island-ui/core/src/lib/DropdownMenu/DropdownMenu.css.ts b/libs/island-ui/core/src/lib/DropdownMenu/DropdownMenu.css.ts index ead5acb77d0c..4b357a3f1bec 100644 --- a/libs/island-ui/core/src/lib/DropdownMenu/DropdownMenu.css.ts +++ b/libs/island-ui/core/src/lib/DropdownMenu/DropdownMenu.css.ts @@ -2,7 +2,7 @@ import { theme, zIndex } from '@island.is/island-ui/theme' import { style, globalStyle } from '@vanilla-extract/css' export const menu = style({ - width: 150, + minWidth: 150, zIndex: zIndex.above, boxShadow: '0px 4px 30px rgba(0, 97, 255, 0.16)', ':focus': { @@ -22,6 +22,7 @@ export const menuItem = style({ outline: 'none', }, }, + padding: `${theme.spacing[2]}px ${theme.spacing[1]}px`, }) globalStyle(`${menuItem} button:focus`, { diff --git a/libs/island-ui/storybook/config/main.ts b/libs/island-ui/storybook/config/main.ts index 3c9e5ccccacd..051bf8baecdd 100644 --- a/libs/island-ui/storybook/config/main.ts +++ b/libs/island-ui/storybook/config/main.ts @@ -75,7 +75,6 @@ const config: StorybookConfig = { '@island.is/application/ui-components': rootDir( '../../../application/ui-components/src', ), - '@island.is/auth/react': rootDir('../../../auth/react/src'), '@island.is/shared/constants': rootDir('../../../shared/constants/src'), '@island.is/shared/form-fields': rootDir( '../../../shared/form-fields/src', diff --git a/libs/judicial-system/message/src/lib/message.ts b/libs/judicial-system/message/src/lib/message.ts index c8650bc09256..c93854bab4e6 100644 --- a/libs/judicial-system/message/src/lib/message.ts +++ b/libs/judicial-system/message/src/lib/message.ts @@ -31,6 +31,8 @@ export enum MessageType { NOTIFICATION_DISPATCH = 'NOTIFICATION_DISPATCH', DEFENDANT_NOTIFICATION = 'DEFENDANT_NOTIFICATION', CIVIL_CLAIMANT_NOTIFICATION = 'CIVIL_CLAIMANT_NOTIFICATION', + INDICTMENT_CASE_NOTIFICATION = 'INDICTMENT_CASE_NOTIFICATION', + EVENT_NOTIFICATION_DISPATCH = 'EVENT_NOTIFICATION_DISPATCH', } export const messageEndpoint: { [key in MessageType]: string } = { @@ -68,6 +70,8 @@ export const messageEndpoint: { [key in MessageType]: string } = { NOTIFICATION_DISPATCH: 'notification/dispatch', DEFENDANT_NOTIFICATION: 'defendantNotification', CIVIL_CLAIMANT_NOTIFICATION: 'civilClaimantNotification', + INDICTMENT_CASE_NOTIFICATION: 'indictmentCaseNotification', + EVENT_NOTIFICATION_DISPATCH: 'eventNotification/dispatch', } export type Message = { diff --git a/libs/judicial-system/types/src/index.ts b/libs/judicial-system/types/src/index.ts index 140f6c9bc92a..82a11272ee06 100644 --- a/libs/judicial-system/types/src/index.ts +++ b/libs/judicial-system/types/src/index.ts @@ -19,6 +19,8 @@ export { NotificationDispatchType, DefendantNotificationType, CivilClaimantNotificationType, + IndictmentCaseNotificationType, + EventNotificationType, notificationTypes, } from './lib/notification' export type { Institution } from './lib/institution' diff --git a/libs/judicial-system/types/src/lib/feature.ts b/libs/judicial-system/types/src/lib/feature.ts index 742e56c2f145..dd8f5e437a8f 100644 --- a/libs/judicial-system/types/src/lib/feature.ts +++ b/libs/judicial-system/types/src/lib/feature.ts @@ -1,3 +1,4 @@ export enum Feature { NONE = 'NONE', // must be at least one + MULTIPLE_INDICTMENT_SUBTYPES = 'MULTIPLE_INDICTMENT_SUBTYPES', } diff --git a/libs/judicial-system/types/src/lib/notification.ts b/libs/judicial-system/types/src/lib/notification.ts index e9b8df060e63..347cef7b5852 100644 --- a/libs/judicial-system/types/src/lib/notification.ts +++ b/libs/judicial-system/types/src/lib/notification.ts @@ -20,6 +20,10 @@ export enum CaseNotificationType { CASE_FILES_UPDATED = 'CASE_FILES_UPDATED', } +export enum IndictmentCaseNotificationType { + INDICTMENT_VERDICT_INFO = 'INDICTMENT_VERDICT_INFO', +} + export enum DefendantNotificationType { DEFENDANT_SELECTED_DEFENDER = 'DEFENDANT_SELECTED_DEFENDER', DEFENDER_ASSIGNED = 'DEFENDER_ASSIGNED', @@ -40,34 +44,40 @@ export enum InstitutionNotificationType { INDICTMENTS_WAITING_FOR_CONFIRMATION = 'INDICTMENTS_WAITING_FOR_CONFIRMATION', } +export enum EventNotificationType { + INDICTMENT_SENT_TO_PUBLIC_PROSECUTOR = 'INDICTMENT_SENT_TO_PUBLIC_PROSECUTOR', +} + export enum NotificationType { - HEADS_UP = CaseNotificationType.HEADS_UP, - READY_FOR_COURT = CaseNotificationType.READY_FOR_COURT, - RECEIVED_BY_COURT = CaseNotificationType.RECEIVED_BY_COURT, - COURT_DATE = CaseNotificationType.COURT_DATE, - RULING = CaseNotificationType.RULING, - MODIFIED = CaseNotificationType.MODIFIED, - REVOKED = CaseNotificationType.REVOKED, ADVOCATE_ASSIGNED = CaseNotificationType.ADVOCATE_ASSIGNED, - DEFENDANTS_NOT_UPDATED_AT_COURT = CaseNotificationType.DEFENDANTS_NOT_UPDATED_AT_COURT, - APPEAL_TO_COURT_OF_APPEALS = CaseNotificationType.APPEAL_TO_COURT_OF_APPEALS, - APPEAL_RECEIVED_BY_COURT = CaseNotificationType.APPEAL_RECEIVED_BY_COURT, - APPEAL_STATEMENT = CaseNotificationType.APPEAL_STATEMENT, + APPEAL_CASE_FILES_UPDATED = CaseNotificationType.APPEAL_CASE_FILES_UPDATED, APPEAL_COMPLETED = CaseNotificationType.APPEAL_COMPLETED, APPEAL_JUDGES_ASSIGNED = CaseNotificationType.APPEAL_JUDGES_ASSIGNED, - APPEAL_CASE_FILES_UPDATED = CaseNotificationType.APPEAL_CASE_FILES_UPDATED, + APPEAL_RECEIVED_BY_COURT = CaseNotificationType.APPEAL_RECEIVED_BY_COURT, + APPEAL_STATEMENT = CaseNotificationType.APPEAL_STATEMENT, + APPEAL_TO_COURT_OF_APPEALS = CaseNotificationType.APPEAL_TO_COURT_OF_APPEALS, APPEAL_WITHDRAWN = CaseNotificationType.APPEAL_WITHDRAWN, - INDICTMENT_DENIED = CaseNotificationType.INDICTMENT_DENIED, - INDICTMENT_RETURNED = CaseNotificationType.INDICTMENT_RETURNED, CASE_FILES_UPDATED = CaseNotificationType.CASE_FILES_UPDATED, + COURT_DATE = CaseNotificationType.COURT_DATE, DEFENDANT_SELECTED_DEFENDER = DefendantNotificationType.DEFENDANT_SELECTED_DEFENDER, + DEFENDANTS_NOT_UPDATED_AT_COURT = CaseNotificationType.DEFENDANTS_NOT_UPDATED_AT_COURT, DEFENDER_ASSIGNED = DefendantNotificationType.DEFENDER_ASSIGNED, + HEADS_UP = CaseNotificationType.HEADS_UP, + INDICTMENT_DENIED = CaseNotificationType.INDICTMENT_DENIED, + INDICTMENT_RETURNED = CaseNotificationType.INDICTMENT_RETURNED, INDICTMENT_SENT_TO_PRISON_ADMIN = DefendantNotificationType.INDICTMENT_SENT_TO_PRISON_ADMIN, + INDICTMENT_SENT_TO_PUBLIC_PROSECUTOR = EventNotificationType.INDICTMENT_SENT_TO_PUBLIC_PROSECUTOR, + INDICTMENT_VERDICT_INFO = IndictmentCaseNotificationType.INDICTMENT_VERDICT_INFO, INDICTMENT_WITHDRAWN_FROM_PRISON_ADMIN = DefendantNotificationType.INDICTMENT_WITHDRAWN_FROM_PRISON_ADMIN, - SERVICE_SUCCESSFUL = SubpoenaNotificationType.SERVICE_SUCCESSFUL, + INDICTMENTS_WAITING_FOR_CONFIRMATION = InstitutionNotificationType.INDICTMENTS_WAITING_FOR_CONFIRMATION, + MODIFIED = CaseNotificationType.MODIFIED, + READY_FOR_COURT = CaseNotificationType.READY_FOR_COURT, + RECEIVED_BY_COURT = CaseNotificationType.RECEIVED_BY_COURT, + REVOKED = CaseNotificationType.REVOKED, + RULING = CaseNotificationType.RULING, SERVICE_FAILED = SubpoenaNotificationType.SERVICE_FAILED, + SERVICE_SUCCESSFUL = SubpoenaNotificationType.SERVICE_SUCCESSFUL, SPOKESPERSON_ASSIGNED = CivilClaimantNotificationType.SPOKESPERSON_ASSIGNED, - INDICTMENTS_WAITING_FOR_CONFIRMATION = InstitutionNotificationType.INDICTMENTS_WAITING_FOR_CONFIRMATION, } export const notificationTypes = Object.values(NotificationType) diff --git a/libs/logging/src/lib/context.spec.ts b/libs/logging/src/lib/context.spec.ts index ca371ba27376..28a44634868c 100644 --- a/libs/logging/src/lib/context.spec.ts +++ b/libs/logging/src/lib/context.spec.ts @@ -1,3 +1,4 @@ +import { CodeOwners } from '@island.is/shared/constants' import { includeContextFormatter, withLoggingContext } from './context' describe('Winston context', () => { @@ -11,6 +12,50 @@ describe('Winston context', () => { process.env = originalEnv }) + it('should add default CODE_OWNER when environment variable is set', () => { + // Arrange + process.env.CODE_OWNER = 'default-team' + const formatter = includeContextFormatter() + const logInfo = { + level: 'info', + message: 'Test message', + } + + // Act + const formattedLog = formatter.transform(logInfo) + + // Assert + expect(formattedLog).toEqual({ + level: 'info', + message: 'Test message', + codeOwner: 'default-team', + }) + }) + + it('should override default CODE_OWNER with context codeOwner', () => { + // Arrange + process.env.CODE_OWNER = 'default-team' + const formatter = includeContextFormatter() + const logInfo = { + level: 'info', + message: 'Test message', + } + const context = { codeOwner: 'context-team' as CodeOwners } + + // Act + let formattedLog: unknown + withLoggingContext(context, () => { + formattedLog = formatter.transform(logInfo) + }) + + // Assert + expect(formattedLog).toEqual({ + level: 'info', + message: 'Test message', + codeOwner: 'context-team', + }) + }) + it('should add context to log info object', () => { // Arrange const formatter = includeContextFormatter() @@ -35,7 +80,7 @@ describe('Winston context', () => { }) }) - it('should not modify log info when no context exists', () => { + it('should not modify log info when no context or CODE_OWNER exists', () => { // Arrange const formatter = includeContextFormatter() const logInfo = { @@ -52,6 +97,7 @@ describe('Winston context', () => { it('should preserve existing log info properties when adding context', () => { // Arrange + process.env.CODE_OWNER = 'default-team' const formatter = includeContextFormatter() const logInfo = { level: 'info', @@ -71,6 +117,7 @@ describe('Winston context', () => { level: 'info', message: 'Test message', existingProp: 'should remain', + codeOwner: 'default-team', requestId: '123', }) }) diff --git a/libs/logging/src/lib/context.ts b/libs/logging/src/lib/context.ts index 03034f67d79d..c27e9652305d 100644 --- a/libs/logging/src/lib/context.ts +++ b/libs/logging/src/lib/context.ts @@ -19,8 +19,12 @@ export const withLoggingContext = <R, TArgs extends unknown[]>( } export const includeContextFormatter = format((info) => { + const defaultCodeOwner = process.env.CODE_OWNER const context = loggingContextStorage.getStore() + if (defaultCodeOwner) { + info.codeOwner = defaultCodeOwner + } if (context) { Object.assign(info, context) } diff --git a/libs/nest/aws/src/lib/s3.service.ts b/libs/nest/aws/src/lib/s3.service.ts index 0e534dcd33c1..12e5c1439e9e 100644 --- a/libs/nest/aws/src/lib/s3.service.ts +++ b/libs/nest/aws/src/lib/s3.service.ts @@ -63,7 +63,7 @@ export class S3Service { const input: CopyObjectRequest = { Bucket: bucket, Key: key, - CopySource: copySource, + CopySource: encodeURIComponent(copySource), } try { return await this.s3Client.send(new CopyObjectCommand(input)) diff --git a/libs/nest/core/src/lib/code-owner/code-owner.interceptor.spec.ts b/libs/nest/core/src/lib/code-owner/code-owner.interceptor.spec.ts index 038802b02237..54ffa4260584 100644 --- a/libs/nest/core/src/lib/code-owner/code-owner.interceptor.spec.ts +++ b/libs/nest/core/src/lib/code-owner/code-owner.interceptor.spec.ts @@ -1,4 +1,4 @@ -import { setCodeOwner } from '@island.is/infra-tracing' +import { withCodeOwner } from '@island.is/infra-tracing' import { CodeOwners } from '@island.is/shared/constants' import { Controller, Get, INestApplication } from '@nestjs/common' import { APP_INTERCEPTOR } from '@nestjs/core' @@ -9,7 +9,7 @@ import { CodeOwnerInterceptor } from './code-owner.interceptor' // Mock the logging module jest.mock('@island.is/infra-tracing', () => ({ - setCodeOwner: jest.fn(), + withCodeOwner: jest.fn((codeOwner, callback) => callback()), })) // Test controller with decorated endpoints @@ -50,26 +50,29 @@ describe('CodeOwnerInterceptor', () => { jest.clearAllMocks() }) - it('should call setCodeOwner when CodeOwner decorator is present', async () => { + it('should call withCodeOwner when CodeOwner decorator is present', async () => { // Make request to endpoint with CodeOwner decorator await request(app.getHttpServer()) .get('/test/with-owner') .expect(200) .expect({ message: 'with owner' }) - // Verify that setCodeOwner was called with correct parameters - expect(setCodeOwner).toHaveBeenCalledWith(CodeOwners.Core) + // Verify that withCodeOwner was called with correct parameters + expect(withCodeOwner).toHaveBeenCalledWith( + CodeOwners.Core, + expect.any(Function), + ) }) - it('should not call setCodeOwner when CodeOwner decorator is not present', async () => { + it('should not call withCodeOwner when CodeOwner decorator is not present', async () => { // Make request to endpoint without CodeOwner decorator await request(app.getHttpServer()) .get('/test/without-owner') .expect(200) .expect({ message: 'without owner' }) - // Verify that setCodeOwner was not called - expect(setCodeOwner).not.toHaveBeenCalled() + // Verify that withCodeOwner was not called + expect(withCodeOwner).not.toHaveBeenCalled() }) it('should handle multiple requests correctly', async () => { @@ -80,8 +83,33 @@ describe('CodeOwnerInterceptor', () => { request(app.getHttpServer()).get('/test/with-owner'), ]) - // Verify that setCodeOwner was called exactly twice (for the two 'with-owner' requests) - expect(setCodeOwner).toHaveBeenCalledTimes(2) - expect(setCodeOwner).toHaveBeenCalledWith(CodeOwners.Core) + // Verify that withCodeOwner was called exactly twice (for the two 'with-owner' requests) + expect(withCodeOwner).toHaveBeenCalledTimes(2) + expect(withCodeOwner).toHaveBeenCalledWith( + CodeOwners.Core, + expect.any(Function), + ) + }) + + it('should properly wrap and execute the handler', async () => { + // Arrange + let handlerExecuted = false + ;(withCodeOwner as jest.Mock).mockImplementation((codeOwner, callback) => { + handlerExecuted = true + return callback() + }) + + // Act + await request(app.getHttpServer()) + .get('/test/with-owner') + .expect(200) + .expect({ message: 'with owner' }) + + // Assert + expect(handlerExecuted).toBe(true) + expect(withCodeOwner).toHaveBeenCalledWith( + CodeOwners.Core, + expect.any(Function), + ) }) }) diff --git a/libs/nest/core/src/lib/code-owner/code-owner.interceptor.ts b/libs/nest/core/src/lib/code-owner/code-owner.interceptor.ts index a9f992fccbc6..89134e97552c 100644 --- a/libs/nest/core/src/lib/code-owner/code-owner.interceptor.ts +++ b/libs/nest/core/src/lib/code-owner/code-owner.interceptor.ts @@ -1,4 +1,4 @@ -import { setCodeOwner } from '@island.is/infra-tracing' +import { withCodeOwner } from '@island.is/infra-tracing' import { CodeOwners } from '@island.is/shared/constants' import { Injectable, @@ -19,9 +19,8 @@ export class CodeOwnerInterceptor implements NestInterceptor { CODE_OWNER_KEY, [context.getHandler(), context.getClass()], ) - if (codeOwner) { - setCodeOwner(codeOwner) + return withCodeOwner(codeOwner, () => next.handle()) } return next.handle() } diff --git a/libs/portals/my-pages/core/src/components/SortableTable/SortableTable.tsx b/libs/portals/my-pages/core/src/components/SortableTable/SortableTable.tsx index 1ae2abb073c9..3d9ad89bf3eb 100644 --- a/libs/portals/my-pages/core/src/components/SortableTable/SortableTable.tsx +++ b/libs/portals/my-pages/core/src/components/SortableTable/SortableTable.tsx @@ -1,13 +1,13 @@ -import React, { useMemo, useState } from 'react' import { - Text, - Table as T, Icon, - TagVariant, + Table as T, Tag, + TagVariant, + Text, } from '@island.is/island-ui/core' -import * as styles from './SortableTable.css' +import React, { useMemo, useState } from 'react' import { ExpandHeader, ExpandRow } from '../ExpandableTable' +import * as styles from './SortableTable.css' type ConfigType = { direction: 'ascending' | 'descending'; key: string } @@ -166,7 +166,7 @@ export const SortableTable = (props: SortableTableProps) => { data={valueItems.map((valueItem, i) => ({ value: valueItems.length - 1 === i && tag ? ( - <Tag variant={tag} outlined={props.tagOutlined}> + <Tag variant={tag} outlined={props.tagOutlined} disabled> {valueItem} </Tag> ) : ( @@ -187,7 +187,11 @@ export const SortableTable = (props: SortableTableProps) => { return ( <T.Data key={`body-${id}-${i}`}> {lastItem && tag ? ( - <Tag variant={tag} outlined={props.tagOutlined}> + <Tag + variant={tag} + outlined={props.tagOutlined} + disabled + > {valueItem} </Tag> ) : ( diff --git a/libs/portals/my-pages/health/src/screens/HealthOverview/HealthOverview.tsx b/libs/portals/my-pages/health/src/screens/HealthOverview/HealthOverview.tsx index 990a8e0e11e4..b7e5ae865dbe 100644 --- a/libs/portals/my-pages/health/src/screens/HealthOverview/HealthOverview.tsx +++ b/libs/portals/my-pages/health/src/screens/HealthOverview/HealthOverview.tsx @@ -1,20 +1,18 @@ -import { useUserInfo } from '@island.is/react-spa/bff' import { AlertMessage, Box, - Text, Button, GridColumn, GridRow, + Icon, SkeletonLoader, Stack, + Text, toast, - Icon, } from '@island.is/island-ui/core' import { useLocale, useNamespaces } from '@island.is/localization' -import { Problem } from '@island.is/react-spa/shared' import { - IntroHeader, + IntroWrapper, SJUKRATRYGGINGAR_SLUG, StackWithBottomDivider, UserInfoLine, @@ -24,6 +22,8 @@ import { isDateAfterToday, m, } from '@island.is/portals/my-pages/core' +import { useUserInfo } from '@island.is/react-spa/bff' +import { Problem } from '@island.is/react-spa/shared' import { useEffect, useState } from 'react' import { messages } from '../../lib/messages' import { HealthPaths } from '../../lib/paths' @@ -85,15 +85,13 @@ export const HealthOverview = () => { ) return ( - <Box> - <Box marginBottom={CONTENT_GAP_LG}> - <IntroHeader - title={formatMessage(user.profile.name)} - intro={formatMessage(messages.overviewIntro)} - serviceProviderSlug={SJUKRATRYGGINGAR_SLUG} - serviceProviderTooltip={formatMessage(messages.healthTooltip)} - /> - </Box> + <IntroWrapper + marginBottom={CONTENT_GAP_LG} + title={formatMessage(user.profile.name)} + intro={formatMessage(messages.overviewIntro)} + serviceProviderSlug={SJUKRATRYGGINGAR_SLUG} + serviceProviderTooltip={formatMessage(messages.healthTooltip)} + > {error ? ( <Problem error={error} noBorder={false} /> ) : loading ? ( @@ -209,7 +207,7 @@ export const HealthOverview = () => { )} </Stack> )} - </Box> + </IntroWrapper> ) } diff --git a/libs/portals/my-pages/health/src/screens/Vaccinations/VaccinationsWrapper.tsx b/libs/portals/my-pages/health/src/screens/Vaccinations/VaccinationsWrapper.tsx index 82aa962b2be4..67c57e727ff6 100644 --- a/libs/portals/my-pages/health/src/screens/Vaccinations/VaccinationsWrapper.tsx +++ b/libs/portals/my-pages/health/src/screens/Vaccinations/VaccinationsWrapper.tsx @@ -1,17 +1,17 @@ -import { useLocale, useNamespaces } from '@island.is/localization' import { Box, SkeletonLoader, Tabs } from '@island.is/island-ui/core' +import { useLocale, useNamespaces } from '@island.is/localization' import { + EmptyTable, HEALTH_DIRECTORATE_SLUG, - IntroHeader, + IntroWrapper, LinkButton, - EmptyTable, } from '@island.is/portals/my-pages/core' +import { Problem } from '@island.is/react-spa/shared' +import { isDefined } from '@island.is/shared/utils' import { messages as m } from '../../lib/messages' import { SECTION_GAP } from '../../utils/constants' import { useGetVaccinationsQuery } from './Vaccinations.generated' import { SortedVaccinationsTable } from './tables/SortedVaccinationsTable' -import { isDefined } from '@island.is/shared/utils' -import { Problem } from '@island.is/react-spa/shared' export const VaccinationsWrapper = () => { useNamespaces('sp.health') @@ -39,13 +39,12 @@ export const VaccinationsWrapper = () => { ].filter(isDefined) return ( - <Box> - <IntroHeader - title={formatMessage(m.vaccinations)} - intro={formatMessage(m.vaccinationsIntro)} - serviceProviderSlug={HEALTH_DIRECTORATE_SLUG} - serviceProviderTooltip={formatMessage(m.landlaeknirVaccinationsTooltip)} - /> + <IntroWrapper + title={formatMessage(m.vaccinations)} + intro={formatMessage(m.vaccinationsIntro)} + serviceProviderSlug={HEALTH_DIRECTORATE_SLUG} + serviceProviderTooltip={formatMessage(m.landlaeknirVaccinationsTooltip)} + > {/* Buttons */} <Box printHidden display="flex" flexDirection="row" marginBottom={6}> <LinkButton @@ -91,7 +90,7 @@ export const VaccinationsWrapper = () => { /> </Box> )} - </Box> + </IntroWrapper> ) } export default VaccinationsWrapper diff --git a/libs/portals/my-pages/health/src/screens/Vaccinations/tables/SortedVaccinationsTable.tsx b/libs/portals/my-pages/health/src/screens/Vaccinations/tables/SortedVaccinationsTable.tsx index b0918f667bdd..ad380413cff9 100644 --- a/libs/portals/my-pages/health/src/screens/Vaccinations/tables/SortedVaccinationsTable.tsx +++ b/libs/portals/my-pages/health/src/screens/Vaccinations/tables/SortedVaccinationsTable.tsx @@ -1,3 +1,5 @@ +import { HealthDirectorateVaccination } from '@island.is/api/schema' +import { Box } from '@island.is/island-ui/core' import { useLocale, useNamespaces } from '@island.is/localization' import { EmptyTable, @@ -6,10 +8,8 @@ import { } from '@island.is/portals/my-pages/core' import { messages } from '../../../lib/messages' import { tagSelector } from '../../../utils/tagSelector' -import { VaccinationsDetailTable } from './VaccinationsDetailTable' import { DetailHeader, DetailRow } from '../../../utils/types' -import { HealthDirectorateVaccination } from '@island.is/api/schema' -import { Box } from '@island.is/island-ui/core' +import { VaccinationsDetailTable } from './VaccinationsDetailTable' interface Props { data?: Array<HealthDirectorateVaccination> @@ -48,7 +48,7 @@ export const SortedVaccinationsTable = ({ data }: Props) => { }} tagOutlined expandable - defaultSortByKey="vaccine" + defaultSortByKey="status" items={ data.map((item, i) => ({ id: item?.id ?? `${i}`, diff --git a/libs/portals/my-pages/social-insurance-maintenance/src/screens/IncomePlanDetail/IncomePlanDetail.tsx b/libs/portals/my-pages/social-insurance-maintenance/src/screens/IncomePlanDetail/IncomePlanDetail.tsx index f5728ec8799d..b96073146403 100644 --- a/libs/portals/my-pages/social-insurance-maintenance/src/screens/IncomePlanDetail/IncomePlanDetail.tsx +++ b/libs/portals/my-pages/social-insurance-maintenance/src/screens/IncomePlanDetail/IncomePlanDetail.tsx @@ -1,5 +1,4 @@ import { - Box, Inline, Stack, Button, @@ -9,7 +8,6 @@ import { } from '@island.is/island-ui/core' import { useLocale, useNamespaces } from '@island.is/localization' import { - EmptyTable, FootNote, IntroHeader, IntroWrapper, @@ -18,14 +16,60 @@ import { m as coreMessages, } from '@island.is/portals/my-pages/core' import { m } from '../../lib/messages' -import { useGetIncomePlanDetailQuery } from './IncomePlanDetail.generated' +import { + useGetIncomePlanDetailLazyQuery, + useGetIncomePlanDetailQuery, +} from './IncomePlanDetail.generated' import { Problem } from '@island.is/react-spa/shared' +import { useEffect, useState } from 'react' +import { useFeatureFlagClient } from '@island.is/react/feature-flags' const IncomePlanDetail = () => { useNamespaces('sp.social-insurance-maintenance') const { formatMessage } = useLocale() - const { data, loading, error } = useGetIncomePlanDetailQuery() + const [displayPaymentPlan, setDisplayPaymentPlan] = useState(false) + const [query, { data, loading, error }] = useGetIncomePlanDetailLazyQuery() + + const featureFlagClient = useFeatureFlagClient() + useEffect(() => { + const isFlagEnabled = async () => { + const ffEnabled = await featureFlagClient.getValue( + `isServicePortalPaymentPlan2025Enabled`, + false, + ) + if (ffEnabled) { + setDisplayPaymentPlan(ffEnabled as boolean) + } + } + isFlagEnabled() + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) + + useEffect(() => { + if (displayPaymentPlan) { + query() + } + }, [displayPaymentPlan]) + + if (!displayPaymentPlan) { + return ( + <IntroWrapper + title={formatMessage(coreMessages.latestIncomePlan)} + intro={formatMessage(m.incomePlanDetail)} + serviceProviderSlug={'tryggingastofnun'} + serviceProviderTooltip={formatMessage( + coreMessages.socialInsuranceTooltip, + )} + > + <AlertMessage + type="warning" + message={formatMessage(m.incomePlanTemporarilyUnavailable)} + /> + </IntroWrapper> + ) + } + return ( <IntroWrapper title={formatMessage(coreMessages.latestIncomePlan)} diff --git a/libs/react-spa/bff/src/lib/BffPoller.tsx b/libs/react-spa/bff/src/lib/BffPoller.tsx index 8374bbcdc5b0..974098093ea9 100644 --- a/libs/react-spa/bff/src/lib/BffPoller.tsx +++ b/libs/react-spa/bff/src/lib/BffPoller.tsx @@ -3,7 +3,7 @@ import { BffUser } from '@island.is/shared/types' import { ReactNode, useCallback, useEffect, useMemo } from 'react' import { BffBroadcastEvents, - useBff, + useAuth, useBffBroadcaster, useUserInfo, } from './bff.hooks' @@ -43,7 +43,7 @@ export const BffPoller = ({ newSessionCb, pollIntervalMS = 10000, }: BffPollerProps) => { - const { signIn, bffUrlGenerator } = useBff() + const { signIn, bffUrlGenerator } = useAuth() const userInfo = useUserInfo() const { postMessage } = useBffBroadcaster() diff --git a/libs/react-spa/bff/src/lib/BffProvider.tsx b/libs/react-spa/bff/src/lib/BffProvider.tsx index 7ca9502c62ff..f10534fc550e 100644 --- a/libs/react-spa/bff/src/lib/BffProvider.tsx +++ b/libs/react-spa/bff/src/lib/BffProvider.tsx @@ -42,6 +42,7 @@ export const BffProvider = ({ authState === 'switching' || authState === 'logging-out' const isLoggedIn = authState === 'logged-in' + const oldLoginPath = `${applicationBasePath}/login` const { postMessage } = useBffBroadcaster((event) => { if ( @@ -74,6 +75,39 @@ export const BffProvider = ({ } }, [postMessage, state.userInfo, isLoggedIn]) + /** + * Builds authentication query parameters for login redirection: + * - target_link_uri: Destination URL after successful login + * • Uses URL from query string if provided + * • Falls back to current URL, with '/login' stripped if on legacy login path + * - prompt: Optional authentication prompt type + * - login_hint: Optional suggested account identifier + */ + const getLoginQueryParams = useCallback(() => { + const urlParams = new URLSearchParams(window.location.search) + const targetLinkUri = urlParams.get('target_link_uri') + const prompt = urlParams.get('prompt') + const loginHint = urlParams.get('login_hint') + const url = window.location.href + + const params = { + target_link_uri: + targetLinkUri ?? + (window.location.pathname.startsWith(oldLoginPath) + ? // Remove `/login` from the path to prevent redirect loop + url.replace(oldLoginPath, applicationBasePath) + : url), + ...(prompt && { + prompt, + }), + ...(loginHint && { + login_hint: loginHint, + }), + } + + return params + }, [applicationBasePath, oldLoginPath]) + const checkLogin = async (noRefresh = false) => { dispatch({ type: ActionType.SIGNIN_START, @@ -100,6 +134,13 @@ export const BffProvider = ({ return } + // If user is logged in and on the old login path, then start the sign-in process + if (window.location.pathname.startsWith(oldLoginPath)) { + signIn() + + return + } + const user = await res.json() dispatch({ @@ -119,10 +160,8 @@ export const BffProvider = ({ type: ActionType.SIGNIN_START, }) - window.location.href = bffUrlGenerator('/login', { - target_link_uri: window.location.href, - }) - }, [bffUrlGenerator]) + window.location.href = bffUrlGenerator('/login', getLoginQueryParams()) + }, [bffUrlGenerator, getLoginQueryParams]) const signOut = useCallback(() => { if (!state.userInfo) { @@ -149,14 +188,17 @@ export const BffProvider = ({ type: ActionType.SWITCH_USER, }) + const loginQueryParams = getLoginQueryParams() + const targetLinkUri = loginQueryParams['target_link_uri'] + window.location.href = bffUrlGenerator('/login', { - target_link_uri: window.location.href, + target_link_uri: targetLinkUri, ...(nationalId ? { login_hint: nationalId } : { prompt: 'select_account' }), }) }, - [bffUrlGenerator], + [bffUrlGenerator, getLoginQueryParams], ) const checkQueryStringError = () => { diff --git a/libs/react-spa/bff/src/lib/bff.hooks.ts b/libs/react-spa/bff/src/lib/bff.hooks.ts index b768ad209ba7..72d4b52a6805 100644 --- a/libs/react-spa/bff/src/lib/bff.hooks.ts +++ b/libs/react-spa/bff/src/lib/bff.hooks.ts @@ -8,11 +8,11 @@ import { BffContext, BffContextType } from './BffContext' /** * This hook is used to get the BFF authentication context. */ -export const useBff = () => { +export const useAuth = () => { const bffContext = useContext(BffContext) if (!bffContext) { - throw new Error('useBff must be used within a BffProvider') + throw new Error('useAuth must be used within a BffProvider') } return bffContext @@ -24,7 +24,7 @@ export const useBff = () => { export const useDynamicBffHook = <Key extends keyof BffContextType>( returnField: Key, ): NonNullable<BffContextType[Key]> => { - const bffContext = useBff() + const bffContext = useAuth() if (!isDefined(bffContext[returnField])) { throw new Error(`The field ${returnField} does not exist in the BffContext`) diff --git a/libs/shared/components/src/auth/UserMenu/UserDelegations.tsx b/libs/shared/components/src/auth/UserMenu/UserDelegations.tsx index 707a82fa022f..b4db29d4d10f 100644 --- a/libs/shared/components/src/auth/UserMenu/UserDelegations.tsx +++ b/libs/shared/components/src/auth/UserMenu/UserDelegations.tsx @@ -1,6 +1,6 @@ import { Box, Stack } from '@island.is/island-ui/core' import { useLocale } from '@island.is/localization' -import { useBff, useUserInfo } from '@island.is/react-spa/bff' +import { useAuth, useUserInfo } from '@island.is/react-spa/bff' import { userMessages } from '@island.is/shared/translations' import { UserDropdownItem } from './UserDropdownItem' import { UserTopicCard } from './UserTopicCard' @@ -16,7 +16,7 @@ export const UserDelegations = ({ }: UserDelegationsProps) => { const user = useUserInfo() const { formatMessage } = useLocale() - const { switchUser } = useBff() + const { switchUser } = useAuth() const actor = user.profile.actor return ( diff --git a/libs/shared/components/src/auth/UserMenu/UserMenu.tsx b/libs/shared/components/src/auth/UserMenu/UserMenu.tsx index a2ffae0c76fb..875b16169a25 100644 --- a/libs/shared/components/src/auth/UserMenu/UserMenu.tsx +++ b/libs/shared/components/src/auth/UserMenu/UserMenu.tsx @@ -1,5 +1,5 @@ import { Box, Hidden } from '@island.is/island-ui/core' -import { useBff } from '@island.is/react-spa/bff' +import { useAuth } from '@island.is/react-spa/bff' import { useEffect, useState } from 'react' import { UserButton } from './UserButton' import { UserDropdown } from './UserDropdown' @@ -29,7 +29,7 @@ export const UserMenu = ({ const [dropdownState, setDropdownState] = useState<'closed' | 'open'>( 'closed', ) - const { signOut, switchUser, userInfo: user } = useBff() + const { signOut, switchUser, userInfo: user } = useAuth() const handleClick = () => { setDropdownState(dropdownState === 'open' ? 'closed' : 'open') diff --git a/package.json b/package.json index e78c508c8747..b24e96893b4d 100644 --- a/package.json +++ b/package.json @@ -228,7 +228,7 @@ "next": "14.2.3", "next-auth": "3.29.10", "next-cookies": "2.0.3", - "next-usequerystate": "1.8.4", + "next-usequerystate": "1.20.0", "node-fetch": "2.6.7", "node-gyp": "9.1.0", "node-html-markdown": "1.3.0", @@ -262,6 +262,7 @@ "react-is": "18.3.1", "react-keyed-flatten-children": "1.2.0", "react-modal": "3.15.1", + "react-native": "0.74.5", "react-number-format": "4.9.1", "react-pdf": "9.1.0", "react-popper": "2.3.0", @@ -346,6 +347,7 @@ "@nestjs/schematics": "10.0.1", "@nestjs/testing": "10.0.5", "@nx/cypress": "19.4.0", + "@nx/devkit": "19.4.0", "@nx/eslint": "19.4.0", "@nx/eslint-plugin": "19.4.0", "@nx/express": "19.4.0", @@ -356,12 +358,16 @@ "@nx/node": "19.4.0", "@nx/playwright": "19.4.0", "@nx/react": "19.4.0", + "@nx/react-native": "19.4.0", "@nx/storybook": "19.4.0", "@nx/web": "19.4.0", "@nx/webpack": "19.4.0", "@nx/workspace": "19.4.0", "@openapitools/openapi-generator-cli": "1.0.15-4.3.1", "@playwright/test": "1.48", + "@react-native-community/cli-platform-android": "~13.6.6", + "@react-native/babel-preset": "0.74.87", + "@react-native/metro-config": "0.74.87", "@storybook/addon-a11y": "7.6.9", "@storybook/addon-essentials": "7.6.9", "@storybook/addon-mdx-gfm": "7.6.9", @@ -375,7 +381,9 @@ "@swc/helpers": "0.5.11", "@testing-library/cypress": "8.0.3", "@testing-library/jest-dom": "5.16.5", + "@testing-library/jest-native": "5.4.3", "@testing-library/react": "15.0.6", + "@testing-library/react-native": "12.9.0", "@testing-library/user-event": "14.4.3", "@types/aws-sdk": "2.7.0", "@types/aws4": "1.5.1", @@ -454,6 +462,7 @@ "jest-environment-jsdom": "29.7.0", "jest-environment-node": "29.7.0", "jest-mock-extended": "3.0.5", + "jest-react-native": "18.0.0", "jest-transform-stub": "2.0.0", "license-checker": "25.0.1", "mailparser": "3.5.0", @@ -461,6 +470,10 @@ "nodemailer-mock": "2.0.6", "nx": "19.4.0", "prettier": "2.6.2", + "react-native-svg": "15.2.0", + "react-native-svg-transformer": "1.3.0", + "react-native-web": "^0.19.11", + "react-test-renderer": "18.2.0", "sequelize-cli": "6.4.1", "sort-paths": "1.1.1", "sqlite3": "5.1.6", @@ -498,7 +511,6 @@ "node-request-interceptor@^0.5.1": "patch:node-request-interceptor@npm%3A0.5.9#./.yarn/patches/node-request-interceptor-npm-0.5.9-77e9d9c058.patch", "dd-trace@5.10.0": "patch:dd-trace@npm%3A5.10.0#./.yarn/patches/dd-trace-npm-5.10.0-184ed36e96.patch", "expo-modules-core@1.1.1": "patch:expo-modules-core@npm%3A1.1.1#./.yarn/patches/expo-modules-core-npm-1.1.1-c3861d47cb.patch", - "react-native@0.71.1": "patch:react-native@npm%3A0.71.1#./.yarn/patches/react-native-npm-0.71.1-f5d237f240.patch", "dd-trace@5.14.1": "patch:dd-trace@npm%3A5.14.1#./.yarn/patches/dd-trace-npm-5.14.1-8d45ad14d6.patch", "react-native-navigation@7.40.0": "patch:react-native-navigation@npm%3A7.40.0#./.yarn/patches/react-native-navigation-npm-7.40.0-68d0a0ab0d.patch", "expo-modules-core@1.12.15": "patch:expo-modules-core@npm%3A1.12.15#./.yarn/patches/expo-modules-core-npm-1.12.15-fdd209ec99.patch" diff --git a/tsconfig.base.json b/tsconfig.base.json index 3cab159e69be..7be0f1c28845 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -574,7 +574,6 @@ "@island.is/application/utils": ["libs/application/utils/src/index.ts"], "@island.is/auth-api-lib": ["libs/auth-api-lib/src/index.ts"], "@island.is/auth-nest-tools": ["libs/auth-nest-tools/src/index.ts"], - "@island.is/auth/react": ["libs/auth/react/src/index.ts"], "@island.is/auth/scopes": ["libs/auth/scopes/src/index.ts"], "@island.is/auth/shared": ["libs/auth/shared/src/index.ts"], "@island.is/cache": ["libs/cache/src/index.ts"], @@ -902,7 +901,6 @@ "@island.is/infra-nest-server": ["libs/infra-nest-server/src/index.ts"], "@island.is/infra-next-server": ["libs/infra-next-server/src/index.ts"], "@island.is/infra-tracing": ["libs/infra-tracing/src/index.ts"], - "@island.is/island-ui-native": ["apps/native/island-ui/src/index.ts"], "@island.is/island-ui/contentful": [ "libs/island-ui/contentful/src/index.ts" ], diff --git a/yarn.lock b/yarn.lock index dc4f63250639..e0441eb6d923 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2389,6 +2389,17 @@ __metadata: languageName: node linkType: hard +"@babel/code-frame@npm:^7.25.9, @babel/code-frame@npm:^7.26.0": + version: 7.26.2 + resolution: "@babel/code-frame@npm:7.26.2" + dependencies: + "@babel/helper-validator-identifier": ^7.25.9 + js-tokens: ^4.0.0 + picocolors: ^1.0.0 + checksum: db13f5c42d54b76c1480916485e6900748bbcb0014a8aca87f50a091f70ff4e0d0a6db63cade75eb41fcc3d2b6ba0a7f89e343def4f96f00269b41b8ab8dd7b8 + languageName: node + linkType: hard + "@babel/compat-data@npm:^7.17.10, @babel/compat-data@npm:^7.19.3": version: 7.19.4 resolution: "@babel/compat-data@npm:7.19.4" @@ -2438,6 +2449,13 @@ __metadata: languageName: node linkType: hard +"@babel/compat-data@npm:^7.25.9": + version: 7.26.2 + resolution: "@babel/compat-data@npm:7.26.2" + checksum: d52fae9b0dc59b409d6005ae6b172e89329f46d68136130065ebe923a156fc633e0f1c8600b3e319b9e0f99fd948f64991a5419e2e9431d00d9d235d5f7a7618 + languageName: node + linkType: hard + "@babel/core@npm:7.22.5": version: 7.22.5 resolution: "@babel/core@npm:7.22.5" @@ -2576,6 +2594,29 @@ __metadata: languageName: node linkType: hard +"@babel/core@npm:^7.25.2": + version: 7.26.0 + resolution: "@babel/core@npm:7.26.0" + dependencies: + "@ampproject/remapping": ^2.2.0 + "@babel/code-frame": ^7.26.0 + "@babel/generator": ^7.26.0 + "@babel/helper-compilation-targets": ^7.25.9 + "@babel/helper-module-transforms": ^7.26.0 + "@babel/helpers": ^7.26.0 + "@babel/parser": ^7.26.0 + "@babel/template": ^7.25.9 + "@babel/traverse": ^7.25.9 + "@babel/types": ^7.26.0 + convert-source-map: ^2.0.0 + debug: ^4.1.0 + gensync: ^1.0.0-beta.2 + json5: ^2.2.3 + semver: ^6.3.1 + checksum: b296084cfd818bed8079526af93b5dfa0ba70282532d2132caf71d4060ab190ba26d3184832a45accd82c3c54016985a4109ab9118674347a7e5e9bc464894e6 + languageName: node + linkType: hard + "@babel/eslint-parser@npm:^7.20.0": version: 7.24.7 resolution: "@babel/eslint-parser@npm:7.24.7" @@ -2673,6 +2714,19 @@ __metadata: languageName: node linkType: hard +"@babel/generator@npm:^7.25.9, @babel/generator@npm:^7.26.0": + version: 7.26.2 + resolution: "@babel/generator@npm:7.26.2" + dependencies: + "@babel/parser": ^7.26.2 + "@babel/types": ^7.26.0 + "@jridgewell/gen-mapping": ^0.3.5 + "@jridgewell/trace-mapping": ^0.3.25 + jsesc: ^3.0.2 + checksum: 6ff850b7d6082619f8c2f518d993cf7254cfbaa20b026282cbef5c9b2197686d076a432b18e36c4d1a42721c016df4f77a8f62c67600775d9683621d534b91b4 + languageName: node + linkType: hard + "@babel/helper-annotate-as-pure@npm:^7.16.0, @babel/helper-annotate-as-pure@npm:^7.16.7, @babel/helper-annotate-as-pure@npm:^7.18.6": version: 7.18.6 resolution: "@babel/helper-annotate-as-pure@npm:7.18.6" @@ -2812,6 +2866,19 @@ __metadata: languageName: node linkType: hard +"@babel/helper-compilation-targets@npm:^7.25.9": + version: 7.25.9 + resolution: "@babel/helper-compilation-targets@npm:7.25.9" + dependencies: + "@babel/compat-data": ^7.25.9 + "@babel/helper-validator-option": ^7.25.9 + browserslist: ^4.24.0 + lru-cache: ^5.1.1 + semver: ^6.3.1 + checksum: 3af536e2db358b38f968abdf7d512d425d1018fef2f485d6f131a57a7bcaed32c606b4e148bb230e1508fa42b5b2ac281855a68eb78270f54698c48a83201b9b + languageName: node + linkType: hard + "@babel/helper-create-class-features-plugin@npm:^7.18.6, @babel/helper-create-class-features-plugin@npm:^7.21.0": version: 7.21.4 resolution: "@babel/helper-create-class-features-plugin@npm:7.21.4" @@ -3228,6 +3295,16 @@ __metadata: languageName: node linkType: hard +"@babel/helper-module-imports@npm:^7.25.9": + version: 7.25.9 + resolution: "@babel/helper-module-imports@npm:7.25.9" + dependencies: + "@babel/traverse": ^7.25.9 + "@babel/types": ^7.25.9 + checksum: 1b411ce4ca825422ef7065dffae7d8acef52023e51ad096351e3e2c05837e9bf9fca2af9ca7f28dc26d596a588863d0fedd40711a88e350b736c619a80e704e6 + languageName: node + linkType: hard + "@babel/helper-module-transforms@npm:^7.18.0, @babel/helper-module-transforms@npm:^7.19.0": version: 7.19.0 resolution: "@babel/helper-module-transforms@npm:7.19.0" @@ -3335,6 +3412,19 @@ __metadata: languageName: node linkType: hard +"@babel/helper-module-transforms@npm:^7.26.0": + version: 7.26.0 + resolution: "@babel/helper-module-transforms@npm:7.26.0" + dependencies: + "@babel/helper-module-imports": ^7.25.9 + "@babel/helper-validator-identifier": ^7.25.9 + "@babel/traverse": ^7.25.9 + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 942eee3adf2b387443c247a2c190c17c4fd45ba92a23087abab4c804f40541790d51ad5277e4b5b1ed8d5ba5b62de73857446b7742f835c18ebd350384e63917 + languageName: node + linkType: hard + "@babel/helper-optimise-call-expression@npm:^7.16.7, @babel/helper-optimise-call-expression@npm:^7.18.6": version: 7.18.6 resolution: "@babel/helper-optimise-call-expression@npm:7.18.6" @@ -3682,6 +3772,13 @@ __metadata: languageName: node linkType: hard +"@babel/helper-string-parser@npm:^7.25.9": + version: 7.25.9 + resolution: "@babel/helper-string-parser@npm:7.25.9" + checksum: 6435ee0849e101681c1849868278b5aee82686ba2c1e27280e5e8aca6233af6810d39f8e4e693d2f2a44a3728a6ccfd66f72d71826a94105b86b731697cdfa99 + languageName: node + linkType: hard + "@babel/helper-validator-identifier@npm:^7.18.6, @babel/helper-validator-identifier@npm:^7.19.1": version: 7.19.1 resolution: "@babel/helper-validator-identifier@npm:7.19.1" @@ -3710,6 +3807,13 @@ __metadata: languageName: node linkType: hard +"@babel/helper-validator-identifier@npm:^7.25.9": + version: 7.25.9 + resolution: "@babel/helper-validator-identifier@npm:7.25.9" + checksum: 5b85918cb1a92a7f3f508ea02699e8d2422fe17ea8e82acd445006c0ef7520fbf48e3dbcdaf7b0a1d571fc3a2715a29719e5226636cb6042e15fe6ed2a590944 + languageName: node + linkType: hard + "@babel/helper-validator-option@npm:^7.18.6": version: 7.18.6 resolution: "@babel/helper-validator-option@npm:7.18.6" @@ -3745,6 +3849,13 @@ __metadata: languageName: node linkType: hard +"@babel/helper-validator-option@npm:^7.25.9": + version: 7.25.9 + resolution: "@babel/helper-validator-option@npm:7.25.9" + checksum: 9491b2755948ebbdd68f87da907283698e663b5af2d2b1b02a2765761974b1120d5d8d49e9175b167f16f72748ffceec8c9cf62acfbee73f4904507b246e2b3d + languageName: node + linkType: hard + "@babel/helper-wrap-function@npm:^7.18.9": version: 7.20.5 resolution: "@babel/helper-wrap-function@npm:7.20.5" @@ -3856,6 +3967,16 @@ __metadata: languageName: node linkType: hard +"@babel/helpers@npm:^7.26.0": + version: 7.26.0 + resolution: "@babel/helpers@npm:7.26.0" + dependencies: + "@babel/template": ^7.25.9 + "@babel/types": ^7.26.0 + checksum: d77fe8d45033d6007eadfa440355c1355eed57902d5a302f450827ad3d530343430a21210584d32eef2f216ae463d4591184c6fc60cf205bbf3a884561469200 + languageName: node + linkType: hard + "@babel/highlight@npm:^7.10.4, @babel/highlight@npm:^7.18.6": version: 7.18.6 resolution: "@babel/highlight@npm:7.18.6" @@ -3955,6 +4076,17 @@ __metadata: languageName: node linkType: hard +"@babel/parser@npm:^7.25.9, @babel/parser@npm:^7.26.0, @babel/parser@npm:^7.26.2": + version: 7.26.2 + resolution: "@babel/parser@npm:7.26.2" + dependencies: + "@babel/types": ^7.26.0 + bin: + parser: ./bin/babel-parser.js + checksum: c88b5ea0adf357ef909cdc2c31e284a154943edc59f63f6e8a4c20bf773a1b2f3d8c2205e59c09ca7cdad91e7466300114548876529277a80651b6436a48d5d9 + languageName: node + linkType: hard + "@babel/parser@npm:^7.9.4": version: 7.20.13 resolution: "@babel/parser@npm:7.20.13" @@ -8001,6 +8133,15 @@ __metadata: languageName: node linkType: hard +"@babel/runtime@npm:^7.25.0": + version: 7.26.0 + resolution: "@babel/runtime@npm:7.26.0" + dependencies: + regenerator-runtime: ^0.14.0 + checksum: c8e2c0504ab271b3467a261a8f119bf2603eb857a0d71e37791f4e3fae00f681365073cc79f141ddaa90c6077c60ba56448004ad5429d07ac73532be9f7cf28a + languageName: node + linkType: hard + "@babel/template@npm:^7.0.0, @babel/template@npm:^7.20.7": version: 7.20.7 resolution: "@babel/template@npm:7.20.7" @@ -8067,6 +8208,32 @@ __metadata: languageName: node linkType: hard +"@babel/template@npm:^7.25.9": + version: 7.25.9 + resolution: "@babel/template@npm:7.25.9" + dependencies: + "@babel/code-frame": ^7.25.9 + "@babel/parser": ^7.25.9 + "@babel/types": ^7.25.9 + checksum: 103641fea19c7f4e82dc913aa6b6ac157112a96d7c724d513288f538b84bae04fb87b1f1e495ac1736367b1bc30e10f058b30208fb25f66038e1f1eb4e426472 + languageName: node + linkType: hard + +"@babel/traverse--for-generate-function-map@npm:@babel/traverse@^7.25.3, @babel/traverse@npm:^7.18.9, @babel/traverse@npm:^7.23.2, @babel/traverse@npm:^7.24.8, @babel/traverse@npm:^7.25.0, @babel/traverse@npm:^7.25.1, @babel/traverse@npm:^7.25.2, @babel/traverse@npm:^7.25.3": + version: 7.25.3 + resolution: "@babel/traverse@npm:7.25.3" + dependencies: + "@babel/code-frame": ^7.24.7 + "@babel/generator": ^7.25.0 + "@babel/parser": ^7.25.3 + "@babel/template": ^7.25.0 + "@babel/types": ^7.25.2 + debug: ^4.3.1 + globals: ^11.1.0 + checksum: 5661308b1357816f1d4e2813a5dd82c6053617acc08c5c95db051b8b6577d07c4446bc861c9a5e8bf294953ac8266ae13d7d9d856b6b889fc0d34c1f51abbd8c + languageName: node + linkType: hard + "@babel/traverse@npm:7, @babel/traverse@npm:^7.20.0, @babel/traverse@npm:^7.20.5, @babel/traverse@npm:^7.20.7, @babel/traverse@npm:^7.21.0, @babel/traverse@npm:^7.21.2, @babel/traverse@npm:^7.21.4": version: 7.21.4 resolution: "@babel/traverse@npm:7.21.4" @@ -8103,21 +8270,6 @@ __metadata: languageName: node linkType: hard -"@babel/traverse@npm:^7.18.9, @babel/traverse@npm:^7.23.2, @babel/traverse@npm:^7.24.8, @babel/traverse@npm:^7.25.0, @babel/traverse@npm:^7.25.1, @babel/traverse@npm:^7.25.2, @babel/traverse@npm:^7.25.3": - version: 7.25.3 - resolution: "@babel/traverse@npm:7.25.3" - dependencies: - "@babel/code-frame": ^7.24.7 - "@babel/generator": ^7.25.0 - "@babel/parser": ^7.25.3 - "@babel/template": ^7.25.0 - "@babel/types": ^7.25.2 - debug: ^4.3.1 - globals: ^11.1.0 - checksum: 5661308b1357816f1d4e2813a5dd82c6053617acc08c5c95db051b8b6577d07c4446bc861c9a5e8bf294953ac8266ae13d7d9d856b6b889fc0d34c1f51abbd8c - languageName: node - linkType: hard - "@babel/traverse@npm:^7.22.1": version: 7.22.4 resolution: "@babel/traverse@npm:7.22.4" @@ -8190,6 +8342,21 @@ __metadata: languageName: node linkType: hard +"@babel/traverse@npm:^7.25.9": + version: 7.25.9 + resolution: "@babel/traverse@npm:7.25.9" + dependencies: + "@babel/code-frame": ^7.25.9 + "@babel/generator": ^7.25.9 + "@babel/parser": ^7.25.9 + "@babel/template": ^7.25.9 + "@babel/types": ^7.25.9 + debug: ^4.3.1 + globals: ^11.1.0 + checksum: 901d325662ff1dd9bc51de00862e01055fa6bc374f5297d7e3731f2f0e268bbb1d2141f53fa82860aa308ee44afdcf186a948f16c83153927925804b95a9594d + languageName: node + linkType: hard + "@babel/types@npm:^7.0.0, @babel/types@npm:^7.12.11, @babel/types@npm:^7.16.0, @babel/types@npm:^7.16.8, @babel/types@npm:^7.18.10, @babel/types@npm:^7.18.6, @babel/types@npm:^7.18.9, @babel/types@npm:^7.19.0, @babel/types@npm:^7.19.3, @babel/types@npm:^7.19.4, @babel/types@npm:^7.3.0, @babel/types@npm:^7.3.3, @babel/types@npm:^7.4, @babel/types@npm:^7.4.4, @babel/types@npm:^7.8.3": version: 7.19.4 resolution: "@babel/types@npm:7.19.4" @@ -8267,6 +8434,16 @@ __metadata: languageName: node linkType: hard +"@babel/types@npm:^7.25.9, @babel/types@npm:^7.26.0": + version: 7.26.0 + resolution: "@babel/types@npm:7.26.0" + dependencies: + "@babel/helper-string-parser": ^7.25.9 + "@babel/helper-validator-identifier": ^7.25.9 + checksum: a3dd37dabac693018872da96edb8c1843a605c1bfacde6c3f504fba79b972426a6f24df70aa646356c0c1b19bdd2c722c623c684a996c002381071680602280d + languageName: node + linkType: hard + "@base2/pretty-print-object@npm:1.0.1": version: 1.0.1 resolution: "@base2/pretty-print-object@npm:1.0.1" @@ -13296,6 +13473,8 @@ __metadata: "@react-native/eslint-config": 0.74.87 "@react-native/metro-config": 0.74.87 "@react-native/typescript-config": 0.74.87 + "@testing-library/jest-native": 5.4.3 + "@testing-library/react-native": 12.9.0 "@tsconfig/react-native": 3.0.5 "@types/intl": ^1 "@types/jest": ^29.2.1 @@ -13306,7 +13485,6 @@ __metadata: babel-jest: ^29.6.3 babel-loader: ^8.3.0 babel-plugin-formatjs: 10.3.9 - babel-plugin-module-resolver: 5.0.2 compare-versions: 6.1.1 configcat-js: 7.0.0 dynamic-color: 0.3.0 @@ -13319,8 +13497,9 @@ __metadata: intl: 1.2.5 jest: 29.7.0 lodash: 4.17.21 + metro-config: 0.81.0 path-to-regexp: 6.2.2 - react: 18.2.0 + react: 18.3.1 react-intl: 5.20.12 react-native: 0.74.5 react-native-app-auth: 7.2.0 @@ -15144,6 +15323,15 @@ __metadata: languageName: node linkType: hard +"@nrwl/react-native@npm:19.4.0": + version: 19.4.0 + resolution: "@nrwl/react-native@npm:19.4.0" + dependencies: + "@nx/react-native": 19.4.0 + checksum: c107fcb4b618d6cd055dbebfab93b2d63429d9a4d417d5337746184245180f2136b98dba36b2a9d6b03969aac71f8b0abb689c5321ef6044c6e007e1f415fa48 + languageName: node + linkType: hard + "@nrwl/react@npm:19.4.0": version: 19.4.0 resolution: "@nrwl/react@npm:19.4.0" @@ -15603,6 +15791,32 @@ __metadata: languageName: node linkType: hard +"@nx/react-native@npm:19.4.0": + version: 19.4.0 + resolution: "@nx/react-native@npm:19.4.0" + dependencies: + "@nrwl/react-native": 19.4.0 + "@nx/devkit": 19.4.0 + "@nx/eslint": 19.4.0 + "@nx/jest": 19.4.0 + "@nx/js": 19.4.0 + "@nx/react": 19.4.0 + "@nx/workspace": 19.4.0 + ajv: ^8.12.0 + chalk: ^4.1.0 + enhanced-resolve: ^5.8.3 + fs-extra: ^11.1.0 + glob: 7.1.4 + ignore: ^5.0.4 + metro-config: ~0.80.4 + metro-resolver: ~0.80.4 + node-fetch: ^2.6.7 + tsconfig-paths: ^4.1.2 + tslib: ^2.3.0 + checksum: 1b6834ff4997932a131b28bfbc898022f00146799291f5d1a1d24b7d9ed0dac8fe79094e4e8a936b6e9a1cd430a8d2bf02acec5ccec2a49c33921ab864994f23 + languageName: node + linkType: hard + "@nx/react@npm:19.4.0": version: 19.4.0 resolution: "@nx/react@npm:19.4.0" @@ -16998,7 +17212,7 @@ __metadata: languageName: node linkType: hard -"@react-native-community/cli-platform-android@npm:13.6.9": +"@react-native-community/cli-platform-android@npm:13.6.9, @react-native-community/cli-platform-android@npm:~13.6.6": version: 13.6.9 resolution: "@react-native-community/cli-platform-android@npm:13.6.9" dependencies: @@ -17444,6 +17658,13 @@ __metadata: languageName: node linkType: hard +"@react-native/normalize-colors@npm:^0.74.1": + version: 0.74.88 + resolution: "@react-native/normalize-colors@npm:0.74.88" + checksum: 348d0f1b9802e824843ec58ed90f72af078b81dd576f72c45caa1ed9846ea733b0dab932e431f88ebc40a186e7443875b64e8e2cf8e669a59abef0aedf2d9aa7 + languageName: node + linkType: hard + "@react-native/typescript-config@npm:0.74.87": version: 0.74.87 resolution: "@react-native/typescript-config@npm:0.74.87" @@ -19988,6 +20209,15 @@ __metadata: languageName: node linkType: hard +"@svgr/babel-plugin-transform-react-native-svg@npm:8.1.0": + version: 8.1.0 + resolution: "@svgr/babel-plugin-transform-react-native-svg@npm:8.1.0" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 85b434a57572f53bd2b9f0606f253e1fcf57b4a8c554ec3f2d43ed17f50d8cae200cb3aaf1ec9d626e1456e8b135dce530ae047eb0bed6d4bf98a752d6640459 + languageName: node + linkType: hard + "@svgr/babel-plugin-transform-svg-component@npm:8.0.0": version: 8.0.0 resolution: "@svgr/babel-plugin-transform-svg-component@npm:8.0.0" @@ -20015,6 +20245,24 @@ __metadata: languageName: node linkType: hard +"@svgr/babel-preset@npm:8.1.0": + version: 8.1.0 + resolution: "@svgr/babel-preset@npm:8.1.0" + dependencies: + "@svgr/babel-plugin-add-jsx-attribute": 8.0.0 + "@svgr/babel-plugin-remove-jsx-attribute": 8.0.0 + "@svgr/babel-plugin-remove-jsx-empty-expression": 8.0.0 + "@svgr/babel-plugin-replace-jsx-attribute-value": 8.0.0 + "@svgr/babel-plugin-svg-dynamic-title": 8.0.0 + "@svgr/babel-plugin-svg-em-dimensions": 8.0.0 + "@svgr/babel-plugin-transform-react-native-svg": 8.1.0 + "@svgr/babel-plugin-transform-svg-component": 8.0.0 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 3a67930f080b8891e1e8e2595716b879c944d253112bae763dce59807ba23454d162216c8d66a0a0e3d4f38a649ecd6c387e545d1e1261dd69a68e9a3392ee08 + languageName: node + linkType: hard + "@svgr/core@npm:8.0.0": version: 8.0.0 resolution: "@svgr/core@npm:8.0.0" @@ -20028,6 +20276,19 @@ __metadata: languageName: node linkType: hard +"@svgr/core@npm:^8.1.0": + version: 8.1.0 + resolution: "@svgr/core@npm:8.1.0" + dependencies: + "@babel/core": ^7.21.3 + "@svgr/babel-preset": 8.1.0 + camelcase: ^6.2.0 + cosmiconfig: ^8.1.3 + snake-case: ^3.0.4 + checksum: da4a12865c7dc59829d58df8bd232d6c85b7115fda40da0d2f844a1a51886e2e945560596ecfc0345d37837ac457de86a931e8b8d8550e729e0c688c02250d8a + languageName: node + linkType: hard + "@svgr/hast-util-to-babel-ast@npm:8.0.0": version: 8.0.0 resolution: "@svgr/hast-util-to-babel-ast@npm:8.0.0" @@ -20052,6 +20313,20 @@ __metadata: languageName: node linkType: hard +"@svgr/plugin-jsx@npm:^8.1.0": + version: 8.1.0 + resolution: "@svgr/plugin-jsx@npm:8.1.0" + dependencies: + "@babel/core": ^7.21.3 + "@svgr/babel-preset": 8.1.0 + "@svgr/hast-util-to-babel-ast": 8.0.0 + svg-parser: ^2.0.4 + peerDependencies: + "@svgr/core": "*" + checksum: 0418a9780753d3544912ee2dad5d2cf8d12e1ba74df8053651b3886aeda54d5f0f7d2dece0af5e0d838332c4f139a57f0dabaa3ca1afa4d1a765efce6a7656f2 + languageName: node + linkType: hard + "@svgr/plugin-svgo@npm:8.0.1": version: 8.0.1 resolution: "@svgr/plugin-svgo@npm:8.0.1" @@ -20065,6 +20340,19 @@ __metadata: languageName: node linkType: hard +"@svgr/plugin-svgo@npm:^8.1.0": + version: 8.1.0 + resolution: "@svgr/plugin-svgo@npm:8.1.0" + dependencies: + cosmiconfig: ^8.1.3 + deepmerge: ^4.3.1 + svgo: ^3.0.2 + peerDependencies: + "@svgr/core": "*" + checksum: 59d9d214cebaacca9ca71a561f463d8b7e5a68ca9443e4792a42d903acd52259b1790c0680bc6afecc3f00a255a6cbd7ea278a9f625bac443620ea58a590c2d0 + languageName: node + linkType: hard + "@svgr/webpack@npm:^8.0.1": version: 8.0.1 resolution: "@svgr/webpack@npm:8.0.1" @@ -20692,6 +20980,42 @@ __metadata: languageName: node linkType: hard +"@testing-library/jest-native@npm:5.4.3": + version: 5.4.3 + resolution: "@testing-library/jest-native@npm:5.4.3" + dependencies: + chalk: ^4.1.2 + jest-diff: ^29.0.1 + jest-matcher-utils: ^29.0.1 + pretty-format: ^29.0.3 + redent: ^3.0.0 + peerDependencies: + react: ">=16.0.0" + react-native: ">=0.59" + react-test-renderer: ">=16.0.0" + checksum: 2a4ebfeff09523860771cfddac6fcc3faa2f855dc63255b9efc016e727132320f16f935cec9717d6d79cfa6715fce6ded877215c8ec85d236a5c3136a65b1020 + languageName: node + linkType: hard + +"@testing-library/react-native@npm:12.9.0": + version: 12.9.0 + resolution: "@testing-library/react-native@npm:12.9.0" + dependencies: + jest-matcher-utils: ^29.7.0 + pretty-format: ^29.7.0 + redent: ^3.0.0 + peerDependencies: + jest: ">=28.0.0" + react: ">=16.8.0" + react-native: ">=0.59" + react-test-renderer: ">=16.8.0" + peerDependenciesMeta: + jest: + optional: true + checksum: 88115b22c127f39b2e1e8098dc1c93ea9c7393800a24f4f380bed64425cc685f98cad5b56b9cb48d85f0dbed1f0f208d0de44137c6e789c98161ff2715f70646 + languageName: node + linkType: hard + "@testing-library/react@npm:15.0.6": version: 15.0.6 resolution: "@testing-library/react@npm:15.0.6" @@ -25995,19 +26319,6 @@ __metadata: languageName: node linkType: hard -"babel-plugin-module-resolver@npm:5.0.2": - version: 5.0.2 - resolution: "babel-plugin-module-resolver@npm:5.0.2" - dependencies: - find-babel-config: ^2.1.1 - glob: ^9.3.3 - pkg-up: ^3.1.0 - reselect: ^4.1.7 - resolve: ^1.22.8 - checksum: f1d198acbbbd0b76c9c0c4aacbf9f1ef90f8d36b3d5209d9e7a75cadee2113a73711550ebddeb9464d143b71df19adc75e165dff99ada2614d7ea333affe3b5a - languageName: node - linkType: hard - "babel-plugin-polyfill-corejs2@npm:^0.3.3": version: 0.3.3 resolution: "babel-plugin-polyfill-corejs2@npm:0.3.3" @@ -29625,6 +29936,15 @@ __metadata: languageName: node linkType: hard +"css-in-js-utils@npm:^3.1.0": + version: 3.1.0 + resolution: "css-in-js-utils@npm:3.1.0" + dependencies: + hyphenate-style-name: ^1.0.3 + checksum: 066318e918c04a5e5bce46b38fe81052ea6ac051bcc6d3c369a1d59ceb1546cb2b6086901ab5d22be084122ee3732169996a3dfb04d3406eaee205af77aec61b + languageName: node + linkType: hard + "css-loader@npm:^6.4.0, css-loader@npm:^6.7.1": version: 6.8.1 resolution: "css-loader@npm:6.8.1" @@ -31816,6 +32136,16 @@ __metadata: languageName: node linkType: hard +"enhanced-resolve@npm:^5.8.3": + version: 5.17.1 + resolution: "enhanced-resolve@npm:5.17.1" + dependencies: + graceful-fs: ^4.2.4 + tapable: ^2.2.0 + checksum: 4bc38cf1cea96456f97503db7280394177d1bc46f8f87c267297d04f795ac5efa81e48115a2f5b6273c781027b5b6bfc5f62b54df629e4d25fa7001a86624f59 + languageName: node + linkType: hard + "enquirer@npm:^2.3.6, enquirer@npm:~2.3.6": version: 2.3.6 resolution: "enquirer@npm:2.3.6" @@ -33698,6 +34028,13 @@ __metadata: languageName: node linkType: hard +"exponential-backoff@npm:^3.1.1": + version: 3.1.1 + resolution: "exponential-backoff@npm:3.1.1" + checksum: 3d21519a4f8207c99f7457287291316306255a328770d320b401114ec8481986e4e467e854cb9914dd965e0a1ca810a23ccb559c642c88f4c7f55c55778a9b48 + languageName: node + linkType: hard + "express-validator@npm:6.14.0": version: 6.14.0 resolution: "express-validator@npm:6.14.0" @@ -34019,6 +34356,13 @@ __metadata: languageName: node linkType: hard +"fast-loops@npm:^1.1.3": + version: 1.1.4 + resolution: "fast-loops@npm:1.1.4" + checksum: 8031a20f465ef35ac4ad98258470250636112d34f7e4efcb4ef21f3ced99df95a1ef1f0d6943df729a1e3e12a9df9319f3019df8cc1a0e0ed5a118bd72e505f9 + languageName: node + linkType: hard + "fast-memoize@npm:^2.5.2": version: 2.5.2 resolution: "fast-memoize@npm:2.5.2" @@ -34161,6 +34505,21 @@ __metadata: languageName: node linkType: hard +"fbjs@npm:^3.0.4": + version: 3.0.5 + resolution: "fbjs@npm:3.0.5" + dependencies: + cross-fetch: ^3.1.5 + fbjs-css-vars: ^1.0.0 + loose-envify: ^1.0.0 + object-assign: ^4.1.0 + promise: ^7.1.1 + setimmediate: ^1.0.5 + ua-parser-js: ^1.0.35 + checksum: e609b5b64686bc96495a5c67728ed9b2710b9b3d695c5759c5f5e47c9483d1c323543ac777a86459e3694efc5712c6ce7212e944feb19752867d699568bb0e54 + languageName: node + linkType: hard + "fd-slicer@npm:~1.1.0": version: 1.1.0 resolution: "fd-slicer@npm:1.1.0" @@ -34377,16 +34736,6 @@ __metadata: languageName: node linkType: hard -"find-babel-config@npm:^2.1.1": - version: 2.1.1 - resolution: "find-babel-config@npm:2.1.1" - dependencies: - json5: ^2.2.3 - path-exists: ^4.0.0 - checksum: 4be54397339520e0cd49870acb10366684ffc001fd0b7bffedd0fe9d3e1d82234692d3cb4e5ba95280a35887238ba6f82dc79569a13a3749ae3931c23e0b3a99 - languageName: node - linkType: hard - "find-cache-dir@npm:^2.0.0": version: 2.1.0 resolution: "find-cache-dir@npm:2.1.0" @@ -35656,18 +36005,6 @@ __metadata: languageName: node linkType: hard -"glob@npm:^9.3.3": - version: 9.3.5 - resolution: "glob@npm:9.3.5" - dependencies: - fs.realpath: ^1.0.0 - minimatch: ^8.0.2 - minipass: ^4.2.4 - path-scurry: ^1.6.1 - checksum: 94b093adbc591bc36b582f77927d1fb0dbf3ccc231828512b017601408be98d1fe798fc8c0b19c6f2d1a7660339c3502ce698de475e9d938ccbb69b47b647c84 - languageName: node - linkType: hard - "global-dirs@npm:^3.0.0": version: 3.0.0 resolution: "global-dirs@npm:3.0.0" @@ -36381,6 +36718,20 @@ __metadata: languageName: node linkType: hard +"hermes-estree@npm:0.23.1": + version: 0.23.1 + resolution: "hermes-estree@npm:0.23.1" + checksum: 0f63edc365099304f4cd8e91a3666a4fb5a2a47baee751dc120df9201640112865944cae93617f554af71be9827e96547f9989f4972d6964ecc121527295fec6 + languageName: node + linkType: hard + +"hermes-estree@npm:0.24.0": + version: 0.24.0 + resolution: "hermes-estree@npm:0.24.0" + checksum: 23d09013c824cd4628f6bae50c7a703cbafcc26ff1802cb35547fac41be4aac6e9892656bb6eb495e5c8c4b1287311dad8eab0f541ff8f1d2f0265b75053002e + languageName: node + linkType: hard + "hermes-parser@npm:0.19.1": version: 0.19.1 resolution: "hermes-parser@npm:0.19.1" @@ -36399,6 +36750,24 @@ __metadata: languageName: node linkType: hard +"hermes-parser@npm:0.23.1": + version: 0.23.1 + resolution: "hermes-parser@npm:0.23.1" + dependencies: + hermes-estree: 0.23.1 + checksum: a08008928aea9ea9a2cab2c0fac3cffa21f7869ab3fabb68e5add0fe057737a0c352d7a446426f7956172ccc8f2d4a215b4fc20d1d08354fc8dc16772c248fce + languageName: node + linkType: hard + +"hermes-parser@npm:0.24.0": + version: 0.24.0 + resolution: "hermes-parser@npm:0.24.0" + dependencies: + hermes-estree: 0.24.0 + checksum: c23cb81d320cedc74841c254ea54d94328f65aa6259375d48ab2b5a3ad2b528c55058726d852376811e4018636d8fd9305a4b2bfa5a962297c1baa57444be172 + languageName: node + linkType: hard + "hermes-profile-transformer@npm:^0.0.6": version: 0.0.6 resolution: "hermes-profile-transformer@npm:0.0.6" @@ -36979,6 +37348,13 @@ __metadata: languageName: node linkType: hard +"hyphenate-style-name@npm:^1.0.3": + version: 1.1.0 + resolution: "hyphenate-style-name@npm:1.1.0" + checksum: b9ed74e29181d96bd58a2d0e62fc4a19879db591dba268275829ff0ae595fcdf11faafaeaa63330a45c3004664d7db1f0fc7cdb372af8ee4615ed8260302c207 + languageName: node + linkType: hard + "hypher@npm:0.2.5": version: 0.2.5 resolution: "hypher@npm:0.2.5" @@ -37315,6 +37691,16 @@ __metadata: languageName: node linkType: hard +"inline-style-prefixer@npm:^6.0.1": + version: 6.0.4 + resolution: "inline-style-prefixer@npm:6.0.4" + dependencies: + css-in-js-utils: ^3.1.0 + fast-loops: ^1.1.3 + checksum: caf7a75d18acbedc7e3b8bfac17563082becd2df6b65accad964a6afdf490329b42315c37fe65ba0177cc10fd32809eb40d62aba23a0118c74d87d4fc58defa2 + languageName: node + linkType: hard + "inquirer-select-directory@npm:^1.2.0": version: 1.2.0 resolution: "inquirer-select-directory@npm:1.2.0" @@ -38616,6 +39002,7 @@ __metadata: "@nestjs/terminus": 10.2.0 "@nestjs/testing": 10.0.5 "@nx/cypress": 19.4.0 + "@nx/devkit": 19.4.0 "@nx/eslint": 19.4.0 "@nx/eslint-plugin": 19.4.0 "@nx/express": 19.4.0 @@ -38626,12 +39013,16 @@ __metadata: "@nx/node": 19.4.0 "@nx/playwright": 19.4.0 "@nx/react": 19.4.0 + "@nx/react-native": 19.4.0 "@nx/storybook": 19.4.0 "@nx/web": 19.4.0 "@nx/webpack": 19.4.0 "@nx/workspace": 19.4.0 "@openapitools/openapi-generator-cli": 1.0.15-4.3.1 "@playwright/test": 1.48 + "@react-native-community/cli-platform-android": ~13.6.6 + "@react-native/babel-preset": 0.74.87 + "@react-native/metro-config": 0.74.87 "@react-pdf/renderer": ^3.1.9 "@rehooks/component-size": 1.0.3 "@simplewebauthn/server": 10.0.0 @@ -38651,7 +39042,9 @@ __metadata: "@swc/helpers": 0.5.11 "@testing-library/cypress": 8.0.3 "@testing-library/jest-dom": 5.16.5 + "@testing-library/jest-native": 5.4.3 "@testing-library/react": 15.0.6 + "@testing-library/react-native": 12.9.0 "@testing-library/user-event": 14.4.3 "@types/archiver": 6.0.2 "@types/aws-sdk": 2.7.0 @@ -38815,6 +39208,7 @@ __metadata: jest-environment-jsdom: 29.7.0 jest-environment-node: 29.7.0 jest-mock-extended: 3.0.5 + jest-react-native: 18.0.0 jest-transform-stub: 2.0.0 js-base64: 2.5.2 js-cookie: 2.2.1 @@ -38840,7 +39234,7 @@ __metadata: next-auth: 3.29.10 next-cookies: 2.0.3 next-secure-headers: 2.1.0 - next-usequerystate: 1.8.4 + next-usequerystate: 1.20.0 node-fetch: 2.6.7 node-gyp: 9.1.0 node-html-markdown: 1.3.0 @@ -38877,6 +39271,10 @@ __metadata: react-is: 18.3.1 react-keyed-flatten-children: 1.2.0 react-modal: 3.15.1 + react-native: 0.74.5 + react-native-svg: 15.2.0 + react-native-svg-transformer: 1.3.0 + react-native-web: ^0.19.11 react-number-format: 4.9.1 react-pdf: 9.1.0 react-popper: 2.3.0 @@ -38884,6 +39282,7 @@ __metadata: react-router-dom: 6.11.2 react-select: 5.8.2 react-table: 7.7.0 + react-test-renderer: 18.2.0 react-toastify: 6.0.8 react-top-loading-bar: 2.3.1 react-use: 15.3.3 @@ -39334,7 +39733,7 @@ __metadata: languageName: node linkType: hard -"jest-diff@npm:^29.4.1, jest-diff@npm:^29.7.0": +"jest-diff@npm:^29.0.1, jest-diff@npm:^29.4.1, jest-diff@npm:^29.7.0": version: 29.7.0 resolution: "jest-diff@npm:29.7.0" dependencies: @@ -39516,6 +39915,18 @@ __metadata: languageName: node linkType: hard +"jest-matcher-utils@npm:^29.0.1, jest-matcher-utils@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-matcher-utils@npm:29.7.0" + dependencies: + chalk: ^4.0.0 + jest-diff: ^29.7.0 + jest-get-type: ^29.6.3 + pretty-format: ^29.7.0 + checksum: d7259e5f995d915e8a37a8fd494cb7d6af24cd2a287b200f831717ba0d015190375f9f5dc35393b8ba2aae9b2ebd60984635269c7f8cff7d85b077543b7744cd + languageName: node + linkType: hard + "jest-matcher-utils@npm:^29.5.0": version: 29.5.0 resolution: "jest-matcher-utils@npm:29.5.0" @@ -39528,18 +39939,6 @@ __metadata: languageName: node linkType: hard -"jest-matcher-utils@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-matcher-utils@npm:29.7.0" - dependencies: - chalk: ^4.0.0 - jest-diff: ^29.7.0 - jest-get-type: ^29.6.3 - pretty-format: ^29.7.0 - checksum: d7259e5f995d915e8a37a8fd494cb7d6af24cd2a287b200f831717ba0d015190375f9f5dc35393b8ba2aae9b2ebd60984635269c7f8cff7d85b077543b7744cd - languageName: node - linkType: hard - "jest-message-util@npm:^27.5.1": version: 27.5.1 resolution: "jest-message-util@npm:27.5.1" @@ -39626,6 +40025,15 @@ __metadata: languageName: node linkType: hard +"jest-react-native@npm:18.0.0": + version: 18.0.0 + resolution: "jest-react-native@npm:18.0.0" + peerDependencies: + react-native: ">=0.38.0" + checksum: 3c524bbdf4a030d99c8202e0f67427130a092f884935ee4c93bcb5a9e9cca8ad1f5db259315175c9b56c2eae5aefbf12ea7bc532956ba806698fbbe3d0ab2dcf + languageName: node + linkType: hard + "jest-regex-util@npm:^27.5.1": version: 27.5.1 resolution: "jest-regex-util@npm:27.5.1" @@ -40434,6 +40842,15 @@ __metadata: languageName: node linkType: hard +"jsesc@npm:^3.0.2": + version: 3.0.2 + resolution: "jsesc@npm:3.0.2" + bin: + jsesc: bin/jsesc + checksum: a36d3ca40574a974d9c2063bf68c2b6141c20da8f2a36bd3279fc802563f35f0527a6c828801295bdfb2803952cf2cf387786c2c90ed564f88d5782475abfe3c + languageName: node + linkType: hard + "jsesc@npm:~0.5.0": version: 0.5.0 resolution: "jsesc@npm:0.5.0" @@ -42997,6 +43414,18 @@ __metadata: languageName: node linkType: hard +"metro-babel-transformer@npm:0.80.12": + version: 0.80.12 + resolution: "metro-babel-transformer@npm:0.80.12" + dependencies: + "@babel/core": ^7.20.0 + flow-enums-runtime: ^0.0.6 + hermes-parser: 0.23.1 + nullthrows: ^1.1.1 + checksum: 1ea8bce0c169f3d8bf46f56da126ca52f4c8ba5ca9ffeaca987c34d269b0a3e2a54d0544bd44bfa5d0322e37f0171a52d2a2160defcbcd91ec1fd96f62b0eece + languageName: node + linkType: hard + "metro-babel-transformer@npm:0.80.9": version: 0.80.9 resolution: "metro-babel-transformer@npm:0.80.9" @@ -43008,6 +43437,27 @@ __metadata: languageName: node linkType: hard +"metro-babel-transformer@npm:0.81.0": + version: 0.81.0 + resolution: "metro-babel-transformer@npm:0.81.0" + dependencies: + "@babel/core": ^7.25.2 + flow-enums-runtime: ^0.0.6 + hermes-parser: 0.24.0 + nullthrows: ^1.1.1 + checksum: e67ef5175f574fbf4a3b6c4f5fd209eb04026cdc32a38e2ebaea21a8c1d4ca20d234aba8e3bff95bfcf60353aaaa0e6369544fe15b1d02aa07f77ab2c26cf053 + languageName: node + linkType: hard + +"metro-cache-key@npm:0.80.12": + version: 0.80.12 + resolution: "metro-cache-key@npm:0.80.12" + dependencies: + flow-enums-runtime: ^0.0.6 + checksum: 7a06601180604361339d19eb833d61b79cc188a4e6ebe73188cc10fbf3a33e711d74c81d1d19a14b6581bd9dfeebe1b253684360682d033ab55909c9995b6a18 + languageName: node + linkType: hard + "metro-cache-key@npm:0.80.9": version: 0.80.9 resolution: "metro-cache-key@npm:0.80.9" @@ -43015,6 +43465,26 @@ __metadata: languageName: node linkType: hard +"metro-cache-key@npm:0.81.0": + version: 0.81.0 + resolution: "metro-cache-key@npm:0.81.0" + dependencies: + flow-enums-runtime: ^0.0.6 + checksum: a96e4062ac0f4684f1d80c8b8c3da380c9d7be506c2bc14750d46a6850610c6e05cb1907cc5421393299f25f40575335e899667519d5435c95a09b0438619847 + languageName: node + linkType: hard + +"metro-cache@npm:0.80.12": + version: 0.80.12 + resolution: "metro-cache@npm:0.80.12" + dependencies: + exponential-backoff: ^3.1.1 + flow-enums-runtime: ^0.0.6 + metro-core: 0.80.12 + checksum: 724e33fdda6a3568572c36a3f2d3465ad1b5f3e8ded5ec116b98e0038826187ebdadd05f77e91ddc17fa71ff4dd91281793a940e7b619cac36044ed868abc01d + languageName: node + linkType: hard + "metro-cache@npm:0.80.9": version: 0.80.9 resolution: "metro-cache@npm:0.80.9" @@ -43025,6 +43495,33 @@ __metadata: languageName: node linkType: hard +"metro-cache@npm:0.81.0": + version: 0.81.0 + resolution: "metro-cache@npm:0.81.0" + dependencies: + exponential-backoff: ^3.1.1 + flow-enums-runtime: ^0.0.6 + metro-core: 0.81.0 + checksum: 0498a93b07b8125987268dde7f95b56ea61826be7834b87f03595de905210dc2675855d8dbbbc0aab0a2f50ed8be0086b096a4085f7320247e3fc6added45167 + languageName: node + linkType: hard + +"metro-config@npm:0.80.12, metro-config@npm:~0.80.4": + version: 0.80.12 + resolution: "metro-config@npm:0.80.12" + dependencies: + connect: ^3.6.5 + cosmiconfig: ^5.0.5 + flow-enums-runtime: ^0.0.6 + jest-validate: ^29.6.3 + metro: 0.80.12 + metro-cache: 0.80.12 + metro-core: 0.80.12 + metro-runtime: 0.80.12 + checksum: 49496d2bc875fbb8c89639979753377888f5ce779742a4ef487d812e7c5f3f6c87dd6ae129727f614d2fe3210f7fde08041055d29772b8c86c018e2ef08e7785 + languageName: node + linkType: hard + "metro-config@npm:0.80.9, metro-config@npm:^0.80.3": version: 0.80.9 resolution: "metro-config@npm:0.80.9" @@ -43040,6 +43537,33 @@ __metadata: languageName: node linkType: hard +"metro-config@npm:0.81.0": + version: 0.81.0 + resolution: "metro-config@npm:0.81.0" + dependencies: + connect: ^3.6.5 + cosmiconfig: ^5.0.5 + flow-enums-runtime: ^0.0.6 + jest-validate: ^29.6.3 + metro: 0.81.0 + metro-cache: 0.81.0 + metro-core: 0.81.0 + metro-runtime: 0.81.0 + checksum: 4969423a292b4aec8f604ae0f682bd62f463ee7a84459c1cf069ff0239427a01e287b97516d265a6b1ec9e8a7b3eb09ad5a8b914e469c9aff56f25473325fe29 + languageName: node + linkType: hard + +"metro-core@npm:0.80.12": + version: 0.80.12 + resolution: "metro-core@npm:0.80.12" + dependencies: + flow-enums-runtime: ^0.0.6 + lodash.throttle: ^4.1.1 + metro-resolver: 0.80.12 + checksum: 319f3965fa76fc08987cbd0228024bdbb0eaad7406e384e48929674188f1066cbc7a233053615ebd84b3ce1bbae28f59c114885fd0a0c179a580319ed69f717e + languageName: node + linkType: hard + "metro-core@npm:0.80.9, metro-core@npm:^0.80.3": version: 0.80.9 resolution: "metro-core@npm:0.80.9" @@ -43050,6 +43574,40 @@ __metadata: languageName: node linkType: hard +"metro-core@npm:0.81.0": + version: 0.81.0 + resolution: "metro-core@npm:0.81.0" + dependencies: + flow-enums-runtime: ^0.0.6 + lodash.throttle: ^4.1.1 + metro-resolver: 0.81.0 + checksum: 4e9e63d4c29f7a4f3e13ee8281c2be4458f5482de5f73d6206782cca78dc580b4d3a16516ff278313fcd1a3e4177e521b3aa0f12768fbf5cc335797557846953 + languageName: node + linkType: hard + +"metro-file-map@npm:0.80.12": + version: 0.80.12 + resolution: "metro-file-map@npm:0.80.12" + dependencies: + anymatch: ^3.0.3 + debug: ^2.2.0 + fb-watchman: ^2.0.0 + flow-enums-runtime: ^0.0.6 + fsevents: ^2.3.2 + graceful-fs: ^4.2.4 + invariant: ^2.2.4 + jest-worker: ^29.6.3 + micromatch: ^4.0.4 + node-abort-controller: ^3.1.1 + nullthrows: ^1.1.1 + walker: ^1.0.7 + dependenciesMeta: + fsevents: + optional: true + checksum: 5e6eafcfafe55fd8a9a6e5613394a20ed2a0ad433a394dcb830f017b8fc9d82ddcd715391e36abe5e98c651c074b99a806d3b04d76f2cadb225f9f5b1c92daef + languageName: node + linkType: hard + "metro-file-map@npm:0.80.9": version: 0.80.9 resolution: "metro-file-map@npm:0.80.9" @@ -43072,6 +43630,39 @@ __metadata: languageName: node linkType: hard +"metro-file-map@npm:0.81.0": + version: 0.81.0 + resolution: "metro-file-map@npm:0.81.0" + dependencies: + anymatch: ^3.0.3 + debug: ^2.2.0 + fb-watchman: ^2.0.0 + flow-enums-runtime: ^0.0.6 + fsevents: ^2.3.2 + graceful-fs: ^4.2.4 + invariant: ^2.2.4 + jest-worker: ^29.6.3 + micromatch: ^4.0.4 + node-abort-controller: ^3.1.1 + nullthrows: ^1.1.1 + walker: ^1.0.7 + dependenciesMeta: + fsevents: + optional: true + checksum: fc99466066fc57d506a90b8dbfc85b9aed3b3dfe362f42c35e24a3f0244b5f3e94b833b52b20cdd728842a1ef7e6c2132b9951a2c2d4013fb470e3a65b9971e0 + languageName: node + linkType: hard + +"metro-minify-terser@npm:0.80.12": + version: 0.80.12 + resolution: "metro-minify-terser@npm:0.80.12" + dependencies: + flow-enums-runtime: ^0.0.6 + terser: ^5.15.0 + checksum: ff527b3f04c5814db139e55ceb7689aaaf0af5c7fbb0eb5d4a6f22044932dfb10bd385d388fa7b352acd03a2d078edaf43a6b5cd11cbc87a7c5502a34fc12735 + languageName: node + linkType: hard + "metro-minify-terser@npm:0.80.9": version: 0.80.9 resolution: "metro-minify-terser@npm:0.80.9" @@ -43081,6 +43672,25 @@ __metadata: languageName: node linkType: hard +"metro-minify-terser@npm:0.81.0": + version: 0.81.0 + resolution: "metro-minify-terser@npm:0.81.0" + dependencies: + flow-enums-runtime: ^0.0.6 + terser: ^5.15.0 + checksum: 53472e5d476613c652f0e8bdf68429c80c66b71dd9a559c2185d56f41a8463ba3431353d453d2e20615875d070389ec24247ddbce67c4d7783bfc85113af18e0 + languageName: node + linkType: hard + +"metro-resolver@npm:0.80.12, metro-resolver@npm:~0.80.4": + version: 0.80.12 + resolution: "metro-resolver@npm:0.80.12" + dependencies: + flow-enums-runtime: ^0.0.6 + checksum: a520030a65afab2f3282604ef6dec802051899a356910606b8ffbc5b82a722008d9d416c8ba3d9ef9527912206586b713733b776803a6b76adac72bcb31870cd + languageName: node + linkType: hard + "metro-resolver@npm:0.80.9": version: 0.80.9 resolution: "metro-resolver@npm:0.80.9" @@ -43088,6 +43698,25 @@ __metadata: languageName: node linkType: hard +"metro-resolver@npm:0.81.0": + version: 0.81.0 + resolution: "metro-resolver@npm:0.81.0" + dependencies: + flow-enums-runtime: ^0.0.6 + checksum: 38349c79b5023d993baf30c7feeb9d60287f33e7bf559b75ce6b4177a4acd991353a0fea0a8caeec9a78efa244c8608c0e5bdff4ac64d6fda89ca0b81c9ca3fc + languageName: node + linkType: hard + +"metro-runtime@npm:0.80.12": + version: 0.80.12 + resolution: "metro-runtime@npm:0.80.12" + dependencies: + "@babel/runtime": ^7.25.0 + flow-enums-runtime: ^0.0.6 + checksum: 11a6d36c7dcf9d221f7de6989556f45d4d64cd1cdd225ec96273b584138b4aa77b7afdc9e9a9488d1dc9a3d90f8e94bb68ab149079cc6ebdb8f8f8b03462cb4f + languageName: node + linkType: hard + "metro-runtime@npm:0.80.9, metro-runtime@npm:^0.80.3": version: 0.80.9 resolution: "metro-runtime@npm:0.80.9" @@ -43097,6 +43726,33 @@ __metadata: languageName: node linkType: hard +"metro-runtime@npm:0.81.0": + version: 0.81.0 + resolution: "metro-runtime@npm:0.81.0" + dependencies: + "@babel/runtime": ^7.25.0 + flow-enums-runtime: ^0.0.6 + checksum: 812869ed71d6017d04c3affafa0b1bd4c86075569e0eb98030b8abddb59923903e3dc8eb23d7dd027384496e27010f6aad7839b0e1105e3873c31d0269fb7971 + languageName: node + linkType: hard + +"metro-source-map@npm:0.80.12": + version: 0.80.12 + resolution: "metro-source-map@npm:0.80.12" + dependencies: + "@babel/traverse": ^7.20.0 + "@babel/types": ^7.20.0 + flow-enums-runtime: ^0.0.6 + invariant: ^2.2.4 + metro-symbolicate: 0.80.12 + nullthrows: ^1.1.1 + ob1: 0.80.12 + source-map: ^0.5.6 + vlq: ^1.0.0 + checksum: 39575bff8666abd0944ec71e01a0c0eacbeab48277528608e894ffa6691c4267c389ee51ad86d5cd8e96f13782b66e1f693a3c60786bb201268678232dce6130 + languageName: node + linkType: hard + "metro-source-map@npm:0.80.9, metro-source-map@npm:^0.80.3": version: 0.80.9 resolution: "metro-source-map@npm:0.80.9" @@ -43113,6 +43769,41 @@ __metadata: languageName: node linkType: hard +"metro-source-map@npm:0.81.0": + version: 0.81.0 + resolution: "metro-source-map@npm:0.81.0" + dependencies: + "@babel/traverse": ^7.25.3 + "@babel/traverse--for-generate-function-map": "npm:@babel/traverse@^7.25.3" + "@babel/types": ^7.25.2 + flow-enums-runtime: ^0.0.6 + invariant: ^2.2.4 + metro-symbolicate: 0.81.0 + nullthrows: ^1.1.1 + ob1: 0.81.0 + source-map: ^0.5.6 + vlq: ^1.0.0 + checksum: e83742c187427b009a5e15eeddd0af0ef29c6e0b88e5f0ac0ba13142e8883f45ce9d66dc8439ca080cea242e955c4f4ba0d64f8344777479ad89d97fa393ad29 + languageName: node + linkType: hard + +"metro-symbolicate@npm:0.80.12": + version: 0.80.12 + resolution: "metro-symbolicate@npm:0.80.12" + dependencies: + flow-enums-runtime: ^0.0.6 + invariant: ^2.2.4 + metro-source-map: 0.80.12 + nullthrows: ^1.1.1 + source-map: ^0.5.6 + through2: ^2.0.1 + vlq: ^1.0.0 + bin: + metro-symbolicate: src/index.js + checksum: b775e4613deec421f6287918d0055c50bb2a38fe3f72581eb70b9441e4497c9c7413c2929c579b24fb76893737b6d5af83a5f6cd8c032e2a83957091f82ec5de + languageName: node + linkType: hard + "metro-symbolicate@npm:0.80.9": version: 0.80.9 resolution: "metro-symbolicate@npm:0.80.9" @@ -43129,6 +43820,37 @@ __metadata: languageName: node linkType: hard +"metro-symbolicate@npm:0.81.0": + version: 0.81.0 + resolution: "metro-symbolicate@npm:0.81.0" + dependencies: + flow-enums-runtime: ^0.0.6 + invariant: ^2.2.4 + metro-source-map: 0.81.0 + nullthrows: ^1.1.1 + source-map: ^0.5.6 + through2: ^2.0.1 + vlq: ^1.0.0 + bin: + metro-symbolicate: src/index.js + checksum: 33990dc3722096beb0fabce5d8d2961b8f400e1f2aa6c19ce9760f9d739b63f25c7bd844e37e0de42e7f95c125431f7e42a7ad0b92b9aee8d214fecdfb4018e7 + languageName: node + linkType: hard + +"metro-transform-plugins@npm:0.80.12": + version: 0.80.12 + resolution: "metro-transform-plugins@npm:0.80.12" + dependencies: + "@babel/core": ^7.20.0 + "@babel/generator": ^7.20.0 + "@babel/template": ^7.0.0 + "@babel/traverse": ^7.20.0 + flow-enums-runtime: ^0.0.6 + nullthrows: ^1.1.1 + checksum: 85c99c367d6c0b9721af744fc980372329c6d37711177660e2d5e2dbe5e92e2cd853604eb8a513ad824eafbed84663472fa304cbbe2036957ee8688b72c2324c + languageName: node + linkType: hard + "metro-transform-plugins@npm:0.80.9": version: 0.80.9 resolution: "metro-transform-plugins@npm:0.80.9" @@ -43142,6 +43864,41 @@ __metadata: languageName: node linkType: hard +"metro-transform-plugins@npm:0.81.0": + version: 0.81.0 + resolution: "metro-transform-plugins@npm:0.81.0" + dependencies: + "@babel/core": ^7.25.2 + "@babel/generator": ^7.25.0 + "@babel/template": ^7.25.0 + "@babel/traverse": ^7.25.3 + flow-enums-runtime: ^0.0.6 + nullthrows: ^1.1.1 + checksum: fea77e227c856cd3a41f55ddcde9852d7408cd3ceb4b434f23e02e5122a95f0a29b1950adae0b806d96bfb26581c1160c4bc62942888698394fcc4e85e0b8ee7 + languageName: node + linkType: hard + +"metro-transform-worker@npm:0.80.12": + version: 0.80.12 + resolution: "metro-transform-worker@npm:0.80.12" + dependencies: + "@babel/core": ^7.20.0 + "@babel/generator": ^7.20.0 + "@babel/parser": ^7.20.0 + "@babel/types": ^7.20.0 + flow-enums-runtime: ^0.0.6 + metro: 0.80.12 + metro-babel-transformer: 0.80.12 + metro-cache: 0.80.12 + metro-cache-key: 0.80.12 + metro-minify-terser: 0.80.12 + metro-source-map: 0.80.12 + metro-transform-plugins: 0.80.12 + nullthrows: ^1.1.1 + checksum: 90684b1f1163bfc84b11bfc01082a38de2a5dd9f7bcabc524bc84f1faff32222954f686a60bc0f464d3e46e86c4c01435111e2ed0e9767a5efbfaf205f55245e + languageName: node + linkType: hard + "metro-transform-worker@npm:0.80.9": version: 0.80.9 resolution: "metro-transform-worker@npm:0.80.9" @@ -43162,6 +43919,79 @@ __metadata: languageName: node linkType: hard +"metro-transform-worker@npm:0.81.0": + version: 0.81.0 + resolution: "metro-transform-worker@npm:0.81.0" + dependencies: + "@babel/core": ^7.25.2 + "@babel/generator": ^7.25.0 + "@babel/parser": ^7.25.3 + "@babel/types": ^7.25.2 + flow-enums-runtime: ^0.0.6 + metro: 0.81.0 + metro-babel-transformer: 0.81.0 + metro-cache: 0.81.0 + metro-cache-key: 0.81.0 + metro-minify-terser: 0.81.0 + metro-source-map: 0.81.0 + metro-transform-plugins: 0.81.0 + nullthrows: ^1.1.1 + checksum: 0fa08b09f4e503183af789e39629dd0fdf4209f3453c0642cdef5e683e69644ec925bcccb2bdb3439059c11fc1418b3bcdd7dc38c768183c3deb8e2bc050e604 + languageName: node + linkType: hard + +"metro@npm:0.80.12": + version: 0.80.12 + resolution: "metro@npm:0.80.12" + dependencies: + "@babel/code-frame": ^7.0.0 + "@babel/core": ^7.20.0 + "@babel/generator": ^7.20.0 + "@babel/parser": ^7.20.0 + "@babel/template": ^7.0.0 + "@babel/traverse": ^7.20.0 + "@babel/types": ^7.20.0 + accepts: ^1.3.7 + chalk: ^4.0.0 + ci-info: ^2.0.0 + connect: ^3.6.5 + debug: ^2.2.0 + denodeify: ^1.2.1 + error-stack-parser: ^2.0.6 + flow-enums-runtime: ^0.0.6 + graceful-fs: ^4.2.4 + hermes-parser: 0.23.1 + image-size: ^1.0.2 + invariant: ^2.2.4 + jest-worker: ^29.6.3 + jsc-safe-url: ^0.2.2 + lodash.throttle: ^4.1.1 + metro-babel-transformer: 0.80.12 + metro-cache: 0.80.12 + metro-cache-key: 0.80.12 + metro-config: 0.80.12 + metro-core: 0.80.12 + metro-file-map: 0.80.12 + metro-resolver: 0.80.12 + metro-runtime: 0.80.12 + metro-source-map: 0.80.12 + metro-symbolicate: 0.80.12 + metro-transform-plugins: 0.80.12 + metro-transform-worker: 0.80.12 + mime-types: ^2.1.27 + nullthrows: ^1.1.1 + serialize-error: ^2.1.0 + source-map: ^0.5.6 + strip-ansi: ^6.0.0 + throat: ^5.0.0 + ws: ^7.5.10 + yargs: ^17.6.2 + bin: + metro: src/cli.js + checksum: 8016f7448e6e0947bd38633c01c3daad47b5a29d4a7294ebe922fa3c505430f78861d85965ecfc6f41d9b209e2663cac0f23c99a80a3f941a19de564203fcdb8 + languageName: node + linkType: hard + "metro@npm:0.80.9, metro@npm:^0.80.3": version: 0.80.9 resolution: "metro@npm:0.80.9" @@ -43215,6 +44045,58 @@ __metadata: languageName: node linkType: hard +"metro@npm:0.81.0": + version: 0.81.0 + resolution: "metro@npm:0.81.0" + dependencies: + "@babel/code-frame": ^7.24.7 + "@babel/core": ^7.25.2 + "@babel/generator": ^7.25.0 + "@babel/parser": ^7.25.3 + "@babel/template": ^7.25.0 + "@babel/traverse": ^7.25.3 + "@babel/types": ^7.25.2 + accepts: ^1.3.7 + chalk: ^4.0.0 + ci-info: ^2.0.0 + connect: ^3.6.5 + debug: ^2.2.0 + denodeify: ^1.2.1 + error-stack-parser: ^2.0.6 + flow-enums-runtime: ^0.0.6 + graceful-fs: ^4.2.4 + hermes-parser: 0.24.0 + image-size: ^1.0.2 + invariant: ^2.2.4 + jest-worker: ^29.6.3 + jsc-safe-url: ^0.2.2 + lodash.throttle: ^4.1.1 + metro-babel-transformer: 0.81.0 + metro-cache: 0.81.0 + metro-cache-key: 0.81.0 + metro-config: 0.81.0 + metro-core: 0.81.0 + metro-file-map: 0.81.0 + metro-resolver: 0.81.0 + metro-runtime: 0.81.0 + metro-source-map: 0.81.0 + metro-symbolicate: 0.81.0 + metro-transform-plugins: 0.81.0 + metro-transform-worker: 0.81.0 + mime-types: ^2.1.27 + nullthrows: ^1.1.1 + serialize-error: ^2.1.0 + source-map: ^0.5.6 + strip-ansi: ^6.0.0 + throat: ^5.0.0 + ws: ^7.5.10 + yargs: ^17.6.2 + bin: + metro: src/cli.js + checksum: 326f13e281ba696361c64b1c6bb77ff5b284771a103a78d446f7944ef8baf89e724bd2a76859c5c4e7adc9e94de2c6619755899efdde9bf1e24d3399e7c7cc00 + languageName: node + linkType: hard + "micromark-core-commonmark@npm:^1.0.0, micromark-core-commonmark@npm:^1.0.1": version: 1.1.0 resolution: "micromark-core-commonmark@npm:1.1.0" @@ -43804,15 +44686,6 @@ __metadata: languageName: node linkType: hard -"minimatch@npm:^8.0.2": - version: 8.0.4 - resolution: "minimatch@npm:8.0.4" - dependencies: - brace-expansion: ^2.0.1 - checksum: 2e46cffb86bacbc524ad45a6426f338920c529dd13f3a732cc2cf7618988ee1aae88df4ca28983285aca9e0f45222019ac2d14ebd17c1edadd2ee12221ab801a - languageName: node - linkType: hard - "minimatch@npm:^9.0.4": version: 9.0.4 resolution: "minimatch@npm:9.0.4" @@ -43938,13 +44811,6 @@ __metadata: languageName: node linkType: hard -"minipass@npm:^4.2.4": - version: 4.2.8 - resolution: "minipass@npm:4.2.8" - checksum: 7f4914d5295a9a30807cae5227a37a926e6d910c03f315930fde52332cf0575dfbc20295318f91f0baf0e6bb11a6f668e30cde8027dea7a11b9d159867a3c830 - languageName: node - linkType: hard - "minipass@npm:^5.0.0 || ^6.0.2 || ^7.0.0": version: 7.0.3 resolution: "minipass@npm:7.0.3" @@ -44538,14 +45404,14 @@ __metadata: languageName: node linkType: hard -"next-usequerystate@npm:1.8.4": - version: 1.8.4 - resolution: "next-usequerystate@npm:1.8.4" +"next-usequerystate@npm:1.20.0": + version: 1.20.0 + resolution: "next-usequerystate@npm:1.20.0" dependencies: mitt: ^3.0.1 peerDependencies: - next: ^13.4 - checksum: 4ab19aa11fd32058246375bdcd7c76fdff357e15a460e80a30835b972b7458ab99e59e8ae38be26b32d27727b156b655c2a0105c200b994408fb26366133b496 + next: ">=13.4 <14.0.2 || ^14.0.3" + checksum: fbe915a99e2374eb43c4b1c7ab9b29f4bf46c4383ee66c61ecb13d768e5552f040a90b33a0df1675c20d0cab64d62035cea1b801512f936648af78726ec6860e languageName: node linkType: hard @@ -45422,6 +46288,15 @@ __metadata: languageName: node linkType: hard +"ob1@npm:0.80.12": + version: 0.80.12 + resolution: "ob1@npm:0.80.12" + dependencies: + flow-enums-runtime: ^0.0.6 + checksum: c78af51d6ecf47ba5198bc7eb27d0456a287589533f1445e6d595e2d067f6f8038da02a98e5faa4a6c3d0c04f77c570bc9b29c652fec55518884c40c73212f17 + languageName: node + linkType: hard + "ob1@npm:0.80.9": version: 0.80.9 resolution: "ob1@npm:0.80.9" @@ -45429,6 +46304,15 @@ __metadata: languageName: node linkType: hard +"ob1@npm:0.81.0": + version: 0.81.0 + resolution: "ob1@npm:0.81.0" + dependencies: + flow-enums-runtime: ^0.0.6 + checksum: f3215ccf72604b4db5f9cfc6c83454a136a035ffd26faffec2c100d5810b87599cc95e167888320f3865959a5f9762c03de20a9e40cf66fc13706886820a9523 + languageName: node + linkType: hard + "object-assign@npm:^4, object-assign@npm:^4.0.1, object-assign@npm:^4.1.0, object-assign@npm:^4.1.1": version: 4.1.1 resolution: "object-assign@npm:4.1.1" @@ -46437,6 +47321,13 @@ __metadata: languageName: node linkType: hard +"path-dirname@npm:^1.0.2": + version: 1.0.2 + resolution: "path-dirname@npm:1.0.2" + checksum: 0d2f6604ae05a252a0025318685f290e2764ecf9c5436f203cdacfc8c0b17c24cdedaa449d766beb94ab88cc7fc70a09ec21e7933f31abc2b719180883e5e33f + languageName: node + linkType: hard + "path-exists@npm:^2.0.0": version: 2.1.0 resolution: "path-exists@npm:2.1.0" @@ -46521,7 +47412,7 @@ __metadata: languageName: node linkType: hard -"path-scurry@npm:^1.11.1, path-scurry@npm:^1.6.1": +"path-scurry@npm:^1.11.1": version: 1.11.1 resolution: "path-scurry@npm:1.11.1" dependencies: @@ -46937,15 +47828,6 @@ __metadata: languageName: node linkType: hard -"pkg-up@npm:^3.1.0": - version: 3.1.0 - resolution: "pkg-up@npm:3.1.0" - dependencies: - find-up: ^3.0.0 - checksum: 5bac346b7c7c903613c057ae3ab722f320716199d753f4a7d053d38f2b5955460f3e6ab73b4762c62fd3e947f58e04f1343e92089e7bb6091c90877406fcd8c8 - languageName: node - linkType: hard - "playwright-core@npm:1.48.2": version: 1.48.2 resolution: "playwright-core@npm:1.48.2" @@ -47678,7 +48560,7 @@ __metadata: languageName: node linkType: hard -"pretty-format@npm:^29.7.0": +"pretty-format@npm:^29.0.3, pretty-format@npm:^29.7.0": version: 29.7.0 resolution: "pretty-format@npm:29.7.0" dependencies: @@ -49147,6 +50029,21 @@ __metadata: languageName: node linkType: hard +"react-native-svg-transformer@npm:1.3.0": + version: 1.3.0 + resolution: "react-native-svg-transformer@npm:1.3.0" + dependencies: + "@svgr/core": ^8.1.0 + "@svgr/plugin-jsx": ^8.1.0 + "@svgr/plugin-svgo": ^8.1.0 + path-dirname: ^1.0.2 + peerDependencies: + react-native: ">=0.59.0" + react-native-svg: ">=12.0.0" + checksum: 09dc490aad05b8f44289fc6c1b75a2fce92b95b27cacb07f320f29c0d0edcc6979fbd6a46f0b13e6b0681c2ae4652d08af35c018bbbd29324db3579a869a4502 + languageName: node + linkType: hard + "react-native-svg@npm:15.2.0": version: 15.2.0 resolution: "react-native-svg@npm:15.2.0" @@ -49180,6 +50077,25 @@ __metadata: languageName: node linkType: hard +"react-native-web@npm:^0.19.11": + version: 0.19.13 + resolution: "react-native-web@npm:0.19.13" + dependencies: + "@babel/runtime": ^7.18.6 + "@react-native/normalize-colors": ^0.74.1 + fbjs: ^3.0.4 + inline-style-prefixer: ^6.0.1 + memoize-one: ^6.0.0 + nullthrows: ^1.1.1 + postcss-value-parser: ^4.2.0 + styleq: ^0.1.3 + peerDependencies: + react: ^18.0.0 + react-dom: ^18.0.0 + checksum: 15077f88204cb980203b8e3784c092c8c25c972bf281db43fd4ccc9696b603380a49f5289fb9a742daddd8e7599baab5798a1f1c857bdd634add827bc39fd8d8 + languageName: node + linkType: hard + "react-native-webview@npm:13.8.6": version: 13.8.6 resolution: "react-native-webview@npm:13.8.6" @@ -49623,15 +50539,6 @@ __metadata: languageName: node linkType: hard -"react@npm:18.2.0": - version: 18.2.0 - resolution: "react@npm:18.2.0" - dependencies: - loose-envify: ^1.1.0 - checksum: 88e38092da8839b830cda6feef2e8505dec8ace60579e46aa5490fc3dc9bba0bd50336507dc166f43e3afc1c42939c09fe33b25fae889d6f402721dcd78fca1b - languageName: node - linkType: hard - "react@npm:18.3.1": version: 18.3.1 resolution: "react@npm:18.3.1" @@ -50580,13 +51487,6 @@ __metadata: languageName: node linkType: hard -"reselect@npm:^4.1.7": - version: 4.1.8 - resolution: "reselect@npm:4.1.8" - checksum: a4ac87cedab198769a29be92bc221c32da76cfdad6911eda67b4d3e7136dca86208c3b210e31632eae31ebd2cded18596f0dd230d3ccc9e978df22f233b5583e - languageName: node - linkType: hard - "resize-observer-polyfill@npm:^1.5.1": version: 1.5.1 resolution: "resize-observer-polyfill@npm:1.5.1" @@ -50698,7 +51598,7 @@ __metadata: languageName: node linkType: hard -"resolve@npm:^1.22.2, resolve@npm:^1.22.4, resolve@npm:^1.22.8": +"resolve@npm:^1.22.2, resolve@npm:^1.22.4": version: 1.22.8 resolution: "resolve@npm:1.22.8" dependencies: @@ -50785,7 +51685,7 @@ __metadata: languageName: node linkType: hard -"resolve@patch:resolve@^1.22.2#~builtin<compat/resolve>, resolve@patch:resolve@^1.22.4#~builtin<compat/resolve>, resolve@patch:resolve@^1.22.8#~builtin<compat/resolve>": +"resolve@patch:resolve@^1.22.2#~builtin<compat/resolve>, resolve@patch:resolve@^1.22.4#~builtin<compat/resolve>": version: 1.22.8 resolution: "resolve@patch:resolve@npm%3A1.22.8#~builtin<compat/resolve>::version=1.22.8&hash=07638b" dependencies: @@ -53642,6 +54542,13 @@ __metadata: languageName: node linkType: hard +"styleq@npm:^0.1.3": + version: 0.1.3 + resolution: "styleq@npm:0.1.3" + checksum: 14a8d23abd914166a9b4bd04ed753bd91363f0e029ee4a94ec2c7dc37d3213fe01fceee22dc655288da3ae89f5dc01cec42d5e2b58478b0dea33bf5bdf509be1 + languageName: node + linkType: hard + "stylis@npm:4.2.0": version: 4.2.0 resolution: "stylis@npm:4.2.0" @@ -55497,6 +56404,15 @@ __metadata: languageName: node linkType: hard +"ua-parser-js@npm:^1.0.35": + version: 1.0.39 + resolution: "ua-parser-js@npm:1.0.39" + bin: + ua-parser-js: script/cli.js + checksum: 19455df8c2348ef53f2e150e7406d3a025a619c2fd69722a1e63363d5ba8d91731ef7585f2dce7d8f14c8782734b4d704c05f246dca5f7565b5ae7d318084f2a + languageName: node + linkType: hard + "uc-first-array@npm:^1.1.10": version: 1.1.10 resolution: "uc-first-array@npm:1.1.10"