From 7bc2a594ae11cc643bc7028250128221e8d5199c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BAnar=20Vestmann?= <43557895+RunarVestmann@users.noreply.github.com> Date: Thu, 19 Dec 2024 11:09:48 +0000 Subject: [PATCH 01/19] feat(web): Organization pages, boolean in CMS that'll hide pages from search engines (#17283) * Add way to hide pages from search engines * Admin only boolean field contentful app --------- Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../pages/fields/admin-only-boolean-field.tsx | 16 ++++++++++++++++ .../Organization/Wrapper/OrganizationWrapper.tsx | 9 ++++++++- apps/web/screens/queries/Organization.tsx | 1 + libs/cms/src/lib/generated/contentfulTypes.d.ts | 3 +++ .../cms/src/lib/models/organizationPage.model.ts | 4 ++++ .../search/importers/organizationPage.service.ts | 3 ++- .../importers/organizationSubpage.service.ts | 5 ++++- 7 files changed, 38 insertions(+), 3 deletions(-) create mode 100644 apps/contentful-apps/pages/fields/admin-only-boolean-field.tsx diff --git a/apps/contentful-apps/pages/fields/admin-only-boolean-field.tsx b/apps/contentful-apps/pages/fields/admin-only-boolean-field.tsx new file mode 100644 index 000000000000..3bf79f5d25a3 --- /dev/null +++ b/apps/contentful-apps/pages/fields/admin-only-boolean-field.tsx @@ -0,0 +1,16 @@ +import { FieldExtensionSDK } from '@contentful/app-sdk' +import { Paragraph } from '@contentful/f36-components' +import { BooleanEditor } from '@contentful/field-editor-boolean' +import { useSDK } from '@contentful/react-apps-toolkit' + +const AdminOnlyBooleanField = () => { + const sdk = useSDK() + + if (!sdk.user.spaceMembership.admin) { + return (Only admins can edit this field) + } + + return +} + +export default AdminOnlyBooleanField diff --git a/apps/web/components/Organization/Wrapper/OrganizationWrapper.tsx b/apps/web/components/Organization/Wrapper/OrganizationWrapper.tsx index 02e5d0341ada..07eb23a1c730 100644 --- a/apps/web/components/Organization/Wrapper/OrganizationWrapper.tsx +++ b/apps/web/components/Organization/Wrapper/OrganizationWrapper.tsx @@ -1175,6 +1175,9 @@ export const OrganizationWrapper: React.FC< const n = useNamespace(namespace) + const indexableBySearchEngine = + organizationPage.canBeFoundInSearchResults ?? true + return ( <> + > + {!indexableBySearchEngine && ( + + )} + { diff --git a/libs/cms/src/lib/models/organizationPage.model.ts b/libs/cms/src/lib/models/organizationPage.model.ts index 4ca0e4b3eb98..233b62efafff 100644 --- a/libs/cms/src/lib/models/organizationPage.model.ts +++ b/libs/cms/src/lib/models/organizationPage.model.ts @@ -86,6 +86,9 @@ export class OrganizationPage { @CacheField(() => OrganizationPageTopLevelNavigation, { nullable: true }) topLevelNavigation?: OrganizationPageTopLevelNavigation | null + + @Field(() => Boolean, { nullable: true }) + canBeFoundInSearchResults?: boolean } export const mapOrganizationPage = ({ @@ -144,5 +147,6 @@ export const mapOrganizationPage = ({ ? mapImage(fields.defaultHeaderImage) : undefined, topLevelNavigation, + canBeFoundInSearchResults: fields.canBeFoundInSearchResults ?? true, } } diff --git a/libs/cms/src/lib/search/importers/organizationPage.service.ts b/libs/cms/src/lib/search/importers/organizationPage.service.ts index 9a60a31bf272..984d64527323 100644 --- a/libs/cms/src/lib/search/importers/organizationPage.service.ts +++ b/libs/cms/src/lib/search/importers/organizationPage.service.ts @@ -15,7 +15,8 @@ export class OrganizationPageSyncService return entries.filter( (entry: Entry): entry is IOrganizationPage => entry.sys.contentType.sys.id === 'organizationPage' && - !!entry.fields.title, + !!entry.fields.title && + (entry.fields.canBeFoundInSearchResults ?? true), ) } diff --git a/libs/cms/src/lib/search/importers/organizationSubpage.service.ts b/libs/cms/src/lib/search/importers/organizationSubpage.service.ts index 7edf40d5a224..5113a03ff260 100644 --- a/libs/cms/src/lib/search/importers/organizationSubpage.service.ts +++ b/libs/cms/src/lib/search/importers/organizationSubpage.service.ts @@ -24,7 +24,10 @@ export class OrganizationSubpageSyncService !!entry.fields.slug && !!entry.fields.organizationPage?.fields?.slug && // Standalone organization pages have their own search, we don't want subpages there to be found in the global search - entry.fields.organizationPage.fields.theme !== 'standalone', + entry.fields.organizationPage.fields.theme !== 'standalone' && + // Subpage should not be searchable if the organization frontpage isn't searchable + (entry.fields.organizationPage.fields.canBeFoundInSearchResults ?? + true), ) } From 62ccd491358eda40ef9ca320b72c2bffb62ebc0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BAnar=20Vestmann?= <43557895+RunarVestmann@users.noreply.github.com> Date: Thu, 19 Dec 2024 11:34:52 +0000 Subject: [PATCH 02/19] fix(web): Overview Links, stop using Image component (#17278) Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../Slice/OverviewLinks/OverviewLinks.tsx | 29 ++++++++----------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/apps/web/components/Organization/Slice/OverviewLinks/OverviewLinks.tsx b/apps/web/components/Organization/Slice/OverviewLinks/OverviewLinks.tsx index 8b1a4604f4e3..1decf6b1bf48 100644 --- a/apps/web/components/Organization/Slice/OverviewLinks/OverviewLinks.tsx +++ b/apps/web/components/Organization/Slice/OverviewLinks/OverviewLinks.tsx @@ -55,23 +55,18 @@ export const OverviewLinksSlice: React.FC< key={index} direction={leftImage ? 'row' : 'rowReverse'} > - - - {/** - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-expect-error make web strict */} - - - + {image?.url && ( + + + + + + )} Date: Thu, 19 Dec 2024 12:01:09 +0000 Subject: [PATCH 03/19] fix(my-pages-assets): chart fixes (#17289) Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../VehicleBulkMileageSubData.tsx | 2 + .../Charts/SimpleBarChart/SimpleBarChart.tsx | 9 ++++- .../Charts/sharedChartComponents.tsx | 40 ++++++++++++------- 3 files changed, 36 insertions(+), 15 deletions(-) diff --git a/libs/portals/my-pages/assets/src/screens/VehicleBulkMileage/VehicleBulkMileageSubData.tsx b/libs/portals/my-pages/assets/src/screens/VehicleBulkMileage/VehicleBulkMileageSubData.tsx index 913851852c79..13d1dac55d2d 100644 --- a/libs/portals/my-pages/assets/src/screens/VehicleBulkMileage/VehicleBulkMileageSubData.tsx +++ b/libs/portals/my-pages/assets/src/screens/VehicleBulkMileage/VehicleBulkMileageSubData.tsx @@ -3,6 +3,7 @@ import { NestedFullTable, SimpleBarChart, formatDate, + numberFormat, } from '@island.is/portals/my-pages/core' import { Box, Text, Button } from '@island.is/island-ui/core' import { AssetsPaths } from '../../lib/paths' @@ -137,6 +138,7 @@ export const VehicleBulkMileageSubData = ({ labels: { mileage: formatMessage(vehicleMessage.odometer), }, + valueFormat: (arg: number) => `${numberFormat(arg)} km`, }} /> ) : undefined} diff --git a/libs/portals/my-pages/core/src/components/Charts/SimpleBarChart/SimpleBarChart.tsx b/libs/portals/my-pages/core/src/components/Charts/SimpleBarChart/SimpleBarChart.tsx index 419ad09aaa11..ba7bd5427c41 100644 --- a/libs/portals/my-pages/core/src/components/Charts/SimpleBarChart/SimpleBarChart.tsx +++ b/libs/portals/my-pages/core/src/components/Charts/SimpleBarChart/SimpleBarChart.tsx @@ -21,6 +21,7 @@ import { theme } from '@island.is/island-ui/theme' interface Axis { label?: string datakey: string + valueFormat?: (arg: number) => string } interface BarType { @@ -29,6 +30,7 @@ interface BarType { interface TooltipType { labels: Record + valueFormat?: (arg: number) => string } interface GraphDataProps { @@ -96,7 +98,12 @@ export const SimpleBarChart = ({ /> } /> } + content={ + + } /> string } interface CustomTooltipProps extends TooltipProps { valueLabels?: Record + valueFormat?: (arg: number) => string } export const CustomTooltip = ({ @@ -20,25 +23,34 @@ export const CustomTooltip = ({ active, label, valueLabels, + valueFormat, }: CustomTooltipProps) => { if (active && payload && payload.length) { return ( {label} - {payload.map((item, index) => ( - -
- - {valueLabels && item.name ? valueLabels[item.name] : item.name} : - {item.value} - - - ))} + {payload + .map((item, index) => { + if (!item.value) return null + + return ( + +
+ + {valueLabels && item.dataKey + ? valueLabels[item.dataKey] + : item.name} + : {valueFormat ? valueFormat(item.value) : item.value} + + + ) + }) + .filter(isDefined)} ) } From 1b5773dd7b096826fd60553260addfb833ac6457 Mon Sep 17 00:00:00 2001 From: norda-gunni <161026627+norda-gunni@users.noreply.github.com> Date: Thu, 19 Dec 2024 12:27:14 +0000 Subject: [PATCH 04/19] fix(application-system): Fix post-cleanup codegen (#17293) Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../templates/accident-notification/src/utils/miscUtils.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/libs/application/templates/accident-notification/src/utils/miscUtils.ts b/libs/application/templates/accident-notification/src/utils/miscUtils.ts index bee5dd87bf40..5ae53a9533cc 100644 --- a/libs/application/templates/accident-notification/src/utils/miscUtils.ts +++ b/libs/application/templates/accident-notification/src/utils/miscUtils.ts @@ -1,4 +1,3 @@ -import { AccidentNotificationConfirmation } from '@island.is/api/schema' import { getValueViaPath } from '@island.is/application/core' import { FormValue, YES } from '@island.is/application/types' import { isReportingOnBehalfOfEmployee as isReportingOnBehalfOfEmployeeOrginal } from './reportingUtils' @@ -28,10 +27,14 @@ export const formatPhonenumber = (value: string) => { } export const hasReceivedConfirmation = (answers: FormValue) => { + // The fetched value is actually typed as AccidentNotificationConfirmation, but importing that type breaks when codegen is run after cleanup const accidentConfirmations = getValueViaPath( answers, 'accidentStatus.receivedConfirmations', - ) as AccidentNotificationConfirmation + ) as { + InjuredOrRepresentativeParty: boolean | undefined + CompanyParty: boolean | undefined + } // if juridical person then the injured or the power of attorney holder has to confirm if (isReportingOnBehalfOfEmployee(answers)) { From 075a58e4f1df67482568918abf4c174fbea5c26b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BAnar=20Vestmann?= <43557895+RunarVestmann@users.noreply.github.com> Date: Thu, 19 Dec 2024 12:56:15 +0000 Subject: [PATCH 05/19] fix(web): RSS feed - Lowercase t in "contentType" query param (#17282) Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- apps/web/pages/api/rss/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/pages/api/rss/index.ts b/apps/web/pages/api/rss/index.ts index d16757d81b47..87ac31be6f16 100644 --- a/apps/web/pages/api/rss/index.ts +++ b/apps/web/pages/api/rss/index.ts @@ -77,7 +77,7 @@ export default async function handler( const contentType = parseAsStringEnum(CONTENT_TYPES) .withDefault('news') - .parseServerSide(req.query?.contentType) as 'news' | 'genericList' | 'event' + .parseServerSide(req.query?.contenttype) as 'news' | 'genericList' | 'event' const apolloClient = initApollo({}, locale) From 6167d4f5b3170c93705e3e8579252aa45559fac9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81sd=C3=ADs=20Erna=20Gu=C3=B0mundsd=C3=B3ttir?= Date: Thu, 19 Dec 2024 13:07:00 +0000 Subject: [PATCH 06/19] feat(my-pages): add back "other" for organ donation registration (#17228) * feat: add back "other" for organ donation registration * refactor: update api client for organ donations * refactor: texts * fix: as * fix: type safety --------- Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../src/lib/health-directorate.service.ts | 20 ++- .../src/lib/models/organ-donation.model.ts | 9 ++ .../clients/organ-donation/clientConfig.json | 130 +++++++++--------- .../my-pages/health/src/lib/messages.ts | 12 ++ libs/portals/my-pages/health/src/module.tsx | 2 +- .../OrganDonation/OrganDonation.css.ts | 32 +++++ .../OrganDonation/OrganDonation.graphql | 17 +-- .../screens/OrganDonation/OrganDonation.tsx | 113 +++++++++------ .../OrganDonation/components/Limitations.tsx | 113 +++++++++++++++ .../components}/Loader.tsx | 2 +- .../OrganDonation/components/NoAccess.tsx | 13 ++ .../components}/RegistrationForm.tsx | 66 +++++---- .../OrganDonation/helpers/textMapper.ts | 27 ++++ .../OrganDonationRegistration/Limitations.tsx | 77 ----------- .../OrganDonationRegistration.css.ts | 6 - 15 files changed, 403 insertions(+), 236 deletions(-) create mode 100644 libs/portals/my-pages/health/src/screens/OrganDonation/OrganDonation.css.ts create mode 100644 libs/portals/my-pages/health/src/screens/OrganDonation/components/Limitations.tsx rename libs/portals/my-pages/health/src/screens/{OrganDonationRegistration => OrganDonation/components}/Loader.tsx (100%) create mode 100644 libs/portals/my-pages/health/src/screens/OrganDonation/components/NoAccess.tsx rename libs/portals/my-pages/health/src/screens/{OrganDonationRegistration => OrganDonation/components}/RegistrationForm.tsx (77%) create mode 100644 libs/portals/my-pages/health/src/screens/OrganDonation/helpers/textMapper.ts delete mode 100644 libs/portals/my-pages/health/src/screens/OrganDonationRegistration/Limitations.tsx delete mode 100644 libs/portals/my-pages/health/src/screens/OrganDonationRegistration/OrganDonationRegistration.css.ts diff --git a/libs/api/domains/health-directorate/src/lib/health-directorate.service.ts b/libs/api/domains/health-directorate/src/lib/health-directorate.service.ts index 9c4bdfc139ed..61395b471319 100644 --- a/libs/api/domains/health-directorate/src/lib/health-directorate.service.ts +++ b/libs/api/domains/health-directorate/src/lib/health-directorate.service.ts @@ -28,14 +28,20 @@ export class HealthDirectorateService { if (data === null) { return null } + const hasExceptionComment: boolean = + data.exceptionComment !== undefined && data.exceptionComment.length > 0 + const hasExceptions: boolean = + data.exceptions !== undefined && data.exceptions.length > 0 const donorStatus: Donor = { - isDonor: data?.isDonor ?? true, + isDonor: data.isDonor, limitations: { hasLimitations: - ((data?.exceptions?.length ?? 0) > 0 && data?.isDonor) ?? false, - limitedOrgansList: data?.exceptions, - comment: data?.exceptionComment, + ((hasExceptionComment || hasExceptions) && data.isDonor) ?? false, + limitedOrgansList: data.exceptions, + comment: data.exceptionComment, }, + isMinor: data.isMinor ?? false, + isTemporaryResident: data.isTemporaryResident ?? false, } return donorStatus } @@ -62,11 +68,15 @@ export class HealthDirectorateService { input: DonorInput, locale: Locale, ): Promise { + const filteredList = + input.organLimitations?.filter((item) => item !== 'other') ?? [] + return await this.organDonationApi.updateOrganDonation( auth, { isDonor: input.isDonor, - exceptions: input.organLimitations ?? [], + exceptions: filteredList, + exceptionComment: input.comment, }, locale === 'is' ? organLocale.Is : organLocale.En, ) diff --git a/libs/api/domains/health-directorate/src/lib/models/organ-donation.model.ts b/libs/api/domains/health-directorate/src/lib/models/organ-donation.model.ts index 8893f2149324..824124479661 100644 --- a/libs/api/domains/health-directorate/src/lib/models/organ-donation.model.ts +++ b/libs/api/domains/health-directorate/src/lib/models/organ-donation.model.ts @@ -33,6 +33,12 @@ export class Donor { @Field(() => Limitations, { nullable: true }) limitations?: Limitations + + @Field(() => Boolean, { defaultValue: false }) + isMinor!: boolean + + @Field(() => Boolean, { defaultValue: false }) + isTemporaryResident!: boolean } @ObjectType('HealthDirectorateOrganDonation') @@ -54,4 +60,7 @@ export class DonorInput { @Field(() => [String], { nullable: true }) organLimitations?: string[] + + @Field({ nullable: true }) + comment?: string } diff --git a/libs/clients/health-directorate/src/lib/clients/organ-donation/clientConfig.json b/libs/clients/health-directorate/src/lib/clients/organ-donation/clientConfig.json index b83056a0484c..8612145527f7 100644 --- a/libs/clients/health-directorate/src/lib/clients/organ-donation/clientConfig.json +++ b/libs/clients/health-directorate/src/lib/clients/organ-donation/clientConfig.json @@ -1,18 +1,11 @@ { "openapi": "3.0.0", "paths": { - "/v1/me/organ-donor-status": { + "/v1/donation-exceptions": { "get": { - "operationId": "MeDonorStatusController_getOrganDonorStatus", - "description": "Get user's donation-exception donor status", + "operationId": "DonationExceptionController_getOrgans", + "description": "Gets a list of organs that can be omitted from an donation-exception donation", "parameters": [ - { - "name": "ip", - "required": false, - "in": "query", - "description": "The IP address of the user", - "schema": { "type": "string" } - }, { "name": "locale", "required": false, @@ -26,7 +19,10 @@ "description": "", "content": { "application/json": { - "schema": { "$ref": "#/components/schemas/OrganDonorDto" } + "schema": { + "type": "array", + "items": { "$ref": "#/components/schemas/OrganDto" } + } } } }, @@ -63,37 +59,38 @@ } } }, - "tags": ["me/organ-donor-status"] - }, - "post": { - "operationId": "MeDonorStatusController_updateOrganDonorStatus", - "description": "Update user's donation-exception donor status", + "tags": ["donation-exceptions"] + } + }, + "/v1/me/organ-donor-status": { + "get": { + "operationId": "MeDonorStatusController_getOrganDonorStatus", + "description": "Get user's donation-exception donor status", "parameters": [ { - "name": "ip", + "name": "locale", "required": false, "in": "query", - "description": "The IP address of the user", - "schema": { "type": "string" } + "description": "The locale to use for the response", + "schema": { "$ref": "#/components/schemas/Locale" } }, { - "name": "locale", + "name": "ip", "required": false, "in": "query", - "description": "The locale to use for the response", - "schema": { "$ref": "#/components/schemas/Locale" } + "description": "The IP address of the user", + "schema": { "type": "string" } } ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/UpdateOrganDonorDto" } - } - } - }, "responses": { - "200": { "description": "" }, + "200": { + "description": "", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/OrganDonorDto" } + } + } + }, "400": { "description": "", "content": { @@ -128,12 +125,10 @@ } }, "tags": ["me/organ-donor-status"] - } - }, - "/v1/donation-exceptions": { - "get": { - "operationId": "DonationExceptionController_getOrgans", - "description": "Gets a list of organs that can be omitted from an donation-exception donation", + }, + "post": { + "operationId": "MeDonorStatusController_updateOrganDonorStatus", + "description": "Update user's donation-exception donor status", "parameters": [ { "name": "locale", @@ -141,20 +136,25 @@ "in": "query", "description": "The locale to use for the response", "schema": { "$ref": "#/components/schemas/Locale" } + }, + { + "name": "ip", + "required": false, + "in": "query", + "description": "The IP address of the user", + "schema": { "type": "string" } } ], - "responses": { - "200": { - "description": "", - "content": { - "application/json": { - "schema": { - "type": "array", - "items": { "$ref": "#/components/schemas/OrganDto" } - } - } + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/UpdateOrganDonorDto" } } - }, + } + }, + "responses": { + "200": { "description": "" }, "400": { "description": "", "content": { @@ -188,7 +188,7 @@ } } }, - "tags": ["donation-exceptions"] + "tags": ["me/organ-donor-status"] } } }, @@ -226,24 +226,11 @@ }, "required": ["id", "name"] }, - "OrganDonorDto": { - "type": "object", - "properties": { - "isDonor": { "type": "boolean" }, - "exceptions": { - "type": "array", - "items": { "$ref": "#/components/schemas/OrganDto" } - }, - "exceptionComment": { "type": "string" }, - "registrationDate": { "format": "date-time", "type": "string" } - }, - "required": ["isDonor", "exceptions"] - }, "HttpProblemResponse": { "type": "object", "properties": { "type": { - "type": "object", + "type": "string", "description": "A URI reference that identifies the problem type" }, "title": { @@ -262,12 +249,27 @@ }, "required": ["type", "title"] }, + "OrganDonorDto": { + "type": "object", + "properties": { + "isDonor": { "type": "boolean" }, + "exceptions": { + "type": "array", + "items": { "$ref": "#/components/schemas/OrganDto" } + }, + "exceptionComment": { "type": "string" }, + "registrationDate": { "format": "date-time", "type": "string" }, + "isMinor": { "type": "boolean" }, + "isTemporaryResident": { "type": "boolean" } + }, + "required": ["isDonor", "exceptions"] + }, "UpdateOrganDonorDto": { "type": "object", "properties": { "isDonor": { "type": "boolean" }, "exceptions": { "type": "array", "items": { "type": "string" } }, - "exceptionComment": { "type": "object" } + "exceptionComment": { "type": "string" } }, "required": ["isDonor", "exceptions"] } diff --git a/libs/portals/my-pages/health/src/lib/messages.ts b/libs/portals/my-pages/health/src/lib/messages.ts index d22f5798bed3..67c32b9383c0 100644 --- a/libs/portals/my-pages/health/src/lib/messages.ts +++ b/libs/portals/my-pages/health/src/lib/messages.ts @@ -1024,10 +1024,22 @@ export const messages = defineMessages({ defaultMessage: 'Textareitur má ekki vera tómur sé þessi valkostur valinn. ', }, + organMinor: { + id: 'sp.health:organ-minor', + defaultMessage: 'Til að geta gerst líffæragjafi þarftu að vera 18 ára.', + }, + organTemporaryNationalId: { + id: 'sp.health:organ-temporary-national-id', + defaultMessage: 'Líffæragjöf er ekki heimiluð á kerfiskennitölur.', + }, other: { id: 'sp.health:other-lower-case', defaultMessage: 'annað', }, + otherPascalCase: { + id: 'sp.health:other', + defaultMessage: 'Annað', + }, registrationComplete: { id: 'sp.health:registration-complete', defaultMessage: 'Skráning tókst', diff --git a/libs/portals/my-pages/health/src/module.tsx b/libs/portals/my-pages/health/src/module.tsx index 5d9cc4fdfccd..2f6f92805030 100644 --- a/libs/portals/my-pages/health/src/module.tsx +++ b/libs/portals/my-pages/health/src/module.tsx @@ -59,7 +59,7 @@ const OrganDonation = lazy(() => ) const OrganDonationRegistration = lazy(() => - import('./screens/OrganDonationRegistration/RegistrationForm'), + import('./screens/OrganDonation/components/RegistrationForm'), ) const Vaccinations = lazy(() => diff --git a/libs/portals/my-pages/health/src/screens/OrganDonation/OrganDonation.css.ts b/libs/portals/my-pages/health/src/screens/OrganDonation/OrganDonation.css.ts new file mode 100644 index 000000000000..4dfc4127851b --- /dev/null +++ b/libs/portals/my-pages/health/src/screens/OrganDonation/OrganDonation.css.ts @@ -0,0 +1,32 @@ +import { style, keyframes } from '@vanilla-extract/css' +import { theme } from '@island.is/island-ui/theme' + +export const buttonContainer = style({ + gap: theme.spacing[2], +}) + +const fadeIn = keyframes({ + from: { + opacity: 0, + }, + to: { + opacity: 1, + }, +}) + +const fadeOut = keyframes({ + from: { + opacity: 1, + }, + to: { + opacity: 0, + }, +}) + +export const commentVisible = style({ + animation: `${fadeIn} 0.5s forwards`, +}) + +export const commentHidden = style({ + animation: `${fadeOut} 0.5s forwards`, +}) diff --git a/libs/portals/my-pages/health/src/screens/OrganDonation/OrganDonation.graphql b/libs/portals/my-pages/health/src/screens/OrganDonation/OrganDonation.graphql index d120a592b167..6790da16ee2b 100644 --- a/libs/portals/my-pages/health/src/screens/OrganDonation/OrganDonation.graphql +++ b/libs/portals/my-pages/health/src/screens/OrganDonation/OrganDonation.graphql @@ -10,21 +10,8 @@ query getDonorStatus($locale: String) { } comment } - } - } -} - -query getOrgansList($locale: String) { - healthDirectorateOrganDonation(locale: $locale) { - donor { - isDonor - limitations { - hasLimitations - limitedOrgansList { - id - name - } - } + isMinor + isTemporaryResident } organList { id diff --git a/libs/portals/my-pages/health/src/screens/OrganDonation/OrganDonation.tsx b/libs/portals/my-pages/health/src/screens/OrganDonation/OrganDonation.tsx index 7efcd493e3b5..7ef00c370fb5 100644 --- a/libs/portals/my-pages/health/src/screens/OrganDonation/OrganDonation.tsx +++ b/libs/portals/my-pages/health/src/screens/OrganDonation/OrganDonation.tsx @@ -1,15 +1,17 @@ +import { Box, Button, Text } from '@island.is/island-ui/core' import { useLocale, useNamespaces } from '@island.is/localization' import { ActionCard, CardLoader, - IntroHeader, + IntroWrapper, LinkResolver, } from '@island.is/portals/my-pages/core' +import { Problem } from '@island.is/react-spa/shared' import { messages as m } from '../../lib/messages' -import { Button, Box, Text } from '@island.is/island-ui/core' import { HealthPaths } from '../../lib/paths' -import { Problem } from '@island.is/react-spa/shared' import { useGetDonorStatusQuery } from './OrganDonation.generated' +import { NoAccess } from './components/NoAccess' +import { getOrganText } from './helpers/textMapper' const OrganDonation = () => { useNamespaces('sp.health') @@ -22,30 +24,39 @@ const OrganDonation = () => { }, }) const donorStatus = data?.healthDirectorateOrganDonation.donor - const cardText: string = donorStatus?.isDonor - ? donorStatus?.limitations?.hasLimitations - ? [ - formatMessage(m.iAmOrganDonorWithExceptionsText), - donorStatus?.limitations.limitedOrgansList - ?.map((organ) => organ.name) - .join(', '), - ].join(' ') + '.' - : formatMessage(m.iAmOrganDonorText) - : formatMessage(m.iAmNotOrganDonorText) + const isMinor = donorStatus?.isMinor + const isTemporaryResident = donorStatus?.isTemporaryResident + + const comment = donorStatus?.limitations?.comment + + const allLimitations: Array = + donorStatus?.limitations?.limitedOrgansList?.map((item) => item.name) ?? [] - const heading = donorStatus?.isDonor - ? donorStatus.limitations?.hasLimitations - ? formatMessage(m.iAmOrganDonorWithExceptions) - : formatMessage(m.iAmOrganDonor) - : formatMessage(m.iAmNotOrganDonor) + if (comment !== undefined && comment !== null && comment.length > 0) { + allLimitations.push(comment) + } + + const texts = getOrganText( + donorStatus?.isDonor ?? true, + donorStatus?.limitations?.hasLimitations ?? false, + { + iAmOrganDonorWithExceptionsText: formatMessage( + m.iAmOrganDonorWithExceptionsText, + ), + iAmNotOrganDonorText: formatMessage(m.iAmOrganDonorText), + iAmOrganDonorText: formatMessage(m.iAmNotOrganDonorText), + iAmOrganDonorWithExceptions: formatMessage(m.iAmOrganDonorWithExceptions), + iAmOrganDonor: formatMessage(m.iAmOrganDonor), + iAmNotOrganDonor: formatMessage(m.iAmNotOrganDonor), + }, + allLimitations, + ) return ( - - - + { - - + , + ]} + > {loading && ( )} - {!error && !loading && donorStatus !== null && ( - - - {formatMessage(m.takeOnOrganDonation)} - - - + {!error && + !loading && + !isMinor && + !isTemporaryResident && + donorStatus !== null && ( + + + {formatMessage(m.takeOnOrganDonation)} + + + + )} + {!error && !loading && (isMinor || isTemporaryResident) && ( + )} {error && !loading && } {!error && !loading && data === null && ( )} - + ) } diff --git a/libs/portals/my-pages/health/src/screens/OrganDonation/components/Limitations.tsx b/libs/portals/my-pages/health/src/screens/OrganDonation/components/Limitations.tsx new file mode 100644 index 000000000000..ab0e895e30a3 --- /dev/null +++ b/libs/portals/my-pages/health/src/screens/OrganDonation/components/Limitations.tsx @@ -0,0 +1,113 @@ +import { HealthDirectorateOrganDonationOrgan } from '@island.is/api/schema' +import { + Box, + Checkbox, + Divider, + GridColumn, + GridContainer, + GridRow, + Input, + Stack, +} from '@island.is/island-ui/core' +import { useLocale, useNamespaces } from '@island.is/localization' +import { useState } from 'react' +import { messages } from '../../../lib/messages' +import * as styles from '../OrganDonation.css' + +interface LimitationsProps { + data: HealthDirectorateOrganDonationOrgan[] + selected?: string[] | null + exceptionComment?: string +} + +const Limitations = ({ + data, + selected, + exceptionComment, +}: LimitationsProps) => { + useNamespaces('sp.health') + const { formatMessage } = useLocale() + const [checked, setChecked] = useState>(selected ?? []) + const [comment, setComment] = useState(exceptionComment ?? '') + + const handleCheckboxChange = (id: string, isChecked: boolean) => { + setChecked((prevState) => + isChecked ? [...prevState, id] : prevState.filter((item) => item !== id), + ) + } + + return ( + + + + + {data?.map((y, yi) => ( + + + handleCheckboxChange(y.id ?? '', e.target.checked) + } + checked={checked.includes(y.id ?? '')} + /> + + ))} + + { + setComment(e.target.value) + handleCheckboxChange('other', e.target.checked) + }} + checked={checked.includes('other')} + /> + + + + {checked.includes('other') && ( + + + + + setComment(e.target.value)} + value={comment} + /> + + + + + )} + + ) +} + +export default Limitations diff --git a/libs/portals/my-pages/health/src/screens/OrganDonationRegistration/Loader.tsx b/libs/portals/my-pages/health/src/screens/OrganDonation/components/Loader.tsx similarity index 100% rename from libs/portals/my-pages/health/src/screens/OrganDonationRegistration/Loader.tsx rename to libs/portals/my-pages/health/src/screens/OrganDonation/components/Loader.tsx index b907c382c105..59bf49245f20 100644 --- a/libs/portals/my-pages/health/src/screens/OrganDonationRegistration/Loader.tsx +++ b/libs/portals/my-pages/health/src/screens/OrganDonation/components/Loader.tsx @@ -1,6 +1,6 @@ -import React from 'react' import { Box, SkeletonLoader, Stack } from '@island.is/island-ui/core' import { useIsMobile } from '@island.is/portals/my-pages/core' +import React from 'react' interface Props { amount?: number diff --git a/libs/portals/my-pages/health/src/screens/OrganDonation/components/NoAccess.tsx b/libs/portals/my-pages/health/src/screens/OrganDonation/components/NoAccess.tsx new file mode 100644 index 000000000000..369589995cd1 --- /dev/null +++ b/libs/portals/my-pages/health/src/screens/OrganDonation/components/NoAccess.tsx @@ -0,0 +1,13 @@ +import { AlertMessage, GridColumn, GridRow } from '@island.is/island-ui/core' + +export const NoAccess = ({ text }: { text: string }) => { + return ( + + + + + + ) +} + +export default NoAccess diff --git a/libs/portals/my-pages/health/src/screens/OrganDonationRegistration/RegistrationForm.tsx b/libs/portals/my-pages/health/src/screens/OrganDonation/components/RegistrationForm.tsx similarity index 77% rename from libs/portals/my-pages/health/src/screens/OrganDonationRegistration/RegistrationForm.tsx rename to libs/portals/my-pages/health/src/screens/OrganDonation/components/RegistrationForm.tsx index 6ef3db623dcf..4b4db5a87fcf 100644 --- a/libs/portals/my-pages/health/src/screens/OrganDonationRegistration/RegistrationForm.tsx +++ b/libs/portals/my-pages/health/src/screens/OrganDonation/components/RegistrationForm.tsx @@ -1,58 +1,68 @@ import { Box, + Button, RadioButton, Stack, Text, - Button, toast, - LoadingDots, } from '@island.is/island-ui/core' import { useLocale, useNamespaces } from '@island.is/localization' import { - IntroHeader, + IntroWrapper, LinkResolver, m as coreMessages, } from '@island.is/portals/my-pages/core' -import { messages } from '../..' -import { useEffect, useState } from 'react' -import React from 'react' -import { HealthPaths } from '../../lib/paths' -import * as styles from './OrganDonationRegistration.css' -import Limitations from './Limitations' +import React, { useEffect, useState } from 'react' import { useNavigate } from 'react-router-dom' +import { messages } from '../../..' +import { HealthPaths } from '../../../lib/paths' +import * as styles from '../OrganDonation.css' import { - useGetOrgansListQuery, + useGetDonorStatusQuery, useUpdateOrganDonationInfoMutation, -} from '../OrganDonation/OrganDonation.generated' +} from '../OrganDonation.generated' +import Limitations from './Limitations' import { Loader } from './Loader' +import { NoAccess } from './NoAccess' const OPT_IN = 'opt-in' const OPT_IN_EXCEPTIONS = 'opt-in-exceptions' const OPT_OUT = 'opt-out' -export const Form2 = () => { +export const OrganRegistrationForm = () => { useNamespaces('sp.health') const { formatMessage, lang } = useLocale() const navigate = useNavigate() - const { data, loading } = useGetOrgansListQuery({ + const { data, loading } = useGetDonorStatusQuery({ variables: { locale: lang }, fetchPolicy: 'no-cache', }) const isDonor = data?.healthDirectorateOrganDonation.donor?.isDonor + const isMinor = data?.healthDirectorateOrganDonation.donor?.isMinor + const isTemporaryResident = + data?.healthDirectorateOrganDonation.donor?.isTemporaryResident const hasLimitations = data?.healthDirectorateOrganDonation.donor?.limitations?.hasLimitations const allLimitations = data?.healthDirectorateOrganDonation.organList + const exceptionComment = + data?.healthDirectorateOrganDonation.donor?.limitations?.comment + const selectedLimitations = data?.healthDirectorateOrganDonation.donor?.limitations?.limitedOrgansList?.map( (item) => item.id, ) + + const updatedLimitations = selectedLimitations + ? [...selectedLimitations, ...(exceptionComment?.length ? ['other'] : [])] + : [] const donorStatus = isDonor ? hasLimitations ? OPT_IN_EXCEPTIONS : OPT_IN : OPT_OUT + const [radioValue, setRadioValue] = useState(donorStatus) const [updateDonorStatus, { loading: submitLoading }] = @@ -76,8 +86,8 @@ export const Form2 = () => { e.preventDefault() const formData = new FormData(e.currentTarget) const data = Object.fromEntries(formData.entries()) - const idKey = 'selected-limitations-' + const otherLimitations = data['otherLimitatons'].toString() const limitations = Object.keys(data) .filter((key) => key.includes(idKey)) .map((key) => key.replace(idKey, '').toLowerCase()) @@ -87,6 +97,7 @@ export const Form2 = () => { input: { isDonor: radioValue === OPT_IN || radioValue === OPT_IN_EXCEPTIONS, organLimitations: radioValue === OPT_IN_EXCEPTIONS ? limitations : [], + comment: otherLimitations, }, locale: lang, }, @@ -94,16 +105,24 @@ export const Form2 = () => { } return ( - - + {formatMessage(messages.changeTake)} {loading && } - {!loading && ( + {!loading && (isMinor || isTemporaryResident) && ( + + )} + {!loading && !isMinor && !isTemporaryResident && (
{ radioValue === OPT_IN_EXCEPTIONS && ( )} @@ -187,8 +207,8 @@ export const Form2 = () => { )} -
+ ) } -export default Form2 +export default OrganRegistrationForm diff --git a/libs/portals/my-pages/health/src/screens/OrganDonation/helpers/textMapper.ts b/libs/portals/my-pages/health/src/screens/OrganDonation/helpers/textMapper.ts new file mode 100644 index 000000000000..7793edbf10bf --- /dev/null +++ b/libs/portals/my-pages/health/src/screens/OrganDonation/helpers/textMapper.ts @@ -0,0 +1,27 @@ +export const getOrganText = ( + isDonor: boolean, + hasLimitations: boolean, + texts: { + iAmOrganDonorWithExceptionsText: string + iAmOrganDonorText: string + iAmNotOrganDonorText: string + iAmOrganDonorWithExceptions: string + iAmOrganDonor: string + iAmNotOrganDonor: string + }, + limitations: string[], +) => { + const limitationText = hasLimitations + ? texts.iAmOrganDonorWithExceptionsText + ' ' + limitations.join(', ') + : texts.iAmOrganDonorText + + const cardText: string = isDonor ? limitationText : texts.iAmNotOrganDonorText + + const heading = isDonor + ? hasLimitations + ? texts.iAmOrganDonorWithExceptions + : texts.iAmOrganDonor + : texts.iAmNotOrganDonor + + return { cardText, heading } +} diff --git a/libs/portals/my-pages/health/src/screens/OrganDonationRegistration/Limitations.tsx b/libs/portals/my-pages/health/src/screens/OrganDonationRegistration/Limitations.tsx deleted file mode 100644 index e625cdf7a3a4..000000000000 --- a/libs/portals/my-pages/health/src/screens/OrganDonationRegistration/Limitations.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import { Box, Checkbox, Divider, Stack } from '@island.is/island-ui/core' -import React, { useState } from 'react' -import { useNamespaces } from '@island.is/localization' -import { HealthDirectorateOrganDonationOrgan } from '@island.is/api/schema' - -interface LimitationsProps { - data: HealthDirectorateOrganDonationOrgan[] - selected?: string[] | null -} - -const Limitations = ({ data, selected }: LimitationsProps) => { - useNamespaces('sp.health') - const [checked, setChecked] = useState>(selected ?? []) - const handleCheckboxChange = (id: string, isChecked: boolean) => { - setChecked((prevState) => - isChecked ? [...prevState, id] : prevState.filter((item) => item !== id), - ) - } - - //const input = data.find((x) => x.type === 'input') - - return ( - - - - - {data?.map( - (y, yi) => ( - // y.type === 'checkbox' && ( - - - - handleCheckboxChange(y.id ?? '', e.target.checked) - } - checked={checked.includes(y.id ?? '')} - /> - - ), - // ), - )} - - - {/* This is commented out because of feature that was removed. May be included later on */} - {/* {input && checked.includes(input.name.toLowerCase()) && ( - - - - - - - - - - )} */} - - ) -} - -export default Limitations diff --git a/libs/portals/my-pages/health/src/screens/OrganDonationRegistration/OrganDonationRegistration.css.ts b/libs/portals/my-pages/health/src/screens/OrganDonationRegistration/OrganDonationRegistration.css.ts deleted file mode 100644 index e985fe3863ac..000000000000 --- a/libs/portals/my-pages/health/src/screens/OrganDonationRegistration/OrganDonationRegistration.css.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { style } from '@vanilla-extract/css' -import { theme } from '@island.is/island-ui/theme' - -export const buttonContainer = style({ - gap: theme.spacing[2], -}) From 1712305ea6eb10d4d3e48edd1bb00dddb07a35e1 Mon Sep 17 00:00:00 2001 From: unakb Date: Thu, 19 Dec 2024 13:29:35 +0000 Subject: [PATCH 07/19] feat(j-s): Allow defenders to filter cases by court date (#17232) Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../Cases/components/DefenderCasesTable.tsx | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/apps/judicial-system/web/src/routes/Defender/Cases/components/DefenderCasesTable.tsx b/apps/judicial-system/web/src/routes/Defender/Cases/components/DefenderCasesTable.tsx index 169584028e9b..7a52c93d46ff 100644 --- a/apps/judicial-system/web/src/routes/Defender/Cases/components/DefenderCasesTable.tsx +++ b/apps/judicial-system/web/src/routes/Defender/Cases/components/DefenderCasesTable.tsx @@ -57,6 +57,9 @@ export const DefenderCasesTable: FC = ({ ) { return entry.defendants[0].name ?? '' } + if (column === 'courtDate') { + return entry.courtDate + } return entry.created } const { sortedData, requestSort, getClassNamesFor, isActiveColumn } = useSort( @@ -129,9 +132,13 @@ export const DefenderCasesTable: FC = ({ ) : ( - - {formatMessage(tables.hearingArrangementDate)} - + requestSort('courtDate')} + sortAsc={getClassNamesFor('courtDate') === 'ascending'} + sortDes={getClassNamesFor('courtDate') === 'descending'} + isActive={isActiveColumn('courtDate')} + /> )} From 07f6c775721be5b0691f18d7c5af3a52231748e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B3n=20Bjarni=20=C3=93lafsson?= <92530555+jonbjarnio@users.noreply.github.com> Date: Thu, 19 Dec 2024 13:48:48 +0000 Subject: [PATCH 08/19] feat(ojoi): Advert main types (#17275) * Updated data schema to make less api calls, fixed bug when user selected a previous advert the inputs fields were not updating. Switched out controller components since we have our own. * Revert "Updated data schema to make less api calls, fixed bug when user selected a previous advert the inputs fields were not updating. Switched out controller components since we have our own." This reverts commit 081223e2598294e838d230e8829010af4c791e52. * Updated client to latest, setup graphql layer to fetch main types. * Advert main types now fully intergrated into the application * Updated the property half to width to match boxprops --------- Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../src/lib/models/advert.input.ts | 26 +- .../src/lib/models/advert.model.ts | 18 + .../src/lib/models/advert.response.ts | 10 + .../lib/officialJournalOfIceland.resolver.ts | 9 + .../lib/officialJournalOfIceland.service.ts | 6 + .../src/components/htmlEditor/HTMLEditor.tsx | 12 +- .../components/input/OJOISelectController.tsx | 64 +- .../src/fields/Advert.tsx | 101 +-- .../src/fields/AdvertModal.tsx | 19 +- .../src/graphql/queries.ts | 32 + .../src/hooks/useTypes.ts | 43 +- .../src/lib/constants.ts | 4 + .../src/lib/dataSchema.ts | 3 + .../src/lib/messages/advert.ts | 16 +- .../src/lib/types.ts | 1 + .../src/lib/utils.ts | 10 + .../src/screens/InvolvedPartyScreen.tsx | 29 +- .../public/src/clientConfig.json | 576 ++++++++++++++---- .../officialJournalOfIcelandClient.service.ts | 20 +- 19 files changed, 774 insertions(+), 225 deletions(-) diff --git a/libs/api/domains/official-journal-of-iceland/src/lib/models/advert.input.ts b/libs/api/domains/official-journal-of-iceland/src/lib/models/advert.input.ts index 868f78ae3130..0e17f4f59ecd 100644 --- a/libs/api/domains/official-journal-of-iceland/src/lib/models/advert.input.ts +++ b/libs/api/domains/official-journal-of-iceland/src/lib/models/advert.input.ts @@ -1,5 +1,5 @@ import { AdvertSignatureTypeEnum } from '@island.is/clients/official-journal-of-iceland' -import { InputType, Field, registerEnumType } from '@nestjs/graphql' +import { InputType, Field, registerEnumType, Int } from '@nestjs/graphql' registerEnumType(AdvertSignatureTypeEnum, { name: 'OfficialJournalOfIcelandAdvertSignatureType', @@ -10,10 +10,10 @@ export class AdvertsInput { @Field(() => String, { nullable: true }) search?: string - @Field(() => Number, { nullable: true }) + @Field(() => Int, { nullable: true }) page?: number - @Field(() => Number, { nullable: true }) + @Field(() => Int, { nullable: true }) pageSize?: number @Field(() => [String], { nullable: true }) @@ -43,10 +43,10 @@ export class TypeQueryParams { @Field(() => String, { nullable: true }) department?: string - @Field(() => Number, { nullable: true }) + @Field(() => Int, { nullable: true }) page?: number - @Field(() => Number, { nullable: true }) + @Field(() => Int, { nullable: true }) pageSize?: number } @@ -61,10 +61,10 @@ export class QueryParams { @Field(() => String, { nullable: true }) search?: string - @Field(() => Number, { nullable: true }) + @Field(() => Int, { nullable: true }) page?: number - @Field(() => Number, { nullable: true }) + @Field(() => Int, { nullable: true }) pageSize?: number } @@ -134,3 +134,15 @@ export class SubmitApplicationInput { @Field(() => AdvertSignature) signature!: AdvertSignature } + +@InputType('OfficialJournalOfIcelandMainTypesInput') +export class MainTypesQueryParams { + @Field(() => String, { nullable: true }) + department?: string + + @Field(() => Int, { nullable: true }) + page?: number + + @Field(() => Int, { nullable: true }) + pageSize?: number +} diff --git a/libs/api/domains/official-journal-of-iceland/src/lib/models/advert.model.ts b/libs/api/domains/official-journal-of-iceland/src/lib/models/advert.model.ts index 30d22c9840f5..70a9135f7d45 100644 --- a/libs/api/domains/official-journal-of-iceland/src/lib/models/advert.model.ts +++ b/libs/api/domains/official-journal-of-iceland/src/lib/models/advert.model.ts @@ -136,3 +136,21 @@ export class Advert { @Field(() => AdvertDocument) document!: AdvertDocument } + +@ObjectType('OfficialJournalOfIcelandAdvertsMainType') +export class AdvertMainType { + @Field() + id!: string + + @Field() + title!: string + + @Field() + slug!: string + + @Field(() => AdvertEntity) + department!: AdvertEntity + + @Field(() => [AdvertType]) + types!: AdvertType[] +} diff --git a/libs/api/domains/official-journal-of-iceland/src/lib/models/advert.response.ts b/libs/api/domains/official-journal-of-iceland/src/lib/models/advert.response.ts index 633157789400..baa06f7a4f6f 100644 --- a/libs/api/domains/official-journal-of-iceland/src/lib/models/advert.response.ts +++ b/libs/api/domains/official-journal-of-iceland/src/lib/models/advert.response.ts @@ -5,6 +5,7 @@ import { AdvertEntity, AdvertMainCategory, AdvertType, + AdvertMainType, } from './advert.model' import { AdvertPaging } from './advert-paging.model' @@ -79,3 +80,12 @@ export class AdvertResponse { @Field(() => Advert) advert?: Advert } + +@ObjectType('OfficialJournalOfIcelandMainTypesResponse') +export class MainTypesResponse { + @Field(() => [AdvertMainType]) + mainTypes!: AdvertMainType[] + + @Field(() => AdvertPaging) + paging!: AdvertPaging +} diff --git a/libs/api/domains/official-journal-of-iceland/src/lib/officialJournalOfIceland.resolver.ts b/libs/api/domains/official-journal-of-iceland/src/lib/officialJournalOfIceland.resolver.ts index cde591d90f87..1fddbf0d108a 100644 --- a/libs/api/domains/official-journal-of-iceland/src/lib/officialJournalOfIceland.resolver.ts +++ b/libs/api/domains/official-journal-of-iceland/src/lib/officialJournalOfIceland.resolver.ts @@ -8,6 +8,7 @@ import { AdvertsInput, QueryParams, TypeQueryParams, + MainTypesQueryParams, } from './models/advert.input' import { AdvertCategoryResponse, @@ -19,6 +20,7 @@ import { AdvertsResponse, AdvertTypeResponse, AdvertTypesResponse, + MainTypesResponse, } from './models/advert.response' import { Features } from '@island.is/feature-flags' import { FeatureFlag } from '@island.is/nest/feature-flags' @@ -72,6 +74,13 @@ export class OfficialJournalOfIcelandResolver { return this.ojoiService.getAdvertTypes(params) } + @Query(() => MainTypesResponse, { + name: 'officialJournalOfIcelandMainTypes', + }) + getAdvertMainTypes(@Args('params') params: MainTypesQueryParams) { + return this.ojoiService.getMainTypes(params) + } + @Query(() => AdvertMainCategoriesResponse, { name: 'officialJournalOfIcelandMainCategories', }) diff --git a/libs/api/domains/official-journal-of-iceland/src/lib/officialJournalOfIceland.service.ts b/libs/api/domains/official-journal-of-iceland/src/lib/officialJournalOfIceland.service.ts index 954a7ab1d8ad..39d9c4c2507d 100644 --- a/libs/api/domains/official-journal-of-iceland/src/lib/officialJournalOfIceland.service.ts +++ b/libs/api/domains/official-journal-of-iceland/src/lib/officialJournalOfIceland.service.ts @@ -8,6 +8,7 @@ import { AdvertSingleParams, QueryParams, TypeQueryParams, + MainTypesQueryParams, } from './models/advert.input' import { AdvertCategoryResponse, @@ -17,6 +18,7 @@ import { AdvertResponse, AdvertsResponse, AdvertTypesResponse, + MainTypesResponse, } from './models/advert.response' import { CasesInProgressResponse } from './models/cases.response' @@ -54,6 +56,10 @@ export class OfficialJournalOfIcelandService { return await this.ojoiService.getAdvertTypes(params) } + async getMainTypes(params: MainTypesQueryParams): Promise { + return await this.ojoiService.getAdvertMainTypes(params) + } + async getInstitutions( params: QueryParams, ): Promise { diff --git a/libs/application/templates/official-journal-of-iceland/src/components/htmlEditor/HTMLEditor.tsx b/libs/application/templates/official-journal-of-iceland/src/components/htmlEditor/HTMLEditor.tsx index f8cb1367ec92..0b91a291d5e7 100644 --- a/libs/application/templates/official-journal-of-iceland/src/components/htmlEditor/HTMLEditor.tsx +++ b/libs/application/templates/official-journal-of-iceland/src/components/htmlEditor/HTMLEditor.tsx @@ -3,7 +3,7 @@ import { Editor, EditorFileUploader } from '@island.is/regulations-tools/Editor' import { useEffect, useRef, useState } from 'react' import { Controller } from 'react-hook-form' import { classes, editorWrapper, errorStyle } from './HTMLEditor.css' -import { Box, Text } from '@island.is/island-ui/core' +import { Box, Stack, Text } from '@island.is/island-ui/core' type Props = { title?: string name: string @@ -50,12 +50,8 @@ export const HTMLEditor = ({ defaultValue={initialValue} render={({ field: { onChange: updateFormValue, value } }) => { return ( - <> - {title && ( - - {title} - - )} + + {title && {title}} {error &&
{error}
} - +
) }} /> diff --git a/libs/application/templates/official-journal-of-iceland/src/components/input/OJOISelectController.tsx b/libs/application/templates/official-journal-of-iceland/src/components/input/OJOISelectController.tsx index 00eac25ff080..799ed40704d8 100644 --- a/libs/application/templates/official-journal-of-iceland/src/components/input/OJOISelectController.tsx +++ b/libs/application/templates/official-journal-of-iceland/src/components/input/OJOISelectController.tsx @@ -4,7 +4,12 @@ import { useApplication } from '../../hooks/useUpdateApplication' import { OJOIApplication } from '../../lib/types' import { useFormContext } from 'react-hook-form' import set from 'lodash/set' -import { Select, SkeletonLoader } from '@island.is/island-ui/core' +import { + Box, + Select, + SkeletonLoader, + useBreakpoint, +} from '@island.is/island-ui/core' import { OJOI_INPUT_HEIGHT } from '../../lib/constants' import { isBaseEntity } from '../../lib/utils' import { getValueViaPath } from '@island.is/application/core' @@ -23,6 +28,7 @@ type Props = { loading?: boolean applicationId: string disabled?: boolean + width?: 'full' | 'half' onBeforeChange?: (answers: OJOIApplication['answers'], value: T) => void onChange?: (value: T) => void } @@ -36,6 +42,7 @@ export const OJOISelectController = ({ loading, applicationId, disabled, + width = 'full', onBeforeChange, onChange, }: Props) => { @@ -46,6 +53,9 @@ export const OJOISelectController = ({ const { setValue } = useFormContext() + const { xs, sm, md } = useBreakpoint() + const isSmOrSmaller = xs || (sm && !md) + const placeholderText = typeof placeholder === 'string' ? placeholder : f(placeholder) @@ -68,35 +78,33 @@ export const OJOISelectController = ({ return opt.value.id === defaultVal.id } - return false + return undefined }) - if (loading) { - return ( - - ) - } - return ( - { + if (!opt?.value) return + return handleChange(opt.value) + }} + /> + )} + ) } diff --git a/libs/application/templates/official-journal-of-iceland/src/fields/Advert.tsx b/libs/application/templates/official-journal-of-iceland/src/fields/Advert.tsx index 0e81b3062f40..fcaa5cfdb6fc 100644 --- a/libs/application/templates/official-journal-of-iceland/src/fields/Advert.tsx +++ b/libs/application/templates/official-journal-of-iceland/src/fields/Advert.tsx @@ -1,8 +1,7 @@ import { InputFields, OJOIFieldBaseProps } from '../lib/types' -import { Box } from '@island.is/island-ui/core' +import { Stack } from '@island.is/island-ui/core' import { FormGroup } from '../components/form/FormGroup' import { advert } from '../lib/messages' -import * as styles from './Advert.css' import { useDepartments } from '../hooks/useDepartments' import { OJOISelectController } from '../components/input/OJOISelectController' import { useTypes } from '../hooks/useTypes' @@ -12,25 +11,23 @@ import { useFormContext } from 'react-hook-form' import { useApplication } from '../hooks/useUpdateApplication' import set from 'lodash/set' import { HTMLEditor } from '../components/htmlEditor/HTMLEditor' -import { getAdvertMarkup } from '../lib/utils' +import { cleanTypename, getAdvertMarkup } from '../lib/utils' +import { DEPARTMENT_A } from '../lib/constants' export const Advert = ({ application }: OJOIFieldBaseProps) => { const { setValue } = useFormContext() const { application: currentApplication } = useApplication({ applicationId: application.id, }) + const { departments, loading: loadingDepartments } = useDepartments() - const { - getLazyTypes, - types, - loading: loadingTypes, - } = useTypes({ - initalDepartmentId: application.answers?.advert?.department?.id, - }) - const titlePreview = getAdvertMarkup({ - type: currentApplication.answers.advert?.type?.title, - title: currentApplication.answers.advert?.title, + const defaultDepartment = + application.answers?.advert?.department?.id || DEPARTMENT_A + + const { getLazyMainTypes, mainTypes, mainTypeLoading } = useTypes({ + initalDepartmentId: defaultDepartment, + pageSize: 300, }) const departmentOptions = departments?.map((d) => ({ @@ -42,20 +39,28 @@ export const Advert = ({ application }: OJOIFieldBaseProps) => { }, })) - const typeOptions = types?.map((d) => ({ + const mainTypeOptions = mainTypes?.map((d) => ({ label: d.title, - value: { - id: d.id, - title: d.title, - slug: d.slug, - }, + value: d, })) + const currentTypes = + currentApplication?.answers?.advert?.mainType?.types?.map((d) => ({ + label: d.title, + value: d, + })) ?? [] + + const titlePreview = getAdvertMarkup({ + type: currentApplication.answers.advert?.type?.title, + title: currentApplication.answers.advert?.title, + }) + return ( - <> + - + { set(answers, InputFields.advert.type, null) }} onChange={(value) => - getLazyTypes({ + getLazyMainTypes({ variables: { params: { department: value.id, @@ -78,19 +83,36 @@ export const Advert = ({ application }: OJOIFieldBaseProps) => { }) } /> - - + { + if (value.types.length === 1) { + const cleaned = cleanTypename(value.types[0]) + set(answers, InputFields.advert.type, cleaned) + } else { + set(answers, InputFields.advert.type, null) + } + }} /> - - + + {currentTypes.length > 1 && ( + + )} + { textarea={true} maxLength={600} /> - - + - + - + { applicationId={application.id} disabled={true} /> - - + { // because this is not a controlled component onChange={(value) => setValue(InputFields.advert.html, value)} /> - + - + ) } diff --git a/libs/application/templates/official-journal-of-iceland/src/fields/AdvertModal.tsx b/libs/application/templates/official-journal-of-iceland/src/fields/AdvertModal.tsx index 8327cefa5e93..5020d1b8fbe7 100644 --- a/libs/application/templates/official-journal-of-iceland/src/fields/AdvertModal.tsx +++ b/libs/application/templates/official-journal-of-iceland/src/fields/AdvertModal.tsx @@ -26,6 +26,7 @@ import debounce from 'lodash/debounce' import { InputFields } from '../lib/types' import { useFormContext } from 'react-hook-form' import { OfficialJournalOfIcelandAdvert } from '@island.is/api/schema' +import { cleanTypename } from '../lib/utils' type Props = { applicationId: string visible: boolean @@ -75,20 +76,12 @@ export const AdvertModal = ({ return } - const clean = (obj: { - __typename?: string - id: string - title: string - slug: string - }) => { - const { __typename: _, ...rest } = obj - return rest - } - - const department = clean(advert.department) - const type = clean(advert.type) + const department = cleanTypename(advert.department) + const type = cleanTypename(advert.type) - const categories = advert.categories.map((category) => clean(category)) + const categories = advert.categories.map((category) => + cleanTypename(category), + ) setValue(InputFields.advert.department, department) setValue(InputFields.advert.type, type) diff --git a/libs/application/templates/official-journal-of-iceland/src/graphql/queries.ts b/libs/application/templates/official-journal-of-iceland/src/graphql/queries.ts index ebe437b894ad..cfe4f70ab68b 100644 --- a/libs/application/templates/official-journal-of-iceland/src/graphql/queries.ts +++ b/libs/application/templates/official-journal-of-iceland/src/graphql/queries.ts @@ -117,6 +117,38 @@ export const ADVERT_QUERY = gql` } ` +export const MAIN_TYPES_QUERY = gql` + query AdvertMainTypes($params: OfficialJournalOfIcelandMainTypesInput!) { + officialJournalOfIcelandMainTypes(params: $params) { + mainTypes { + id + title + slug + department { + id + title + slug + } + types { + id + title + slug + } + } + paging { + page + pageSize + totalPages + totalItems + hasNextPage + hasPreviousPage + nextPage + previousPage + } + } + } +` + export const TYPES_QUERY = gql` query AdvertTypes($params: OfficialJournalOfIcelandTypesInput!) { officialJournalOfIcelandTypes(params: $params) { diff --git a/libs/application/templates/official-journal-of-iceland/src/hooks/useTypes.ts b/libs/application/templates/official-journal-of-iceland/src/hooks/useTypes.ts index 8cd4ff5678ed..7ff98ea1949f 100644 --- a/libs/application/templates/official-journal-of-iceland/src/hooks/useTypes.ts +++ b/libs/application/templates/official-journal-of-iceland/src/hooks/useTypes.ts @@ -1,7 +1,10 @@ import { useLazyQuery, useQuery } from '@apollo/client' -import { OfficialJournalOfIcelandAdvertsTypesResponse } from '@island.is/api/schema' +import { + OfficialJournalOfIcelandAdvertsTypesResponse, + OfficialJournalOfIcelandMainTypesResponse, +} from '@island.is/api/schema' -import { TYPES_QUERY } from '../graphql/queries' +import { MAIN_TYPES_QUERY, TYPES_QUERY } from '../graphql/queries' type UseTypesParams = { initalDepartmentId?: string @@ -14,6 +17,10 @@ type TypesResponse = { officialJournalOfIcelandTypes: OfficialJournalOfIcelandAdvertsTypesResponse } +type MainTypesResponse = { + officialJournalOfIcelandMainTypes: OfficialJournalOfIcelandMainTypesResponse +} + type TypesVariables = { params: { department?: string @@ -50,6 +57,16 @@ export const useTypes = ({ }, ) + const { + data: mainTypesData, + loading: mainTypeLoading, + error: mainTypeError, + } = useQuery(MAIN_TYPES_QUERY, { + variables: { + params: params, + }, + }) + const [ getLazyTypes, { data: lazyTypes, loading: lazyTypesLoading, error: lazyTypesError }, @@ -57,11 +74,33 @@ export const useTypes = ({ fetchPolicy: 'network-only', }) + const [ + getLazyMainTypes, + { + data: lazyMainTypes, + loading: lazyMainTypesLoading, + error: lazyMainTypesError, + }, + ] = useLazyQuery(MAIN_TYPES_QUERY, { + fetchPolicy: 'network-only', + }) + const currentTypes = lazyTypes ? lazyTypes.officialJournalOfIcelandTypes.types : data?.officialJournalOfIcelandTypes.types + const currentMainTypes = lazyMainTypes + ? lazyMainTypes.officialJournalOfIcelandMainTypes.mainTypes + : mainTypesData?.officialJournalOfIcelandMainTypes.mainTypes + return { + mainTypes: currentMainTypes, + mainTypeLoading, + mainTypeError, + lazyMainTypesLoading, + lazyMainTypesError, + getLazyMainTypes, + lazyMainTypes: lazyMainTypes?.officialJournalOfIcelandMainTypes.mainTypes, lazyTypes: lazyTypes?.officialJournalOfIcelandTypes.types, lazyTypesLoading, lazyTypesError, diff --git a/libs/application/templates/official-journal-of-iceland/src/lib/constants.ts b/libs/application/templates/official-journal-of-iceland/src/lib/constants.ts index e05b15dcaf66..ed4ea3efc463 100644 --- a/libs/application/templates/official-journal-of-iceland/src/lib/constants.ts +++ b/libs/application/templates/official-journal-of-iceland/src/lib/constants.ts @@ -14,6 +14,10 @@ export enum AnswerOption { NO = 'no', } +export const DEPARTMENT_A = 'a-deild' +export const DEPARTMENT_B = 'b-deild' +export const DEPARTMENT_C = 'c-deild' + export enum ApplicationAttachmentType { ORIGINAL = 'frumrit', ADDITIONS = 'fylgiskjol', diff --git a/libs/application/templates/official-journal-of-iceland/src/lib/dataSchema.ts b/libs/application/templates/official-journal-of-iceland/src/lib/dataSchema.ts index 046cbeed2664..8dcfe02c7bbd 100644 --- a/libs/application/templates/official-journal-of-iceland/src/lib/dataSchema.ts +++ b/libs/application/templates/official-journal-of-iceland/src/lib/dataSchema.ts @@ -69,6 +69,9 @@ const advertSchema = z .object({ department: baseEntitySchema.optional(), type: baseEntitySchema.optional().nullable(), + mainType: baseEntitySchema + .extend({ types: z.array(baseEntitySchema).optional() }) + .optional(), title: z.string().optional(), html: z.string().optional(), requestedDate: z.string().optional(), diff --git a/libs/application/templates/official-journal-of-iceland/src/lib/messages/advert.ts b/libs/application/templates/official-journal-of-iceland/src/lib/messages/advert.ts index 681773cf8393..0bde1ec2be09 100644 --- a/libs/application/templates/official-journal-of-iceland/src/lib/messages/advert.ts +++ b/libs/application/templates/official-journal-of-iceland/src/lib/messages/advert.ts @@ -63,15 +63,27 @@ export const advert = { description: 'Placeholder for the department input', }, }), + mainType: defineMessages({ + label: { + id: 'ojoi.application:advert.inputs.mainType.label', + defaultMessage: 'Tegund birtingar', + description: 'Label for the main type input', + }, + placeholder: { + id: 'ojoi.application:advert.inputs.mainType.placeholder', + defaultMessage: 'Veldu tegund birtingar', + description: 'Placeholder for the main type input', + }, + }), type: defineMessages({ label: { id: 'ojoi.application:advert.inputs.type.label', - defaultMessage: 'Tegund birtingar', + defaultMessage: 'Undirtegund birtingar', description: 'Label for the type input', }, placeholder: { id: 'ojoi.application:advert.inputs.type.placeholder', - defaultMessage: 'Veldu tegund birtingar', + defaultMessage: 'Veldu undirtegund birtingar', description: 'Placeholder for the type input', }, }), diff --git a/libs/application/templates/official-journal-of-iceland/src/lib/types.ts b/libs/application/templates/official-journal-of-iceland/src/lib/types.ts index 1529e2d56442..032d13c63692 100644 --- a/libs/application/templates/official-journal-of-iceland/src/lib/types.ts +++ b/libs/application/templates/official-journal-of-iceland/src/lib/types.ts @@ -12,6 +12,7 @@ export const InputFields = { }, [Routes.ADVERT]: { department: 'advert.department', + mainType: 'advert.mainType', type: 'advert.type', title: 'advert.title', html: 'advert.html', diff --git a/libs/application/templates/official-journal-of-iceland/src/lib/utils.ts b/libs/application/templates/official-journal-of-iceland/src/lib/utils.ts index 15699f900241..fdfaad3ab0d9 100644 --- a/libs/application/templates/official-journal-of-iceland/src/lib/utils.ts +++ b/libs/application/templates/official-journal-of-iceland/src/lib/utils.ts @@ -388,3 +388,13 @@ export const convertNumberToRoman = (num: number) => { const roman = ['I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX', 'X'] return roman[num - 1] } + +export const cleanTypename = (obj: { + __typename?: string + id: string + title: string + slug: string +}) => { + const { __typename: _, ...rest } = obj + return rest +} diff --git a/libs/application/templates/official-journal-of-iceland/src/screens/InvolvedPartyScreen.tsx b/libs/application/templates/official-journal-of-iceland/src/screens/InvolvedPartyScreen.tsx index 7ed23a605848..b35efca32233 100644 --- a/libs/application/templates/official-journal-of-iceland/src/screens/InvolvedPartyScreen.tsx +++ b/libs/application/templates/official-journal-of-iceland/src/screens/InvolvedPartyScreen.tsx @@ -90,21 +90,20 @@ export const InvolvedPartyScreen = ({ /> )} - - { - setSubmitButtonDisabled && setSubmitButtonDisabled(false) - }} - /> - + { + setSubmitButtonDisabled && setSubmitButtonDisabled(false) + }} + /> ) diff --git a/libs/clients/official-journal-of-iceland/public/src/clientConfig.json b/libs/clients/official-journal-of-iceland/public/src/clientConfig.json index 092ccd7520e1..6206b173263a 100644 --- a/libs/clients/official-journal-of-iceland/public/src/clientConfig.json +++ b/libs/clients/official-journal-of-iceland/public/src/clientConfig.json @@ -169,15 +169,40 @@ } } }, - "/api/v1/types/{id}": { + "/api/v1/maincategories": { "get": { - "operationId": "getAdvertTypeById", + "operationId": "getMainCategories", "parameters": [ { - "name": "id", - "required": true, - "in": "path", + "name": "search", + "description": "String to search for", + "required": false, + "in": "query", "schema": { "type": "string" } + }, + { + "name": "ids", + "required": false, + "in": "query", + "schema": { + "default": [], + "type": "array", + "items": { "type": "string" } + } + }, + { + "name": "page", + "description": "Page number to return.", + "required": false, + "in": "query", + "schema": { "type": "number" } + }, + { + "name": "pageSize", + "description": "Page size number to return.", + "required": false, + "in": "query", + "schema": { "type": "number" } } ], "responses": { @@ -186,7 +211,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/GetAdvertTypeResponse" + "$ref": "#/components/schemas/GetMainCategoriesResponse" } } } @@ -194,23 +219,26 @@ } } }, - "/api/v1/types": { + "/api/v1/categories": { "get": { - "operationId": "getAdvertTypes", + "operationId": "getCategories", "parameters": [ { - "name": "department", - "description": "Department slug to get categories for.", + "name": "search", + "description": "String to search for", "required": false, "in": "query", "schema": { "type": "string" } }, { - "name": "search", - "description": "String to search for in types.", + "name": "ids", "required": false, "in": "query", - "schema": { "type": "string" } + "schema": { + "default": [], + "type": "array", + "items": { "type": "string" } + } }, { "name": "page", @@ -221,7 +249,7 @@ }, { "name": "pageSize", - "description": "Number of items per page.", + "description": "Page size number to return.", "required": false, "in": "query", "schema": { "type": "number" } @@ -233,7 +261,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/GetAdvertTypesResponse" + "$ref": "#/components/schemas/GetCategoriesResponse" } } } @@ -241,9 +269,9 @@ } } }, - "/api/v1/maincategories": { + "/api/v1/institutions": { "get": { - "operationId": "getMainCategories", + "operationId": "getInstitutions", "parameters": [ { "name": "search", @@ -283,7 +311,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/GetMainCategoriesResponse" + "$ref": "#/components/schemas/GetInstitutionsResponse" } } } @@ -291,37 +319,36 @@ } } }, - "/api/v1/categories": { + "/api/v1/signatures": { "get": { - "operationId": "getCategories", + "operationId": "getSignatures", "parameters": [ { - "name": "search", - "description": "String to search for", + "description": "Search for a specific signature by id", "required": false, + "name": "id", "in": "query", "schema": { "type": "string" } }, { - "name": "ids", + "description": "Search for a specific signature by type", + "example": "Regular", "required": false, + "name": "type", "in": "query", - "schema": { - "default": [], - "type": "array", - "items": { "type": "string" } - } + "schema": { "type": "string" } }, { - "name": "page", - "description": "Page number to return.", + "description": "Search for a specific signature", + "example": "Dagur B. Eggertsson", "required": false, + "name": "search", "in": "query", - "schema": { "type": "number" } + "schema": { "type": "string" } }, { - "name": "pageSize", - "description": "Page size number to return.", + "name": "page", + "description": "Page number to return.", "required": false, "in": "query", "schema": { "type": "number" } @@ -333,7 +360,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/GetCategoriesResponse" + "$ref": "#/components/schemas/GetAdvertSignatureResponse" } } } @@ -341,9 +368,9 @@ } } }, - "/api/v1/institutions": { + "/api/v1/cases": { "get": { - "operationId": "getInstitutions", + "operationId": "getCasesInProgress", "parameters": [ { "name": "search", @@ -383,7 +410,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/GetInstitutionsResponse" + "$ref": "#/components/schemas/GetCasesInProgressReponse" } } } @@ -391,115 +418,384 @@ } } }, - "/api/v1/signatures": { + "/api/v1/error": { "get": { - "operationId": "getSignatures", + "operationId": "error", + "parameters": [], + "responses": { + "default": { + "description": "", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/ValidationResponse" } + } + } + } + } + } + }, + "/api/v1/advert-types/types": { + "get": { + "operationId": "getTypes", + "summary": "", "parameters": [ { - "description": "Search for a specific signature by id", "required": false, + "description": "Search by id", "name": "id", "in": "query", "schema": { "type": "string" } }, { - "description": "Search for a specific signature by type", - "example": "Regular", "required": false, - "name": "type", + "description": "Filter by unassigned", + "name": "unassigned", + "in": "query", + "schema": { "type": "boolean" } + }, + { + "required": false, + "description": "Search by main type id", + "name": "mainType", "in": "query", "schema": { "type": "string" } }, { - "description": "Search for a specific signature", - "example": "Dagur B. Eggertsson", "required": false, + "description": "Search by title", "name": "search", "in": "query", "schema": { "type": "string" } }, { + "required": false, + "description": "Search by slug", + "name": "slug", + "in": "query", + "schema": { "type": "string" } + }, + { + "required": false, + "description": "Search by department slug, title or id", + "name": "department", + "in": "query", + "schema": { "type": "string" } + }, + { + "required": false, + "description": "The page number", "name": "page", - "description": "Page number to return.", + "in": "query", + "schema": { "type": "number" } + }, + { "required": false, + "description": "The page size", + "name": "pageSize", "in": "query", "schema": { "type": "number" } } ], "responses": { - "default": { + "200": { "description": "", "content": { "application/json": { - "schema": { - "$ref": "#/components/schemas/GetAdvertSignatureResponse" - } + "schema": { "$ref": "#/components/schemas/GetAdvertTypes" } + } + } + }, + "400": { + "description": "", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/AdvertTypeError" } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/AdvertTypeError" } } } } } } }, - "/api/v1/cases": { + "/api/v1/advert-types/main-types": { "get": { - "operationId": "getCasesInProgress", + "operationId": "getMainTypes", + "summary": "", "parameters": [ { + "required": false, + "description": "Search by id", + "name": "id", + "in": "query", + "schema": { "type": "string" } + }, + { + "required": false, + "description": "Filter by unassigned", + "name": "unassigned", + "in": "query", + "schema": { "type": "boolean" } + }, + { + "required": false, + "description": "Search by main type id", + "name": "mainType", + "in": "query", + "schema": { "type": "string" } + }, + { + "required": false, + "description": "Search by title", "name": "search", - "description": "String to search for", + "in": "query", + "schema": { "type": "string" } + }, + { "required": false, + "description": "Search by slug", + "name": "slug", "in": "query", "schema": { "type": "string" } }, { - "name": "ids", "required": false, + "description": "Search by department slug, title or id", + "name": "department", "in": "query", - "schema": { - "default": [], - "type": "array", - "items": { "type": "string" } - } + "schema": { "type": "string" } }, { - "name": "page", - "description": "Page number to return.", "required": false, + "description": "The page number", + "name": "page", "in": "query", "schema": { "type": "number" } }, { - "name": "pageSize", - "description": "Page size number to return.", "required": false, + "description": "The page size", + "name": "pageSize", "in": "query", "schema": { "type": "number" } } ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/GetAdvertMainTypes" } + } + } + }, + "400": { + "description": "", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/AdvertTypeError" } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/AdvertTypeError" } + } + } + } + } + } + }, + "/api/v1/advert-types/types/{id}": { + "get": { + "operationId": "getTypeById", + "summary": "", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { "type": "string" } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/GetAdvertType" } + } + } + }, + "400": { + "description": "", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/AdvertTypeError" } + } + } + }, + "404": { + "description": "", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/AdvertTypeError" } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/AdvertTypeError" } + } + } + } + } + } + }, + "/api/v1/advert-types/main-types/{id}": { + "get": { + "operationId": "getMainTypeById", + "summary": "", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { "type": "string" } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/GetAdvertMainType" } + } + } + }, + "400": { + "description": "", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/AdvertTypeError" } + } + } + }, + "404": { + "description": "", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/AdvertTypeError" } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/AdvertTypeError" } + } + } + } + } + } + }, + "/api/v1/pdf/case/{id}": { + "get": { + "operationId": "getPdfByCaseId", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { "type": "string" } + } + ], "responses": { "default": { "description": "", "content": { "application/json": { - "schema": { - "$ref": "#/components/schemas/GetCasesInProgressReponse" - } + "schema": { "$ref": "#/components/schemas/GetPdfRespone" } } } } } } }, - "/api/v1/error": { + "/api/v1/pdf/application/{id}": { "get": { - "operationId": "error", - "parameters": [], + "operationId": "getPdfByApplicationId", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { "type": "string" } + } + ], "responses": { "default": { "description": "", "content": { "application/json": { - "schema": { "$ref": "#/components/schemas/ValidationResponse" } + "schema": { "$ref": "#/components/schemas/GetPdfRespone" } + } + } + } + } + } + }, + "/api/v1/pdf/case/{id}/url": { + "get": { + "operationId": "getPdfUrlByCaseId", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { "type": "string" } + } + ], + "responses": { + "default": { + "description": "", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/GetPdfUrlResponse" } + } + } + } + } + } + }, + "/api/v1/pdf/application/{id}/url": { + "get": { + "operationId": "getPdfUrlByApplicationId", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { "type": "string" } + } + ], + "responses": { + "default": { + "description": "", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/GetPdfUrlResponse" } } } } @@ -551,23 +847,18 @@ "properties": { "id": { "type": "string", - "description": "Unique ID for the advert type, GUID format.", - "example": "00000000-0000-0000-0000-000000000000", - "nullable": false + "description": "The id of the main advert type" }, "title": { "type": "string", - "description": "Title of the advert type, always uppercased.", - "example": "AUGLÝSING" + "description": "The title of the main advert type" }, "slug": { "type": "string", - "description": "Slug of the advert type, used in URLs and API requests.", - "example": "auglysing" + "description": "The slug of the main advert type" }, "department": { - "description": "Department the advert type belongs to.", - "nullable": true, + "description": "The department of the main advert type", "allOf": [{ "$ref": "#/components/schemas/Department" }] } }, @@ -977,31 +1268,6 @@ }, "required": ["departments", "paging"] }, - "GetAdvertTypeResponse": { - "type": "object", - "properties": { - "type": { - "description": "Advert type", - "allOf": [{ "$ref": "#/components/schemas/AdvertType" }] - } - }, - "required": ["type"] - }, - "GetAdvertTypesResponse": { - "type": "object", - "properties": { - "types": { - "description": "List of advert types", - "type": "array", - "items": { "$ref": "#/components/schemas/AdvertType" } - }, - "paging": { - "description": "Paging info", - "allOf": [{ "$ref": "#/components/schemas/Paging" }] - } - }, - "required": ["types", "paging"] - }, "MainCategory": { "type": "object", "properties": { @@ -1190,6 +1456,108 @@ } }, "required": ["message", "statusCode"] + }, + "GetAdvertTypes": { + "type": "object", + "properties": { + "types": { + "description": "List of advert types", + "type": "array", + "items": { "$ref": "#/components/schemas/AdvertType" } + }, + "paging": { + "description": "Paging information", + "allOf": [{ "$ref": "#/components/schemas/Paging" }] + } + }, + "required": ["types", "paging"] + }, + "AdvertTypeError": { + "type": "object", + "properties": { + "errorType": { + "type": "string", + "enum": ["DuplicateError", "ValidationError", "NotFoundError"] + }, + "name": { "type": "string" }, + "message": { "type": "string" }, + "severity": { "type": "string", "enum": ["info", "warning", "error"] } + }, + "required": ["errorType", "name", "message", "severity"] + }, + "AdvertMainType": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "The id of the main advert type" + }, + "title": { + "type": "string", + "description": "The title of the main advert type" + }, + "slug": { + "type": "string", + "description": "The slug of the main advert type" + }, + "department": { + "description": "The department this main type belongs to", + "allOf": [{ "$ref": "#/components/schemas/Department" }] + }, + "types": { + "description": "All types under this main type", + "type": "array", + "items": { "$ref": "#/components/schemas/AdvertType" } + } + }, + "required": ["id", "title", "slug", "department", "types"] + }, + "GetAdvertMainTypes": { + "type": "object", + "properties": { + "mainTypes": { + "description": "List of all main advert types", + "type": "array", + "items": { "$ref": "#/components/schemas/AdvertMainType" } + }, + "paging": { + "description": "Paging information", + "allOf": [{ "$ref": "#/components/schemas/Paging" }] + } + }, + "required": ["mainTypes", "paging"] + }, + "GetAdvertType": { + "type": "object", + "properties": { + "type": { + "description": "The advert type", + "allOf": [{ "$ref": "#/components/schemas/AdvertType" }] + } + }, + "required": ["type"] + }, + "GetAdvertMainType": { + "type": "object", + "properties": { + "mainType": { + "description": "The main advert type", + "allOf": [{ "$ref": "#/components/schemas/AdvertMainType" }] + } + }, + "required": ["mainType"] + }, + "GetPdfRespone": { + "type": "object", + "properties": { + "content": { "type": "string", "description": "Base64 encoded PDF" } + }, + "required": ["content"] + }, + "GetPdfUrlResponse": { + "type": "object", + "properties": { "url": { "type": "string" } }, + "required": ["url"] } } } diff --git a/libs/clients/official-journal-of-iceland/public/src/lib/officialJournalOfIcelandClient.service.ts b/libs/clients/official-journal-of-iceland/public/src/lib/officialJournalOfIcelandClient.service.ts index 1ab4b33951c9..a600c48cb36e 100644 --- a/libs/clients/official-journal-of-iceland/public/src/lib/officialJournalOfIcelandClient.service.ts +++ b/libs/clients/official-journal-of-iceland/public/src/lib/officialJournalOfIcelandClient.service.ts @@ -7,11 +7,13 @@ import { GetDepartmentsRequest, GetInstitutionsRequest, GetMainCategoriesRequest, - GetAdvertTypesRequest, GetDepartmentByIdRequest, - GetAdvertTypeByIdRequest, GetCasesInProgressRequest, + GetTypeByIdRequest, + GetTypesRequest, + GetMainTypesRequest, } from '../../gen/fetch/apis' +import { GetAdvertMainTypes } from '../../gen/fetch' @Injectable() export class OfficialJournalOfIcelandClientService { @@ -33,12 +35,18 @@ export class OfficialJournalOfIcelandClientService { return this.api.getDepartments(params ?? {}) } - public async getAdvertTypeById(params: GetAdvertTypeByIdRequest) { - return this.api.getAdvertTypeById(params) + public async getAdvertTypeById(params: GetTypeByIdRequest) { + return this.api.getTypeById(params) } - public async getAdvertTypes(params: GetAdvertTypesRequest) { - return this.api.getAdvertTypes(params) + public async getAdvertMainTypes( + params: GetMainTypesRequest, + ): Promise { + return this.api.getMainTypes(params) + } + + public async getAdvertTypes(params: GetTypesRequest) { + return this.api.getTypes(params) } public async getMainCategories(params: GetMainCategoriesRequest) { From 72a8ce98dcd6b753a3a59578e7ce5b9a90b9ee9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=B3rhildur=20Thorleiksd=C3=B3ttir?= <16031078+thorhildurt@users.noreply.github.com> Date: Thu, 19 Dec 2024 14:24:38 +0000 Subject: [PATCH 09/19] feat(j-s): Add received date and new table state tag for indictment prison cases (#17279) * feat(j-s): adding interceptor with when prison admin fetches indictment * feat(j-s): Show openend by prison admin date + formatting * fix(j-s): show received tag in prison cases when openendByPrisonAdminDate is populated * refactor(j-s): Use case tags * refactor(j-s): small fixes * chore: nx format:write update dirty files * refactor(j-s): address self-review * fix(j-s): add user role in get by id test * chore: nx format:write update dirty files --------- Co-authored-by: andes-it Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../defendant/models/defendant.model.ts | 3 + .../src/app/modules/case/case.service.ts | 10 +++ .../case/interceptors/case.interceptor.ts | 11 +++- ...defendantIndictmentAccessed.interceptor.ts | 63 +++++++++++++++++++ .../case/limitedAccessCase.controller.ts | 8 ++- .../getById.spec.ts | 20 ++++-- .../modules/defendant/defendant.service.ts | 18 +++++- .../models/defendantEventLog.model.ts | 14 +++-- .../src/components/FormProvider/case.graphql | 1 + .../FormProvider/limitedAccessCase.graphql | 1 + .../web/src/components/Tags/utils.ts | 16 +++++ .../IndictmentOverview.strings.ts | 7 ++- .../IndictmentOverview/IndictmentOverview.tsx | 7 +++ .../src/routes/Shared/Cases/PrisonCases.tsx | 24 +++++-- .../web/src/routes/Shared/Cases/cases.graphql | 1 + .../routes/Shared/Cases/prisonCases.graphql | 1 + .../judicial-system/types/src/lib/eventLog.ts | 1 + 17 files changed, 187 insertions(+), 19 deletions(-) create mode 100644 apps/judicial-system/backend/src/app/modules/case/interceptors/defendantIndictmentAccessed.interceptor.ts diff --git a/apps/judicial-system/api/src/app/modules/defendant/models/defendant.model.ts b/apps/judicial-system/api/src/app/modules/defendant/models/defendant.model.ts index 589709247bd9..437900ffb488 100644 --- a/apps/judicial-system/api/src/app/modules/defendant/models/defendant.model.ts +++ b/apps/judicial-system/api/src/app/modules/defendant/models/defendant.model.ts @@ -110,6 +110,9 @@ export class Defendant { @Field(() => String, { nullable: true }) readonly sentToPrisonAdminDate?: string + @Field(() => String, { nullable: true }) + readonly openedByPrisonAdminDate?: string + @Field(() => PunishmentType, { nullable: true }) readonly punishmentType?: PunishmentType } diff --git a/apps/judicial-system/backend/src/app/modules/case/case.service.ts b/apps/judicial-system/backend/src/app/modules/case/case.service.ts index 75fe945637d3..3adf5cac8f0d 100644 --- a/apps/judicial-system/backend/src/app/modules/case/case.service.ts +++ b/apps/judicial-system/backend/src/app/modules/case/case.service.ts @@ -417,6 +417,16 @@ export const caseListInclude: Includeable[] = [ as: 'defendants', required: false, order: [['created', 'ASC']], + include: [ + { + model: DefendantEventLog, + as: 'eventLogs', + required: false, + where: { eventType: defendantEventTypes }, + order: [['created', 'DESC']], + separate: true, + }, + ], separate: true, }, { diff --git a/apps/judicial-system/backend/src/app/modules/case/interceptors/case.interceptor.ts b/apps/judicial-system/backend/src/app/modules/case/interceptors/case.interceptor.ts index 85e302ad6754..adfb51abe7f2 100644 --- a/apps/judicial-system/backend/src/app/modules/case/interceptors/case.interceptor.ts +++ b/apps/judicial-system/backend/src/app/modules/case/interceptors/case.interceptor.ts @@ -7,6 +7,8 @@ import { NestInterceptor, } from '@nestjs/common' +import { DefendantEventType } from '@island.is/judicial-system/types' + import { Defendant, DefendantEventLog } from '../../defendant' import { Case } from '../models/case.model' import { CaseString } from '../models/caseString.model' @@ -15,8 +17,15 @@ export const transformDefendants = (defendants?: Defendant[]) => { return defendants?.map((defendant) => ({ ...defendant.toJSON(), sentToPrisonAdminDate: defendant.isSentToPrisonAdmin - ? DefendantEventLog.sentToPrisonAdminDate(defendant.eventLogs)?.created + ? DefendantEventLog.getDefendantEventLogTypeDate({ + defendantEventLogs: defendant.eventLogs, + eventType: DefendantEventType.SENT_TO_PRISON_ADMIN, + }) : undefined, + openedByPrisonAdminDate: DefendantEventLog.getDefendantEventLogTypeDate({ + defendantEventLogs: defendant.eventLogs, + eventType: DefendantEventType.OPENED_BY_PRISON_ADMIN, + }), })) } diff --git a/apps/judicial-system/backend/src/app/modules/case/interceptors/defendantIndictmentAccessed.interceptor.ts b/apps/judicial-system/backend/src/app/modules/case/interceptors/defendantIndictmentAccessed.interceptor.ts new file mode 100644 index 000000000000..64d122d8db7c --- /dev/null +++ b/apps/judicial-system/backend/src/app/modules/case/interceptors/defendantIndictmentAccessed.interceptor.ts @@ -0,0 +1,63 @@ +import { + CallHandler, + ExecutionContext, + Injectable, + NestInterceptor, +} from '@nestjs/common' + +import { + DefendantEventType, + isIndictmentCase, + isPrisonAdminUser, + User, +} from '@island.is/judicial-system/types' + +import { DefendantEventLog, DefendantService } from '../../defendant' +import { Case } from '../models/case.model' + +const hasValidOpenByPrisonAdminEvent = ( + defendantEventLogs: DefendantEventLog[], +) => { + const sentToPrisonAdminDate = DefendantEventLog.getDefendantEventLogTypeDate({ + defendantEventLogs, + eventType: DefendantEventType.SENT_TO_PRISON_ADMIN, + }) + const openedByPrisonAdminDate = + DefendantEventLog.getDefendantEventLogTypeDate({ + defendantEventLogs, + eventType: DefendantEventType.OPENED_BY_PRISON_ADMIN, + }) + return ( + sentToPrisonAdminDate && + openedByPrisonAdminDate && + sentToPrisonAdminDate <= openedByPrisonAdminDate + ) +} + +@Injectable() +export class DefendantIndictmentAccessedInterceptor implements NestInterceptor { + constructor(private readonly defendantService: DefendantService) {} + + intercept(context: ExecutionContext, next: CallHandler) { + const request = context.switchToHttp().getRequest() + const user: User = request.user + const theCase: Case = request.case + + if (isIndictmentCase(theCase.type) && isPrisonAdminUser(user)) { + const defendantsIndictmentNotOpened = theCase.defendants?.filter( + ({ isSentToPrisonAdmin, eventLogs = [] }) => + isSentToPrisonAdmin && !hasValidOpenByPrisonAdminEvent(eventLogs), + ) + + // create new events for all defendants that prison admin has not accessed according to defendant event logs + defendantsIndictmentNotOpened?.forEach((defendant) => + this.defendantService.createDefendantEvent({ + caseId: theCase.id, + defendantId: defendant.id, + eventType: DefendantEventType.OPENED_BY_PRISON_ADMIN, + }), + ) + } + return next.handle() + } +} diff --git a/apps/judicial-system/backend/src/app/modules/case/limitedAccessCase.controller.ts b/apps/judicial-system/backend/src/app/modules/case/limitedAccessCase.controller.ts index c263e83f455d..be63d588328f 100644 --- a/apps/judicial-system/backend/src/app/modules/case/limitedAccessCase.controller.ts +++ b/apps/judicial-system/backend/src/app/modules/case/limitedAccessCase.controller.ts @@ -30,13 +30,16 @@ import type { User as TUser } from '@island.is/judicial-system/types' import { CaseState, CaseType, + DefendantEventType, indictmentCases, investigationCases, restrictionCases, + UserRole, } from '@island.is/judicial-system/types' import { nowFactory } from '../../factories' import { defenderRule, prisonSystemStaffRule } from '../../guards' +import { DefendantService } from '../defendant' import { EventService } from '../event' import { User } from '../user' import { TransitionCaseDto } from './dto/transitionCase.dto' @@ -57,6 +60,7 @@ import { } from './guards/rolesRules' import { CaseInterceptor } from './interceptors/case.interceptor' import { CompletedAppealAccessedInterceptor } from './interceptors/completedAppealAccessed.interceptor' +import { DefendantIndictmentAccessedInterceptor } from './interceptors/defendantIndictmentAccessed.interceptor' import { LimitedAccessCaseFileInterceptor } from './interceptors/limitedAccessCaseFile.interceptor' import { Case } from './models/case.model' import { transitionCase } from './state/case.state' @@ -73,6 +77,7 @@ export class LimitedAccessCaseController { private readonly limitedAccessCaseService: LimitedAccessCaseService, private readonly eventService: EventService, private readonly pdfService: PdfService, + private readonly defendantService: DefendantService, @Inject(LOGGER_PROVIDER) private readonly logger: Logger, ) {} @@ -84,6 +89,7 @@ export class LimitedAccessCaseController { ) @RolesRules(prisonSystemStaffRule, defenderRule) @UseInterceptors( + DefendantIndictmentAccessedInterceptor, CompletedAppealAccessedInterceptor, LimitedAccessCaseFileInterceptor, CaseInterceptor, @@ -100,7 +106,7 @@ export class LimitedAccessCaseController { ): Promise { this.logger.debug(`Getting limitedAccess case ${caseId} by id`) - if (!theCase.openedByDefender) { + if (user.role === UserRole.DEFENDER && !theCase.openedByDefender) { const updated = await this.limitedAccessCaseService.update( theCase, { openedByDefender: nowFactory() }, diff --git a/apps/judicial-system/backend/src/app/modules/case/test/limitedAccessCaseController/getById.spec.ts b/apps/judicial-system/backend/src/app/modules/case/test/limitedAccessCaseController/getById.spec.ts index 034bded816f0..4036446f36ed 100644 --- a/apps/judicial-system/backend/src/app/modules/case/test/limitedAccessCaseController/getById.spec.ts +++ b/apps/judicial-system/backend/src/app/modules/case/test/limitedAccessCaseController/getById.spec.ts @@ -1,6 +1,6 @@ import { uuid } from 'uuidv4' -import type { User } from '@island.is/judicial-system/types' +import { type User, UserRole } from '@island.is/judicial-system/types' import { createTestingCaseModule } from '../createTestingCaseModule' @@ -14,14 +14,18 @@ interface Then { error: Error } -type GivenWhenThen = (caseId: string, theCase: Case) => Promise +type GivenWhenThen = ( + caseId: string, + theCase: Case, + user?: User, +) => Promise describe('LimitedAccessCaseController - Get by id', () => { let givenWhenThen: GivenWhenThen const openedBeforeDate = randomDate() const openedNowDate = randomDate() const caseId = uuid() - const user = { id: uuid() } as User + const defaultUser = { id: uuid() } as User let mockCaseModel: typeof Case @@ -42,7 +46,11 @@ describe('LimitedAccessCaseController - Get by id', () => { const mockFindOne = mockCaseModel.findOne as jest.Mock mockFindOne.mockResolvedValue(updatedCase) - givenWhenThen = async (caseId: string, theCase: Case) => { + givenWhenThen = async ( + caseId: string, + theCase: Case, + user = defaultUser, + ) => { const then = {} as Then try { @@ -79,11 +87,11 @@ describe('LimitedAccessCaseController - Get by id', () => { describe('case exists and has not been opened by defender before', () => { const theCase = { id: caseId } as Case - + const user = { ...defaultUser, role: UserRole.DEFENDER } as User let then: Then beforeEach(async () => { - then = await givenWhenThen(caseId, theCase) + then = await givenWhenThen(caseId, theCase, user) }) it('should update openedByDefender and return case', () => { diff --git a/apps/judicial-system/backend/src/app/modules/defendant/defendant.service.ts b/apps/judicial-system/backend/src/app/modules/defendant/defendant.service.ts index 6276f0634115..d1f24d828393 100644 --- a/apps/judicial-system/backend/src/app/modules/defendant/defendant.service.ts +++ b/apps/judicial-system/backend/src/app/modules/defendant/defendant.service.ts @@ -271,6 +271,22 @@ export class DefendantService { return updatedDefendant } + async createDefendantEvent({ + caseId, + defendantId, + eventType, + }: { + caseId: string + defendantId: string + eventType: DefendantEventType + }): Promise { + await this.defendantEventLogModel.create({ + caseId, + defendantId, + eventType, + }) + } + async updateIndictmentCaseDefendant( theCase: Case, defendant: Defendant, @@ -284,7 +300,7 @@ export class DefendantService { ) if (update.isSentToPrisonAdmin) { - this.defendantEventLogModel.create({ + this.createDefendantEvent({ caseId: theCase.id, defendantId: defendant.id, eventType: DefendantEventType.SENT_TO_PRISON_ADMIN, diff --git a/apps/judicial-system/backend/src/app/modules/defendant/models/defendantEventLog.model.ts b/apps/judicial-system/backend/src/app/modules/defendant/models/defendantEventLog.model.ts index ca0a332704ee..df19027fd0f9 100644 --- a/apps/judicial-system/backend/src/app/modules/defendant/models/defendantEventLog.model.ts +++ b/apps/judicial-system/backend/src/app/modules/defendant/models/defendantEventLog.model.ts @@ -20,11 +20,17 @@ import { Defendant } from './defendant.model' timestamps: true, }) export class DefendantEventLog extends Model { - static sentToPrisonAdminDate(defendantEventLogs?: DefendantEventLog[]) { + // gets the latest log date of a given type, since the defendant event logs are sorted + static getDefendantEventLogTypeDate({ + defendantEventLogs, + eventType, + }: { + defendantEventLogs?: DefendantEventLog[] + eventType: DefendantEventType + }) { return defendantEventLogs?.find( - (defendantEventLog) => - defendantEventLog.eventType === DefendantEventType.SENT_TO_PRISON_ADMIN, - ) + (defendantEventLog) => defendantEventLog.eventType === eventType, + )?.created } @Column({ diff --git a/apps/judicial-system/web/src/components/FormProvider/case.graphql b/apps/judicial-system/web/src/components/FormProvider/case.graphql index 4aec6d983d4b..069e36645c3f 100644 --- a/apps/judicial-system/web/src/components/FormProvider/case.graphql +++ b/apps/judicial-system/web/src/components/FormProvider/case.graphql @@ -35,6 +35,7 @@ query Case($input: CaseQueryInput!) { subpoenaType isSentToPrisonAdmin sentToPrisonAdminDate + openedByPrisonAdminDate punishmentType subpoenas { id diff --git a/apps/judicial-system/web/src/components/FormProvider/limitedAccessCase.graphql b/apps/judicial-system/web/src/components/FormProvider/limitedAccessCase.graphql index dea05680c538..8d31030894bc 100644 --- a/apps/judicial-system/web/src/components/FormProvider/limitedAccessCase.graphql +++ b/apps/judicial-system/web/src/components/FormProvider/limitedAccessCase.graphql @@ -47,6 +47,7 @@ query LimitedAccessCase($input: CaseQueryInput!) { subpoenaType isSentToPrisonAdmin sentToPrisonAdminDate + openedByPrisonAdminDate punishmentType subpoenas { id diff --git a/apps/judicial-system/web/src/components/Tags/utils.ts b/apps/judicial-system/web/src/components/Tags/utils.ts index e4f1b3ac9f83..1b047e13806a 100644 --- a/apps/judicial-system/web/src/components/Tags/utils.ts +++ b/apps/judicial-system/web/src/components/Tags/utils.ts @@ -119,3 +119,19 @@ export const getIndictmentRulingDecisionTag = ( return { color: 'darkerBlue', text: strings.complete } } } + +export const getPrisonCaseStateTag = ( + prisonCaseState: CaseState, +): { + color: TagVariant + text: { id: string; defaultMessage: string; description: string } +} => { + switch (prisonCaseState) { + case CaseState.NEW: + return { color: 'purple', text: strings.new } + case CaseState.RECEIVED: + return { color: 'blue', text: strings.received } + default: + return { color: 'darkerBlue', text: strings.complete } + } +} diff --git a/apps/judicial-system/web/src/routes/Prison/IndictmentOverview/IndictmentOverview.strings.ts b/apps/judicial-system/web/src/routes/Prison/IndictmentOverview/IndictmentOverview.strings.ts index d574238883e5..bc87e97beef8 100644 --- a/apps/judicial-system/web/src/routes/Prison/IndictmentOverview/IndictmentOverview.strings.ts +++ b/apps/judicial-system/web/src/routes/Prison/IndictmentOverview/IndictmentOverview.strings.ts @@ -14,7 +14,12 @@ export const strings = defineMessages({ indictmentCompletedTitle: { id: 'judicial.system.core:indictment_overview.indictment_completed_title', defaultMessage: 'Dómsuppkvaðning {date}', - description: 'Titill á yfirliti ákæru fyrir fangelsi', + description: 'Undirtitill á yfirliti ákæru fyrir fangelsi', + }, + indictmentReceivedTitle: { + id: 'judicial.system.core:indictment_overview.indictment_received_title', + defaultMessage: 'Móttekið {date}', + description: 'Undirtitill á yfirliti ákæru fyrir fangelsi', }, infoCardDefendantsTitle: { id: 'judicial.system.core:indictment_overview.info_card_defendants_title', diff --git a/apps/judicial-system/web/src/routes/Prison/IndictmentOverview/IndictmentOverview.tsx b/apps/judicial-system/web/src/routes/Prison/IndictmentOverview/IndictmentOverview.tsx index beab42f136a8..82065184df56 100644 --- a/apps/judicial-system/web/src/routes/Prison/IndictmentOverview/IndictmentOverview.tsx +++ b/apps/judicial-system/web/src/routes/Prison/IndictmentOverview/IndictmentOverview.tsx @@ -81,6 +81,13 @@ const IndictmentOverview = () => { })} )} + {defendant?.openedByPrisonAdminDate && ( + + {formatMessage(strings.indictmentReceivedTitle, { + date: formatDate(defendant.openedByPrisonAdminDate, 'PPP'), + })} + + )} diff --git a/apps/judicial-system/web/src/routes/Shared/Cases/PrisonCases.tsx b/apps/judicial-system/web/src/routes/Shared/Cases/PrisonCases.tsx index 3027b2e998f6..1e4046e8e11e 100644 --- a/apps/judicial-system/web/src/routes/Shared/Cases/PrisonCases.tsx +++ b/apps/judicial-system/web/src/routes/Shared/Cases/PrisonCases.tsx @@ -14,6 +14,7 @@ import { titles, } from '@island.is/judicial-system-web/messages' import { + CaseTag, Logo, PageHeader, SectionHeading, @@ -31,6 +32,7 @@ import { getDurationDate, } from '@island.is/judicial-system-web/src/components/Table' import Table from '@island.is/judicial-system-web/src/components/Table/Table' +import { getPrisonCaseStateTag } from '@island.is/judicial-system-web/src/components/Tags/utils' import { CaseListEntry, CaseState, @@ -217,11 +219,23 @@ export const PrisonCases: FC = () => { ), }, { - cell: () => ( - - {'Nýtt'} - - ), + cell: (row) => { + const prisonCaseState = + row.defendants && + row.defendants?.length > 0 && + row.defendants[0].openedByPrisonAdminDate + ? CaseState.RECEIVED + : CaseState.NEW + const prisonCaseStateTag = + getPrisonCaseStateTag(prisonCaseState) + + return ( + + ) + }, }, ]} generateContextMenuItems={(row) => [openCaseInNewTabMenuItem(row.id)]} diff --git a/apps/judicial-system/web/src/routes/Shared/Cases/cases.graphql b/apps/judicial-system/web/src/routes/Shared/Cases/cases.graphql index 4001b83c6fcb..0bcfe4933c95 100644 --- a/apps/judicial-system/web/src/routes/Shared/Cases/cases.graphql +++ b/apps/judicial-system/web/src/routes/Shared/Cases/cases.graphql @@ -28,6 +28,7 @@ query Cases { defenderChoice verdictViewDate isSentToPrisonAdmin + openedByPrisonAdminDate } courtDate isValidToDateInThePast diff --git a/apps/judicial-system/web/src/routes/Shared/Cases/prisonCases.graphql b/apps/judicial-system/web/src/routes/Shared/Cases/prisonCases.graphql index 04f635815e1e..321f459f4a7c 100644 --- a/apps/judicial-system/web/src/routes/Shared/Cases/prisonCases.graphql +++ b/apps/judicial-system/web/src/routes/Shared/Cases/prisonCases.graphql @@ -26,6 +26,7 @@ query PrisonCases { name noNationalId defenderChoice + openedByPrisonAdminDate } courtDate isValidToDateInThePast diff --git a/libs/judicial-system/types/src/lib/eventLog.ts b/libs/judicial-system/types/src/lib/eventLog.ts index fca7b91f81ea..5aa84c863960 100644 --- a/libs/judicial-system/types/src/lib/eventLog.ts +++ b/libs/judicial-system/types/src/lib/eventLog.ts @@ -15,6 +15,7 @@ export const eventTypes = Object.values(EventType) export enum DefendantEventType { SENT_TO_PRISON_ADMIN = 'SENT_TO_PRISON_ADMIN', + OPENED_BY_PRISON_ADMIN = 'OPENED_BY_PRISON_ADMIN', } export const defendantEventTypes = Object.values(DefendantEventType) From 68f32f0b57b33d5bfd0cd790c6ac331943da5c99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BAnar=20Vestmann?= <43557895+RunarVestmann@users.noreply.github.com> Date: Thu, 19 Dec 2024 14:46:15 +0000 Subject: [PATCH 10/19] fix(web): RSS feed - Event dates should show single digit for date if it can (#17297) Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- apps/web/pages/api/rss/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/pages/api/rss/index.ts b/apps/web/pages/api/rss/index.ts index 87ac31be6f16..f55c4c07bb22 100644 --- a/apps/web/pages/api/rss/index.ts +++ b/apps/web/pages/api/rss/index.ts @@ -180,7 +180,7 @@ export default async function handler( .map((item) => { const formattedStartDate = format( new Date(item.startDate), - 'dd. MMMM yyyy', + 'd. MMMM yyyy', { locale: localeMap[locale], }, From f31b613c66f1fa0997bf85903c78e31658496f64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gu=C3=B0j=C3=B3n=20Gu=C3=B0j=C3=B3nsson?= Date: Thu, 19 Dec 2024 15:16:05 +0000 Subject: [PATCH 11/19] feat(j-s): Deliver Subpoena to Court (#17239) * Delivers subpoena to court * Updates tests * Update apps/judicial-system/backend/src/app/modules/subpoena/test/internalSubpoenaController/deliverSubpoenaToPolice.spec.ts --------- Co-authored-by: unakb Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../src/app/modules/case/case.service.ts | 36 ++++-- .../modules/case/internalCase.controller.ts | 5 +- .../case/test/caseController/update.spec.ts | 12 ++ .../deliverProsecutorToCourtGuards.spec.ts | 25 ++-- .../src/app/modules/court/court.service.ts | 2 + .../subpoena/internalSubpoena.controller.ts | 40 ++++++- .../app/modules/subpoena/subpoena.module.ts | 2 + .../app/modules/subpoena/subpoena.service.ts | 37 ++++++ .../test/createTestingSubpoenaModule.ts | 8 +- .../deliverSubpoanaToCourtGuards.spec.ts | 29 +++++ .../deliverSubpoanaToPoliceGuards.spec.ts | 29 +++++ .../deliverSubpoenaToCourt.spec.ts | 111 ++++++++++++++++++ .../deliverSubpoenaToPolice.spec.ts | 46 ++++---- .../updateSubpoeanaGuards.spec.ts | 2 +- .../message/src/lib/message.ts | 2 + 15 files changed, 334 insertions(+), 52 deletions(-) create mode 100644 apps/judicial-system/backend/src/app/modules/subpoena/test/internalSubpoenaController/deliverSubpoanaToCourtGuards.spec.ts create mode 100644 apps/judicial-system/backend/src/app/modules/subpoena/test/internalSubpoenaController/deliverSubpoanaToPoliceGuards.spec.ts create mode 100644 apps/judicial-system/backend/src/app/modules/subpoena/test/internalSubpoenaController/deliverSubpoenaToCourt.spec.ts diff --git a/apps/judicial-system/backend/src/app/modules/case/case.service.ts b/apps/judicial-system/backend/src/app/modules/case/case.service.ts index 3adf5cac8f0d..ff03a3b13def 100644 --- a/apps/judicial-system/backend/src/app/modules/case/case.service.ts +++ b/apps/judicial-system/backend/src/app/modules/case/case.service.ts @@ -1312,18 +1312,29 @@ export class CaseService { (defendant) => defendant.id === updatedDefendant.id, )?.subpoenas?.[0]?.id !== updatedDefendant.subpoenas?.[0]?.id, ) - .map((updatedDefendant) => ({ - type: MessageType.DELIVERY_TO_POLICE_SUBPOENA, - user, - caseId: theCase.id, - elementId: [ - updatedDefendant.id, - updatedDefendant.subpoenas?.[0].id ?? '', - ], - })) + .map((updatedDefendant) => [ + { + type: MessageType.DELIVERY_TO_POLICE_SUBPOENA, + user, + caseId: theCase.id, + elementId: [ + updatedDefendant.id, + updatedDefendant.subpoenas?.[0].id ?? '', + ], + }, + { + type: MessageType.DELIVERY_TO_COURT_SUBPOENA, + user, + caseId: theCase.id, + elementId: [ + updatedDefendant.id, + updatedDefendant.subpoenas?.[0].id ?? '', + ], + }, + ]) if (messages && messages.length > 0) { - return this.messageService.sendMessagesToQueue(messages) + return this.messageService.sendMessagesToQueue(messages.flat()) } } @@ -1426,7 +1437,10 @@ export class CaseService { await this.addMessagesForCourtCaseConnectionToQueue(updatedCase, user) } } else { - if (updatedCase.prosecutorId !== theCase.prosecutorId) { + if ( + !isIndictment && + updatedCase.prosecutorId !== theCase.prosecutorId + ) { // New prosecutor await this.addMessagesForProsecutorChangeToQueue(updatedCase, user) } diff --git a/apps/judicial-system/backend/src/app/modules/case/internalCase.controller.ts b/apps/judicial-system/backend/src/app/modules/case/internalCase.controller.ts index d48a86633f14..5b65a20a1abe 100644 --- a/apps/judicial-system/backend/src/app/modules/case/internalCase.controller.ts +++ b/apps/judicial-system/backend/src/app/modules/case/internalCase.controller.ts @@ -116,7 +116,10 @@ export class InternalCaseController { ) } - @UseGuards(CaseExistsGuard) + @UseGuards( + CaseExistsGuard, + new CaseTypeGuard([...restrictionCases, ...investigationCases]), + ) @Post( `case/:caseId/${messageEndpoint[MessageType.DELIVERY_TO_COURT_PROSECUTOR]}`, ) diff --git a/apps/judicial-system/backend/src/app/modules/case/test/caseController/update.spec.ts b/apps/judicial-system/backend/src/app/modules/case/test/caseController/update.spec.ts index b3b0bb816313..51d7fd6c5d6e 100644 --- a/apps/judicial-system/backend/src/app/modules/case/test/caseController/update.spec.ts +++ b/apps/judicial-system/backend/src/app/modules/case/test/caseController/update.spec.ts @@ -897,12 +897,24 @@ describe('CaseController - Update', () => { caseId: theCase.id, elementId: [defendantId1, subpoenaId1], }, + { + type: MessageType.DELIVERY_TO_COURT_SUBPOENA, + user, + caseId: theCase.id, + elementId: [defendantId1, subpoenaId1], + }, { type: MessageType.DELIVERY_TO_POLICE_SUBPOENA, user, caseId: theCase.id, elementId: [defendantId2, subpoenaId2], }, + { + type: MessageType.DELIVERY_TO_COURT_SUBPOENA, + user, + caseId: theCase.id, + elementId: [defendantId2, subpoenaId2], + }, ]) }) }) diff --git a/apps/judicial-system/backend/src/app/modules/case/test/internalCaseController/deliverProsecutorToCourtGuards.spec.ts b/apps/judicial-system/backend/src/app/modules/case/test/internalCaseController/deliverProsecutorToCourtGuards.spec.ts index 4e5a488ca75b..3c13895d77c9 100644 --- a/apps/judicial-system/backend/src/app/modules/case/test/internalCaseController/deliverProsecutorToCourtGuards.spec.ts +++ b/apps/judicial-system/backend/src/app/modules/case/test/internalCaseController/deliverProsecutorToCourtGuards.spec.ts @@ -1,6 +1,10 @@ -import { CanActivate } from '@nestjs/common' +import { + investigationCases, + restrictionCases, +} from '@island.is/judicial-system/types' import { CaseExistsGuard } from '../../guards/caseExists.guard' +import { CaseTypeGuard } from '../../guards/caseType.guard' import { InternalCaseController } from '../../internalCase.controller' describe('InternalCaseController - Deliver prosecutor to court guards', () => { @@ -14,19 +18,12 @@ describe('InternalCaseController - Deliver prosecutor to court guards', () => { ) }) - it('should have one guards', () => { - expect(guards).toHaveLength(1) - }) - - describe('CaseExistsGuard', () => { - let guard: CanActivate - - beforeEach(() => { - guard = new guards[0]() - }) - - it('should have CaseExistsGuard as guard 1', () => { - expect(guard).toBeInstanceOf(CaseExistsGuard) + it('should have the right guard configuration', () => { + expect(guards).toHaveLength(2) + expect(new guards[0]()).toBeInstanceOf(CaseExistsGuard) + expect(guards[1]).toBeInstanceOf(CaseTypeGuard) + expect(guards[1]).toEqual({ + allowedCaseTypes: [...restrictionCases, ...investigationCases], }) }) }) diff --git a/apps/judicial-system/backend/src/app/modules/court/court.service.ts b/apps/judicial-system/backend/src/app/modules/court/court.service.ts index 33f9c74c7c83..1cfd7b5f014b 100644 --- a/apps/judicial-system/backend/src/app/modules/court/court.service.ts +++ b/apps/judicial-system/backend/src/app/modules/court/court.service.ts @@ -34,6 +34,7 @@ export enum CourtDocumentFolder { CASE_DOCUMENTS = 'Gögn málsins', COURT_DOCUMENTS = 'Dómar, úrskurðir og Þingbók', APPEAL_DOCUMENTS = 'Kæra til Landsréttar', + SUBPOENA_DOCUMENTS = 'Boðanir', } export type Subtype = Exclude | IndictmentSubtype @@ -342,6 +343,7 @@ export class CourtService { return await this.courtClientService.createCase(courtId, { caseType: isIndictment ? 'S - Ákærumál' : 'R - Rannsóknarmál', + // TODO: send a list of subtypes when CourtService supports it subtype: courtSubtype as string, status: 'Skráð', receivalDate: formatISO(receivalDate, { representation: 'date' }), diff --git a/apps/judicial-system/backend/src/app/modules/subpoena/internalSubpoena.controller.ts b/apps/judicial-system/backend/src/app/modules/subpoena/internalSubpoena.controller.ts index 87402f7b3ada..2937e657a3f4 100644 --- a/apps/judicial-system/backend/src/app/modules/subpoena/internalSubpoena.controller.ts +++ b/apps/judicial-system/backend/src/app/modules/subpoena/internalSubpoena.controller.ts @@ -67,7 +67,7 @@ export class InternalSubpoenaController { ) @ApiOkResponse({ type: DeliverResponse, - description: 'Delivers a subpoena to police', + description: 'Delivers a subpoena to the police', }) deliverSubpoenaToPolice( @Param('caseId') caseId: string, @@ -79,7 +79,7 @@ export class InternalSubpoenaController { @Body() deliverDto: DeliverDto, ): Promise { this.logger.debug( - `Delivering subpoena ${subpoenaId} to police for defendant ${defendantId} of case ${caseId}`, + `Delivering subpoena ${subpoenaId} pdf to police for defendant ${defendantId} of case ${caseId}`, ) return this.subpoenaService.deliverSubpoenaToPolice( @@ -89,4 +89,40 @@ export class InternalSubpoenaController { deliverDto.user, ) } + + @UseGuards( + CaseExistsGuard, + new CaseTypeGuard(indictmentCases), + DefendantExistsGuard, + SubpoenaExistsGuard, + ) + @Post( + `case/:caseId/${ + messageEndpoint[MessageType.DELIVERY_TO_COURT_SUBPOENA] + }/:defendantId/:subpoenaId`, + ) + @ApiOkResponse({ + type: DeliverResponse, + description: 'Delivers a subpoena to the court', + }) + deliverSubpoenaToCourt( + @Param('caseId') caseId: string, + @Param('defendantId') defendantId: string, + @Param('subpoenaId') subpoenaId: string, + @CurrentCase() theCase: Case, + @CurrentDefendant() defendant: Defendant, + @CurrentSubpoena() subpoena: Subpoena, + @Body() deliverDto: DeliverDto, + ): Promise { + this.logger.debug( + `Delivering subpoena ${subpoenaId} pdf to court for defendant ${defendantId} of case ${caseId}`, + ) + + return this.subpoenaService.deliverSubpoenaToCourt( + theCase, + defendant, + subpoena, + deliverDto.user, + ) + } } diff --git a/apps/judicial-system/backend/src/app/modules/subpoena/subpoena.module.ts b/apps/judicial-system/backend/src/app/modules/subpoena/subpoena.module.ts index 31fba1e49734..05be74045e18 100644 --- a/apps/judicial-system/backend/src/app/modules/subpoena/subpoena.module.ts +++ b/apps/judicial-system/backend/src/app/modules/subpoena/subpoena.module.ts @@ -4,6 +4,7 @@ import { SequelizeModule } from '@nestjs/sequelize' import { MessageModule } from '@island.is/judicial-system/message' import { CaseModule } from '../case/case.module' +import { CourtModule } from '../court/court.module' import { DefendantModule } from '../defendant/defendant.module' import { Defendant } from '../defendant/models/defendant.model' import { EventModule } from '../event/event.module' @@ -23,6 +24,7 @@ import { SubpoenaService } from './subpoena.service' forwardRef(() => MessageModule), forwardRef(() => EventModule), forwardRef(() => DefendantModule), + CourtModule, SequelizeModule.forFeature([Subpoena, Defendant]), ], controllers: [ diff --git a/apps/judicial-system/backend/src/app/modules/subpoena/subpoena.service.ts b/apps/judicial-system/backend/src/app/modules/subpoena/subpoena.service.ts index e2d3116d4aa5..a83405e8a488 100644 --- a/apps/judicial-system/backend/src/app/modules/subpoena/subpoena.service.ts +++ b/apps/judicial-system/backend/src/app/modules/subpoena/subpoena.service.ts @@ -31,6 +31,7 @@ import { import { Case } from '../case/models/case.model' import { PdfService } from '../case/pdf.service' +import { CourtDocumentFolder, CourtService } from '../court' import { DefendantService } from '../defendant/defendant.service' import { Defendant } from '../defendant/models/defendant.model' import { EventService } from '../event' @@ -93,6 +94,7 @@ export class SubpoenaService { private readonly fileService: FileService, private readonly eventService: EventService, private readonly defendantService: DefendantService, + private readonly courtService: CourtService, @Inject(LOGGER_PROVIDER) private readonly logger: Logger, ) {} @@ -356,6 +358,41 @@ export class SubpoenaService { } } + async deliverSubpoenaToCourt( + theCase: Case, + defendant: Defendant, + subpoena: Subpoena, + user: TUser, + ): Promise { + return this.pdfService + .getSubpoenaPdf(theCase, defendant, subpoena) + .then(async (pdf) => { + const fileName = `Fyrirkall - ${defendant.name}` + + return this.courtService.createDocument( + user, + theCase.id, + theCase.courtId, + theCase.courtCaseNumber, + CourtDocumentFolder.SUBPOENA_DOCUMENTS, + fileName, + `${fileName}.pdf`, + 'application/pdf', + pdf, + ) + }) + .then(() => ({ delivered: true })) + .catch((reason) => { + // Tolerate failure, but log error + this.logger.warn( + `Failed to upload subpoena ${subpoena.id} pdf to court for defendant ${defendant.id} of case ${theCase.id}`, + { reason }, + ) + + return { delivered: false } + }) + } + async getSubpoena(subpoena: Subpoena, user?: TUser): Promise { if (!subpoena.subpoenaId) { // The subpoena has not been delivered to the police diff --git a/apps/judicial-system/backend/src/app/modules/subpoena/test/createTestingSubpoenaModule.ts b/apps/judicial-system/backend/src/app/modules/subpoena/test/createTestingSubpoenaModule.ts index e397c3e498ac..c4982ece5817 100644 --- a/apps/judicial-system/backend/src/app/modules/subpoena/test/createTestingSubpoenaModule.ts +++ b/apps/judicial-system/backend/src/app/modules/subpoena/test/createTestingSubpoenaModule.ts @@ -13,6 +13,7 @@ import { import { MessageService } from '@island.is/judicial-system/message' import { CaseService, PdfService } from '../../case' +import { CourtService } from '../../court' import { Defendant, DefendantService } from '../../defendant' import { EventService } from '../../event' import { FileService } from '../../file' @@ -24,6 +25,7 @@ import { Subpoena } from '../models/subpoena.model' import { SubpoenaController } from '../subpoena.controller' import { SubpoenaService } from '../subpoena.service' +jest.mock('@island.is/judicial-system/message') jest.mock('../../user/user.service') jest.mock('../../case/case.service') jest.mock('../../case/pdf.service') @@ -31,7 +33,7 @@ jest.mock('../../police/police.service') jest.mock('../../file/file.service') jest.mock('../../event/event.service') jest.mock('../../defendant/defendant.service') -jest.mock('@island.is/judicial-system/message') +jest.mock('../../court/court.service') export const createTestingSubpoenaModule = async () => { const subpoenaModule = await Test.createTestingModule({ @@ -51,6 +53,7 @@ export const createTestingSubpoenaModule = async () => { FileService, EventService, DefendantService, + CourtService, { provide: LOGGER_PROVIDER, useValue: { @@ -94,6 +97,8 @@ export const createTestingSubpoenaModule = async () => { const fileService = subpoenaModule.get(FileService) + const courtService = subpoenaModule.get(CourtService) + const subpoenaModel = await subpoenaModule.resolve( getModelToken(Subpoena), ) @@ -118,6 +123,7 @@ export const createTestingSubpoenaModule = async () => { pdfService, policeService, fileService, + courtService, subpoenaModel, subpoenaService, subpoenaController, diff --git a/apps/judicial-system/backend/src/app/modules/subpoena/test/internalSubpoenaController/deliverSubpoanaToCourtGuards.spec.ts b/apps/judicial-system/backend/src/app/modules/subpoena/test/internalSubpoenaController/deliverSubpoanaToCourtGuards.spec.ts new file mode 100644 index 000000000000..448185532c87 --- /dev/null +++ b/apps/judicial-system/backend/src/app/modules/subpoena/test/internalSubpoenaController/deliverSubpoanaToCourtGuards.spec.ts @@ -0,0 +1,29 @@ +import { indictmentCases } from '@island.is/judicial-system/types' + +import { CaseExistsGuard, CaseTypeGuard } from '../../../case' +import { DefendantExistsGuard } from '../../../defendant' +import { SubpoenaExistsGuard } from '../../guards/subpoenaExists.guard' +import { InternalSubpoenaController } from '../../internalSubpoena.controller' + +describe('InternalSubpoenaController - Deliver subpoena to court guards', () => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let guards: any[] + + beforeEach(() => { + guards = Reflect.getMetadata( + '__guards__', + InternalSubpoenaController.prototype.deliverSubpoenaToCourt, + ) + }) + + it('should have the right guard configuration', () => { + expect(guards).toHaveLength(4) + expect(new guards[0]()).toBeInstanceOf(CaseExistsGuard) + expect(guards[1]).toBeInstanceOf(CaseTypeGuard) + expect(guards[1]).toEqual({ + allowedCaseTypes: indictmentCases, + }) + expect(new guards[2]()).toBeInstanceOf(DefendantExistsGuard) + expect(new guards[3]()).toBeInstanceOf(SubpoenaExistsGuard) + }) +}) diff --git a/apps/judicial-system/backend/src/app/modules/subpoena/test/internalSubpoenaController/deliverSubpoanaToPoliceGuards.spec.ts b/apps/judicial-system/backend/src/app/modules/subpoena/test/internalSubpoenaController/deliverSubpoanaToPoliceGuards.spec.ts new file mode 100644 index 000000000000..4f1663e3cc03 --- /dev/null +++ b/apps/judicial-system/backend/src/app/modules/subpoena/test/internalSubpoenaController/deliverSubpoanaToPoliceGuards.spec.ts @@ -0,0 +1,29 @@ +import { indictmentCases } from '@island.is/judicial-system/types' + +import { CaseExistsGuard, CaseTypeGuard } from '../../../case' +import { DefendantExistsGuard } from '../../../defendant' +import { SubpoenaExistsGuard } from '../../guards/subpoenaExists.guard' +import { InternalSubpoenaController } from '../../internalSubpoena.controller' + +describe('InternalSubpoenaController - Deliver subpoena to police guards', () => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let guards: any[] + + beforeEach(() => { + guards = Reflect.getMetadata( + '__guards__', + InternalSubpoenaController.prototype.deliverSubpoenaToPolice, + ) + }) + + it('should have the right guard configuration', () => { + expect(guards).toHaveLength(4) + expect(new guards[0]()).toBeInstanceOf(CaseExistsGuard) + expect(guards[1]).toBeInstanceOf(CaseTypeGuard) + expect(guards[1]).toEqual({ + allowedCaseTypes: indictmentCases, + }) + expect(new guards[2]()).toBeInstanceOf(DefendantExistsGuard) + expect(new guards[3]()).toBeInstanceOf(SubpoenaExistsGuard) + }) +}) diff --git a/apps/judicial-system/backend/src/app/modules/subpoena/test/internalSubpoenaController/deliverSubpoenaToCourt.spec.ts b/apps/judicial-system/backend/src/app/modules/subpoena/test/internalSubpoenaController/deliverSubpoenaToCourt.spec.ts new file mode 100644 index 000000000000..1601f396fc59 --- /dev/null +++ b/apps/judicial-system/backend/src/app/modules/subpoena/test/internalSubpoenaController/deliverSubpoenaToCourt.spec.ts @@ -0,0 +1,111 @@ +import { uuid } from 'uuidv4' + +import { createTestingSubpoenaModule } from '../createTestingSubpoenaModule' + +import { Case, PdfService } from '../../../case' +import { CourtService } from '../../../court' +import { Defendant } from '../../../defendant' +import { DeliverDto } from '../../dto/deliver.dto' +import { DeliverResponse } from '../../models/deliver.response' +import { Subpoena } from '../../models/subpoena.model' + +interface Then { + result: DeliverResponse + error: Error +} + +type GivenWhenThen = () => Promise + +describe('InternalSubpoenaController - Deliver subpoena to court', () => { + const caseId = uuid() + const courtId = uuid() + const courtCaseNumber = uuid() + const subpoenaId = uuid() + const defendantId = uuid() + const defendantName = uuid() + + const subpoena = { id: subpoenaId } as Subpoena + const defendant = { + id: defendantId, + name: defendantName, + subpoenas: [subpoena], + } as Defendant + const theCase = { + id: caseId, + courtId, + courtCaseNumber, + defendants: [defendant], + } as Case + const user = { id: uuid() } + const dto = { user } as DeliverDto + + let mockPdfService: PdfService + let mockCourtService: CourtService + let givenWhenThen: GivenWhenThen + + beforeEach(async () => { + const { courtService, pdfService, internalSubpoenaController } = + await createTestingSubpoenaModule() + + mockPdfService = pdfService + const mockGetSubpoenaPdf = mockPdfService.getSubpoenaPdf as jest.Mock + mockGetSubpoenaPdf.mockRejectedValue(new Error('Some error')) + + mockCourtService = courtService + const mockCreateDocument = mockCourtService.createDocument as jest.Mock + mockCreateDocument.mockRejectedValue(new Error('Some error')) + + givenWhenThen = async () => { + const then = {} as Then + + await internalSubpoenaController + .deliverSubpoenaToCourt( + caseId, + defendantId, + subpoenaId, + theCase, + defendant, + subpoena, + dto, + ) + .then((result) => (then.result = result)) + .catch((error) => (then.error = error)) + + return then + } + }) + + describe('subpoena delivered to court', () => { + const subpoenaPdf = uuid() + let then: Then + + beforeEach(async () => { + const mockGetSubpoenaPdf = mockPdfService.getSubpoenaPdf as jest.Mock + mockGetSubpoenaPdf.mockResolvedValue(subpoenaPdf) + const mockCreateDocument = mockCourtService.createDocument as jest.Mock + mockCreateDocument.mockResolvedValue('') + + then = await givenWhenThen() + }) + + it('should deliver the subpoena', () => { + expect(mockPdfService.getSubpoenaPdf).toBeCalledWith( + theCase, + defendant, + subpoena, + ) + expect(mockCourtService.createDocument).toBeCalledWith( + user, + caseId, + courtId, + courtCaseNumber, + 'Boðanir', + `Fyrirkall - ${defendantName}`, + `Fyrirkall - ${defendantName}.pdf`, + 'application/pdf', + subpoenaPdf, + ) + expect(then.result).toEqual({ delivered: true }) + }) + }) +}) diff --git a/apps/judicial-system/backend/src/app/modules/subpoena/test/internalSubpoenaController/deliverSubpoenaToPolice.spec.ts b/apps/judicial-system/backend/src/app/modules/subpoena/test/internalSubpoenaController/deliverSubpoenaToPolice.spec.ts index 43255bd30aa1..ff02a709d797 100644 --- a/apps/judicial-system/backend/src/app/modules/subpoena/test/internalSubpoenaController/deliverSubpoenaToPolice.spec.ts +++ b/apps/judicial-system/backend/src/app/modules/subpoena/test/internalSubpoenaController/deliverSubpoenaToPolice.spec.ts @@ -2,14 +2,14 @@ import { uuid } from 'uuidv4' import { createTestingSubpoenaModule } from '../createTestingSubpoenaModule' -import { Case } from '../../../case' +import { Case, PdfService } from '../../../case' import { Defendant } from '../../../defendant' import { DeliverDto } from '../../dto/deliver.dto' import { DeliverResponse } from '../../models/deliver.response' import { Subpoena } from '../../models/subpoena.model' -import { SubpoenaService } from '../../subpoena.service' interface Then { + result: DeliverResponse error: Error } @@ -22,57 +22,59 @@ describe('InternalSubpoenaController - Deliver subpoena to police', () => { const subpoena = { id: subpoenaId } as Subpoena const defendant = { id: defendantId, subpoenas: [subpoena] } as Defendant - const theCase = { id: caseId } as Case - const user = { user: { id: uuid() } } as DeliverDto - const delivered = { delivered: true } as DeliverResponse + const theCase = { id: caseId, defendants: [defendant] } as Case + const user = { id: uuid() } + const dto = { user } as DeliverDto - let mockSubpoenaService: SubpoenaService + let mockPdfService: PdfService let givenWhenThen: GivenWhenThen beforeEach(async () => { - const { subpoenaService, internalSubpoenaController } = + const { pdfService, internalSubpoenaController } = await createTestingSubpoenaModule() - mockSubpoenaService = subpoenaService - - const deliverSubpoenaToPoliceMock = jest.fn() - mockSubpoenaService.deliverSubpoenaToPolice = deliverSubpoenaToPoliceMock - - deliverSubpoenaToPoliceMock.mockResolvedValueOnce(delivered) + mockPdfService = pdfService + const mockGetSubpoenaPdf = mockPdfService.getSubpoenaPdf as jest.Mock + mockGetSubpoenaPdf.mockRejectedValue(new Error('Some error')) givenWhenThen = async () => { const then = {} as Then - try { - await internalSubpoenaController.deliverSubpoenaToPolice( + await internalSubpoenaController + .deliverSubpoenaToPolice( caseId, defendantId, subpoenaId, theCase, defendant, subpoena, - user, + dto, ) - } catch (error) { - then.error = error as Error - } + .then((result) => (then.result = result)) + .catch((error) => (then.error = error)) return then } }) describe('subpoena delivered to police', () => { + const subpoenaPdf = uuid() + let then: Then + beforeEach(async () => { - await givenWhenThen() + const mockGetSubpoenaPdf = mockPdfService.getSubpoenaPdf as jest.Mock + mockGetSubpoenaPdf.mockResolvedValue(subpoenaPdf) + + then = await givenWhenThen() }) it('should call deliverSubpoenaToPolice', () => { - expect(mockSubpoenaService.deliverSubpoenaToPolice).toHaveBeenCalledWith( + expect(mockPdfService.getSubpoenaPdf).toBeCalledWith( theCase, defendant, subpoena, - user.user, ) + // TODO: complete tests when all indictments are generated }) }) }) diff --git a/apps/judicial-system/backend/src/app/modules/subpoena/test/internalSubpoenaController/updateSubpoeanaGuards.spec.ts b/apps/judicial-system/backend/src/app/modules/subpoena/test/internalSubpoenaController/updateSubpoeanaGuards.spec.ts index 1cad7bd28cdb..33106b7cc5a8 100644 --- a/apps/judicial-system/backend/src/app/modules/subpoena/test/internalSubpoenaController/updateSubpoeanaGuards.spec.ts +++ b/apps/judicial-system/backend/src/app/modules/subpoena/test/internalSubpoenaController/updateSubpoeanaGuards.spec.ts @@ -1,7 +1,7 @@ import { PoliceSubpoenaExistsGuard } from '../../guards/policeSubpoenaExists.guard' import { InternalSubpoenaController } from '../../internalSubpoena.controller' -describe('InternalSubpoenaController - Update subpoena guards', () => { +describe('InternalSubpoenaController - Update subpoena guards', () => { // eslint-disable-next-line @typescript-eslint/no-explicit-any let guards: any[] diff --git a/libs/judicial-system/message/src/lib/message.ts b/libs/judicial-system/message/src/lib/message.ts index bb32cdc84e05..2472dfa4c37c 100644 --- a/libs/judicial-system/message/src/lib/message.ts +++ b/libs/judicial-system/message/src/lib/message.ts @@ -11,6 +11,7 @@ export enum MessageType { DELIVERY_TO_COURT_CASE_FILE = 'DELIVERY_TO_COURT_CASE_FILE', DELIVERY_TO_COURT_CASE_FILES_RECORD = 'DELIVERY_TO_COURT_CASE_FILES_RECORD', DELIVERY_TO_COURT_REQUEST = 'DELIVERY_TO_COURT_REQUEST', + DELIVERY_TO_COURT_SUBPOENA = 'DELIVERY_TO_COURT_SUBPOENA', DELIVERY_TO_COURT_COURT_RECORD = 'DELIVERY_TO_COURT_COURT_RECORD', DELIVERY_TO_COURT_SIGNED_RULING = 'DELIVERY_TO_COURT_SIGNED_RULING', DELIVERY_TO_COURT_CASE_CONCLUSION = 'DELIVERY_TO_COURT_CASE_CONCLUSION', @@ -49,6 +50,7 @@ export const messageEndpoint: { [key in MessageType]: string } = { DELIVERY_TO_COURT_CASE_FILE: 'deliverCaseFileToCourt', DELIVERY_TO_COURT_CASE_FILES_RECORD: 'deliverCaseFilesRecordToCourt', DELIVERY_TO_COURT_REQUEST: 'deliverRequestToCourt', + DELIVERY_TO_COURT_SUBPOENA: 'deliverSubpoenaToCourt', DELIVERY_TO_COURT_COURT_RECORD: 'deliverCourtRecordToCourt', DELIVERY_TO_COURT_SIGNED_RULING: 'deliverSignedRulingToCourt', DELIVERY_TO_COURT_CASE_CONCLUSION: 'deliverCaseConclusionToCourt', From 53fccb67f2501a2294d8795b7f9d1eebb1775352 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=B3rhildur=20Thorleiksd=C3=B3ttir?= <16031078+thorhildurt@users.noreply.github.com> Date: Thu, 19 Dec 2024 15:38:43 +0000 Subject: [PATCH 12/19] chore(j-s): remove the option to set a reviewer when a decision has been taken (#17299) Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../Indictments/Overview/Overview.tsx | 82 ++++++++++--------- 1 file changed, 42 insertions(+), 40 deletions(-) diff --git a/apps/judicial-system/web/src/routes/PublicProsecutor/Indictments/Overview/Overview.tsx b/apps/judicial-system/web/src/routes/PublicProsecutor/Indictments/Overview/Overview.tsx index 36abf477db77..5c6980497124 100644 --- a/apps/judicial-system/web/src/routes/PublicProsecutor/Indictments/Overview/Overview.tsx +++ b/apps/judicial-system/web/src/routes/PublicProsecutor/Indictments/Overview/Overview.tsx @@ -121,48 +121,50 @@ export const Overview = () => { - - - {fm(strings.reviewerSubtitle, { - isFine: - workingCase.indictmentRulingDecision === - CaseIndictmentRulingDecision.FINE, - indictmentAppealDeadline: formatDate( - workingCase.indictmentAppealDeadline, - ), - appealDeadlineIsInThePast: - workingCase.indictmentVerdictAppealDeadlineExpired, - })} - - } - /> - - { + setSelectedIndictmentReviewer(value as Option) + }} + isDisabled={loading} + required + /> + + + )} Date: Thu, 19 Dec 2024 16:03:42 +0000 Subject: [PATCH 13/19] feat(web): Article Institution Panel - Read eyebrow text from org namespace (#17292) Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- apps/web/screens/Article/Article.tsx | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/apps/web/screens/Article/Article.tsx b/apps/web/screens/Article/Article.tsx index 1ca78a49e537..16bfd88f3b59 100644 --- a/apps/web/screens/Article/Article.tsx +++ b/apps/web/screens/Article/Article.tsx @@ -262,12 +262,14 @@ interface ArticleSidebarProps { article: Article activeSlug?: string | string[] n: (s: string) => string + organizationNamespace: Record } const ArticleSidebar: FC> = ({ article, activeSlug, n, + organizationNamespace, }) => { const { linkResolver } = useLinkResolver() const { activeLocale } = useI18n() @@ -296,7 +298,9 @@ const ArticleSidebar: FC> = ({ {article?.organization && article.organization.length > 0 && ( > = ({ /> )} {article?.subArticles && article.subArticles.length > 0 && ( - + )} []; slug: string }[] stepperNamespace: GetNamespaceQuery['getNamespace'] + organizationNamespace: Record } const ArticleScreen: Screen = ({ @@ -332,6 +342,7 @@ const ArticleScreen: Screen = ({ namespace, stepperNamespace, stepOptionsFromNamespace, + organizationNamespace, }) => { const { activeLocale } = useI18n() const portalRef = useRef() @@ -540,7 +551,8 @@ const ArticleScreen: Screen = ({ // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-expect-error make web strict title: article.organization[0].title, - label: n('organization'), + label: + organizationNamespace?.['organization'] ?? n('organization'), // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-expect-error make web strict href: getOrganizationLink( @@ -616,6 +628,7 @@ const ArticleScreen: Screen = ({ article={article} n={n} activeSlug={query.subSlug} + organizationNamespace={organizationNamespace} /> } @@ -648,6 +661,7 @@ const ArticleScreen: Screen = ({ n={n} activeSlug={query.subSlug} isMenuDialog + organizationNamespace={organizationNamespace} /> )} @@ -774,6 +788,7 @@ const ArticleScreen: Screen = ({ n={n} activeSlug={query.subSlug} isMenuDialog + organizationNamespace={organizationNamespace} /> )} @@ -910,7 +925,7 @@ ArticleScreen.getProps = async ({ apolloClient, query, locale }) => { const organizationNamespace = extractNamespaceFromOrganization( article.organization?.[0], - ) + ) as Record return { article, @@ -918,6 +933,7 @@ ArticleScreen.getProps = async ({ apolloClient, query, locale }) => { stepOptionsFromNamespace, stepperNamespace, customTopLoginButtonItem: organizationNamespace?.customTopLoginButtonItem, + organizationNamespace, ...getThemeConfig(article), } } From b76b7dd90c5cd6b8a66ac34cda263c294a905ae3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=B3rhildur=20Thorleiksd=C3=B3ttir?= <16031078+thorhildurt@users.noreply.github.com> Date: Thu, 19 Dec 2024 19:33:50 +0000 Subject: [PATCH 14/19] feat(j-s): Add punishment type tag column in prison cases overview table (#17285) * feat(j-s): js web utility array function * feat(j-s): add punishment type columns in prison cases overview * fix(j-s): fix sort by punishement type * fix(j-s): format and clean-up * fix(j-s): Address auto code review feedback * fix(j-s): fix tests * chore: nx format:write update dirty files --------- Co-authored-by: andes-it Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../case-list/models/caseList.model.ts | 7 +++ .../web/messages/Core/tables.ts | 5 +++ .../web/src/components/Table/Table.tsx | 2 + .../src/components/Tags/CaseTag.strings.ts | 25 +++++++++++ .../web/src/components/Tags/utils.ts | 32 +++++++++++++ .../src/routes/Shared/Cases/PrisonCases.tsx | 24 +++++++++- .../web/src/routes/Shared/Cases/cases.graphql | 2 + .../routes/Shared/Cases/prisonCases.graphql | 1 + .../web/src/utils/arrayHelpers.spec.ts | 45 +++++++++++++++++++ .../web/src/utils/arrayHelpers.ts | 8 ++++ 10 files changed, 150 insertions(+), 1 deletion(-) create mode 100644 apps/judicial-system/web/src/utils/arrayHelpers.spec.ts create mode 100644 apps/judicial-system/web/src/utils/arrayHelpers.ts diff --git a/apps/judicial-system/api/src/app/modules/case-list/models/caseList.model.ts b/apps/judicial-system/api/src/app/modules/case-list/models/caseList.model.ts index 6372ac2e17d2..b5bf4659ebcc 100644 --- a/apps/judicial-system/api/src/app/modules/case-list/models/caseList.model.ts +++ b/apps/judicial-system/api/src/app/modules/case-list/models/caseList.model.ts @@ -11,6 +11,7 @@ import { CourtSessionType, IndictmentCaseReviewDecision, IndictmentDecision, + PunishmentType, } from '@island.is/judicial-system/types' import { Defendant } from '../../defendant' @@ -142,4 +143,10 @@ export class CaseListEntry { @Field(() => String, { nullable: true }) readonly indictmentCompletedDate?: string + + // TEMP: Use with caution! This key will never be populated. + // It was added to bypass table component type checks for a required custom sort key + // until we have a resolution on how to handle multiple defendants in the case list + @Field(() => PunishmentType, { nullable: true }) + readonly defendantsPunishmentType?: PunishmentType } diff --git a/apps/judicial-system/web/messages/Core/tables.ts b/apps/judicial-system/web/messages/Core/tables.ts index b2b2500d65bb..c17703f0fbf6 100644 --- a/apps/judicial-system/web/messages/Core/tables.ts +++ b/apps/judicial-system/web/messages/Core/tables.ts @@ -113,6 +113,11 @@ export const tables = defineMessages({ defaultMessage: 'Dómstóll', description: 'Notaður sem titill fyrir dómstóll dálk í lista yfir mál.', }, + punishmentType: { + id: 'judicial.system.core:tables.punishment_type', + defaultMessage: 'Refsitegund', + description: 'Notaður sem titill fyrir refsitegund dálk í lista yfir mál.', + }, sentencingDate: { id: 'judicial.system.core:tables.sentencing_date', defaultMessage: 'Dómsuppkvaðning', diff --git a/apps/judicial-system/web/src/components/Table/Table.tsx b/apps/judicial-system/web/src/components/Table/Table.tsx index 8e572422391c..13e6a3cad088 100644 --- a/apps/judicial-system/web/src/components/Table/Table.tsx +++ b/apps/judicial-system/web/src/components/Table/Table.tsx @@ -180,6 +180,8 @@ const Table: FC = (props) => { switch (column) { case 'defendants': return entry.defendants?.[0]?.name ?? '' + case 'defendantsPunishmentType': + return entry.defendants?.[0]?.punishmentType ?? '' case 'courtCaseNumber': return courtAbbreviation ? `${courtAbbreviation}: ${entry.courtCaseNumber}` diff --git a/apps/judicial-system/web/src/components/Tags/CaseTag.strings.ts b/apps/judicial-system/web/src/components/Tags/CaseTag.strings.ts index 28f7807d82f3..0119c6c284ab 100644 --- a/apps/judicial-system/web/src/components/Tags/CaseTag.strings.ts +++ b/apps/judicial-system/web/src/components/Tags/CaseTag.strings.ts @@ -97,4 +97,29 @@ export const strings = defineMessages({ defaultMessage: 'Afturkallað', description: 'Notað fyrir "Afturkallað" tagg', }, + punishmentTypeImprisonment: { + id: 'judicial.system.core:case_tag.punishment_type_imprisonment', + defaultMessage: 'Óskb.', + description: 'Notað fyrir "Óskilorðsbundið" tagg', + }, + punishmentTypeProbation: { + id: 'judicial.system.core:case_tag.punishment_type_probation', + defaultMessage: 'Skb.', + description: 'Notað fyrir "Skilorðsbundið" tagg', + }, + punishmentTypeFine: { + id: 'judicial.system.core:case_tag.punishment_type_fine', + defaultMessage: 'Sekt', + description: 'Notað fyrir "Sekt" tagg', + }, + punishmentTypeIndictmentRulingDecisionFine: { + id: 'judicial.system.core:case_tag.punishment_type_indictment_ruling_decision_fine', + defaultMessage: 'VL', + description: 'Notað fyrir "Viðurlagaákvörðun" tagg', + }, + punishmentTypeSignedFineInvitation: { + id: 'judicial.system.core:case_tag.punishment_type_signed_fine_invitation', + defaultMessage: 'ÁS', + description: 'Notað fyrir "Áritað sektarboð" tagg', + }, }) diff --git a/apps/judicial-system/web/src/components/Tags/utils.ts b/apps/judicial-system/web/src/components/Tags/utils.ts index 1b047e13806a..51cc830a199e 100644 --- a/apps/judicial-system/web/src/components/Tags/utils.ts +++ b/apps/judicial-system/web/src/components/Tags/utils.ts @@ -2,6 +2,7 @@ import { TagVariant } from '@island.is/island-ui/core' import { isDistrictCourtUser, isPublicProsecutorUser, + PunishmentType, } from '@island.is/judicial-system/types' import { @@ -120,6 +121,37 @@ export const getIndictmentRulingDecisionTag = ( } } +export const getPunishmentTypeTag = ( + punishmentType?: PunishmentType | null, +): { + color: TagVariant + text: { id: string; defaultMessage: string; description: string } +} | null => { + if (!punishmentType) return null + + const getPunishmentTypeLabel = (punishmentType?: PunishmentType | null) => { + switch (punishmentType) { + case PunishmentType.IMPRISONMENT: + return strings.punishmentTypeImprisonment + case PunishmentType.PROBATION: + return strings.punishmentTypeProbation + case PunishmentType.FINE: + return strings.punishmentTypeFine + case PunishmentType.INDICTMENT_RULING_DECISION_FINE: + return strings.punishmentTypeIndictmentRulingDecisionFine + case PunishmentType.SIGNED_FINE_INVITATION: + return strings.punishmentTypeSignedFineInvitation + default: + return strings.unknown + } + } + + return { + color: 'red' as TagVariant, + text: getPunishmentTypeLabel(punishmentType), + } +} + export const getPrisonCaseStateTag = ( prisonCaseState: CaseState, ): { diff --git a/apps/judicial-system/web/src/routes/Shared/Cases/PrisonCases.tsx b/apps/judicial-system/web/src/routes/Shared/Cases/PrisonCases.tsx index 1e4046e8e11e..1c00c16dde09 100644 --- a/apps/judicial-system/web/src/routes/Shared/Cases/PrisonCases.tsx +++ b/apps/judicial-system/web/src/routes/Shared/Cases/PrisonCases.tsx @@ -32,13 +32,17 @@ import { getDurationDate, } from '@island.is/judicial-system-web/src/components/Table' import Table from '@island.is/judicial-system-web/src/components/Table/Table' -import { getPrisonCaseStateTag } from '@island.is/judicial-system-web/src/components/Tags/utils' +import { + getPrisonCaseStateTag, + getPunishmentTypeTag, +} from '@island.is/judicial-system-web/src/components/Tags/utils' import { CaseListEntry, CaseState, CaseType, InstitutionType, } from '@island.is/judicial-system-web/src/graphql/schema' +import { isNonEmptyArray } from '@island.is/judicial-system-web/src/utils/arrayHelpers' import { usePrisonCasesQuery } from './prisonCases.generated' import { cases as m } from './Cases.strings' @@ -183,6 +187,10 @@ export const PrisonCases: FC = () => { { title: formatMessage(tables.court), }, + { + title: formatMessage(tables.punishmentType), + sortable: { isSortable: true, key: 'defendantsPunishmentType' }, + }, { title: capitalize(formatMessage(tables.sentencingDate)), }, @@ -213,6 +221,20 @@ export const PrisonCases: FC = () => { { cell: (row) => , }, + { + cell: (row) => { + const punishmentType = isNonEmptyArray(row.defendants) + ? row.defendants[0].punishmentType + : undefined + const punishmentTypeTag = getPunishmentTypeTag(punishmentType) + return punishmentTypeTag ? ( + + ) : null + }, + }, { cell: (row) => ( diff --git a/apps/judicial-system/web/src/routes/Shared/Cases/cases.graphql b/apps/judicial-system/web/src/routes/Shared/Cases/cases.graphql index 0bcfe4933c95..b188cbfac1aa 100644 --- a/apps/judicial-system/web/src/routes/Shared/Cases/cases.graphql +++ b/apps/judicial-system/web/src/routes/Shared/Cases/cases.graphql @@ -28,8 +28,10 @@ query Cases { defenderChoice verdictViewDate isSentToPrisonAdmin + punishmentType openedByPrisonAdminDate } + defendantsPunishmentType courtDate isValidToDateInThePast initialRulingDate diff --git a/apps/judicial-system/web/src/routes/Shared/Cases/prisonCases.graphql b/apps/judicial-system/web/src/routes/Shared/Cases/prisonCases.graphql index 321f459f4a7c..98b6483bff99 100644 --- a/apps/judicial-system/web/src/routes/Shared/Cases/prisonCases.graphql +++ b/apps/judicial-system/web/src/routes/Shared/Cases/prisonCases.graphql @@ -26,6 +26,7 @@ query PrisonCases { name noNationalId defenderChoice + punishmentType openedByPrisonAdminDate } courtDate diff --git a/apps/judicial-system/web/src/utils/arrayHelpers.spec.ts b/apps/judicial-system/web/src/utils/arrayHelpers.spec.ts new file mode 100644 index 000000000000..fa49af5eefe5 --- /dev/null +++ b/apps/judicial-system/web/src/utils/arrayHelpers.spec.ts @@ -0,0 +1,45 @@ +import { isEmptyArray, isNonEmptyArray, isPresentArray } from './arrayHelpers' + +describe('arrayHelpers', () => { + describe('isPresentArray', () => { + const testCases = [ + { input: undefined, expected: false }, + { input: null, expected: false }, + { input: [], expected: true }, + { input: [1], expected: true }, + ] + testCases.forEach(({ input, expected }) => { + it(`should return ${expected} for input ${input}`, () => { + expect(isPresentArray(input)).toBe(expected) + }) + }) + }) + + describe('isEmptyArray', () => { + const testCases = [ + { input: undefined, expected: false }, + { input: null, expected: false }, + { input: [], expected: true }, + { input: [1], expected: false }, + ] + testCases.forEach(({ input, expected }) => { + it(`should return ${expected} for input ${input}`, () => { + expect(isEmptyArray(input)).toBe(expected) + }) + }) + }) + + describe('isNonEmptyArray', () => { + const testCases = [ + { input: undefined, expected: false }, + { input: null, expected: false }, + { input: [], expected: false }, + { input: [1], expected: true }, + ] + testCases.forEach(({ input, expected }) => { + it(`should return ${expected} for input ${input}`, () => { + expect(isNonEmptyArray(input)).toBe(expected) + }) + }) + }) +}) diff --git a/apps/judicial-system/web/src/utils/arrayHelpers.ts b/apps/judicial-system/web/src/utils/arrayHelpers.ts new file mode 100644 index 000000000000..8b2d62131b7b --- /dev/null +++ b/apps/judicial-system/web/src/utils/arrayHelpers.ts @@ -0,0 +1,8 @@ +export const isPresentArray = (arr: T[] | undefined | null): arr is T[] => + arr !== undefined && arr !== null && Array.isArray(arr) + +export const isEmptyArray = (arr: T[] | undefined | null): arr is T[] => + isPresentArray(arr) && arr?.length === 0 + +export const isNonEmptyArray = (arr: T[] | undefined | null): arr is T[] => + isPresentArray(arr) && arr.length > 0 From e146770b7501d76543766fa2b6a69ea44f7e31b1 Mon Sep 17 00:00:00 2001 From: albinagu <47886428+albinagu@users.noreply.github.com> Date: Fri, 20 Dec 2024 09:08:27 +0000 Subject: [PATCH 15/19] fix(inheritance-report): Fixes for go live (#17295) Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../templates/inheritance-report/utils/mappers.ts | 3 +++ .../inheritance-report/src/forms/prerequisites.ts | 2 -- .../src/forms/sections/assets.ts | 14 ++++++++++---- .../src/forms/sections/debtsAndFuneralCost.ts | 3 +++ .../forms/sections/prepaidInheritance/applicant.ts | 2 +- .../prepaidInheritance/inheritanceExecutor.ts | 4 ++-- .../inheritance-report/src/lib/dataSchema.ts | 1 + .../inheritance-report/src/lib/messages.ts | 10 ++++++++++ .../templates/inheritance-report/src/types.ts | 1 + 9 files changed, 31 insertions(+), 9 deletions(-) diff --git a/libs/application/template-api-modules/src/lib/modules/templates/inheritance-report/utils/mappers.ts b/libs/application/template-api-modules/src/lib/modules/templates/inheritance-report/utils/mappers.ts index b4e0e2ab0f8e..87fbccb3d32e 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 @@ -157,6 +157,7 @@ export const expandAnswers = ( deceasedShare: gun.deceasedShare ?? '', deceasedShareEnabled: gun.deceasedShareEnabled ?? [], deceasedShareAmount: gun.deceasedShareAmount ?? 0, + enabled: gun.enabled ?? true, } }), total: answers.assets.guns?.total ?? 0, @@ -199,6 +200,7 @@ export const expandAnswers = ( deceasedShare: realEstate.deceasedShare ?? '0', deceasedShareEnabled: realEstate.deceasedShareEnabled ?? [], deceasedShareAmount: realEstate.deceasedShareAmount ?? 0, + enabled: realEstate.enabled ?? true, } }), total: answers.assets.realEstate?.total ?? 0, @@ -227,6 +229,7 @@ export const expandAnswers = ( deceasedShare: vehicle.deceasedShare ?? '0', deceasedShareEnabled: vehicle.deceasedShareEnabled ?? [], deceasedShareAmount: vehicle?.deceasedShareAmount ?? 0, + enabled: vehicle.enabled ?? true, } }), total: answers.assets.vehicles?.total ?? 0, diff --git a/libs/application/templates/inheritance-report/src/forms/prerequisites.ts b/libs/application/templates/inheritance-report/src/forms/prerequisites.ts index 74b17ab2358b..5296679ded4b 100644 --- a/libs/application/templates/inheritance-report/src/forms/prerequisites.ts +++ b/libs/application/templates/inheritance-report/src/forms/prerequisites.ts @@ -38,8 +38,6 @@ export const getForm = ({ value: ESTATE_INHERITANCE, label: m.preDataCollectionApplicationForDefault, disabled: !allowEstateApplication, - //TODO: remove tooltip when this application is ready to go live - tooltip: m.preDataCollectionApplicationForDefaultTooltip, }, { value: PREPAID_INHERITANCE, diff --git a/libs/application/templates/inheritance-report/src/forms/sections/assets.ts b/libs/application/templates/inheritance-report/src/forms/sections/assets.ts index a8b9ab5fe2fd..b2c3d8b511ab 100644 --- a/libs/application/templates/inheritance-report/src/forms/sections/assets.ts +++ b/libs/application/templates/inheritance-report/src/forms/sections/assets.ts @@ -345,7 +345,7 @@ export const assets = buildSection({ [ESTATE_INHERITANCE]: m.bankAccountCapital, [PREPAID_INHERITANCE]: m.bankAccountCapitalPrePaid, }, - id: 'propertyValuation', + id: 'amount', required: true, currency: true, }, @@ -376,7 +376,7 @@ export const assets = buildSection({ fromExternalData: 'bankAccounts', skipPushRight: true, repeaterButtonText: m.bankAccountRepeaterButton, - sumField: 'propertyValuation', + sumField: 'amount', sumField2: 'exchangeRateOrInterest', }, ), @@ -494,13 +494,19 @@ export const assets = buildSection({ format: '######-####', }, { - title: m.stocksFaceValue, + title: { + [ESTATE_INHERITANCE]: m.stocksFaceValue, + [PREPAID_INHERITANCE]: m.stocksFaceValuePrePaid, + }, id: 'amount', currency: true, required: true, }, { - title: m.stocksRateOfChange, + title: { + [ESTATE_INHERITANCE]: m.stocksRateOfChange, + [PREPAID_INHERITANCE]: m.stocksRateOfChangePrePaid, + }, id: 'exchangeRateOrInterest', type: 'currency', required: true, diff --git a/libs/application/templates/inheritance-report/src/forms/sections/debtsAndFuneralCost.ts b/libs/application/templates/inheritance-report/src/forms/sections/debtsAndFuneralCost.ts index ed46966a1955..8651c95870f1 100644 --- a/libs/application/templates/inheritance-report/src/forms/sections/debtsAndFuneralCost.ts +++ b/libs/application/templates/inheritance-report/src/forms/sections/debtsAndFuneralCost.ts @@ -87,6 +87,9 @@ export const debtsAndFuneralCost = buildSection({ { label: DebtTypes.OtherDebts, }, + { + label: DebtTypes.InsuranceInstitute, + }, ], }, ), diff --git a/libs/application/templates/inheritance-report/src/forms/sections/prepaidInheritance/applicant.ts b/libs/application/templates/inheritance-report/src/forms/sections/prepaidInheritance/applicant.ts index dc53eb07648d..89606f99a45b 100644 --- a/libs/application/templates/inheritance-report/src/forms/sections/prepaidInheritance/applicant.ts +++ b/libs/application/templates/inheritance-report/src/forms/sections/prepaidInheritance/applicant.ts @@ -20,7 +20,7 @@ export const prePaidApplicant = buildSection({ children: [ buildNationalIdWithNameField({ id: 'prePaidApplicant', - title: m.name, + title: '', width: 'full', required: true, }), diff --git a/libs/application/templates/inheritance-report/src/forms/sections/prepaidInheritance/inheritanceExecutor.ts b/libs/application/templates/inheritance-report/src/forms/sections/prepaidInheritance/inheritanceExecutor.ts index a411304e2a32..593efcd2b8ae 100644 --- a/libs/application/templates/inheritance-report/src/forms/sections/prepaidInheritance/inheritanceExecutor.ts +++ b/libs/application/templates/inheritance-report/src/forms/sections/prepaidInheritance/inheritanceExecutor.ts @@ -26,7 +26,7 @@ export const inheritanceExecutor = buildSection({ }), buildNationalIdWithNameField({ id: 'executors.executor', - title: m.name, + title: '', required: true, }), buildTextField({ @@ -73,7 +73,7 @@ export const inheritanceExecutor = buildSection({ }), buildNationalIdWithNameField({ id: 'executors.spouse', - title: m.name, + title: '', required: true, condition: (answers) => !!((answers.executors as any)?.includeSpouse as Array) diff --git a/libs/application/templates/inheritance-report/src/lib/dataSchema.ts b/libs/application/templates/inheritance-report/src/lib/dataSchema.ts index 3e437681836f..0e77b8b62b09 100644 --- a/libs/application/templates/inheritance-report/src/lib/dataSchema.ts +++ b/libs/application/templates/inheritance-report/src/lib/dataSchema.ts @@ -55,6 +55,7 @@ const assetSchema = ({ withShare }: { withShare?: boolean } = {}) => .refine((v) => (withShare ? validateAssetNumber(v) : true)), description: z.string(), propertyValuation: z.string(), + enabled: z.boolean(), ...(withShare ? { share: z.string() } : {}), ...deceasedShare, }) diff --git a/libs/application/templates/inheritance-report/src/lib/messages.ts b/libs/application/templates/inheritance-report/src/lib/messages.ts index 84f67d71a06a..e0b816cab74e 100644 --- a/libs/application/templates/inheritance-report/src/lib/messages.ts +++ b/libs/application/templates/inheritance-report/src/lib/messages.ts @@ -708,11 +708,21 @@ export const m = defineMessages({ }, stocksFaceValue: { id: 'ir.application:stocksFaceValue', + defaultMessage: 'Nafnverð á dánardegi', + description: '', + }, + stocksFaceValuePrePaid: { + id: 'ir.application:stocksFaceValuePrePaid', defaultMessage: 'Nafnverð', description: '', }, stocksRateOfChange: { id: 'ir.application:stocksRateOfChange', + defaultMessage: 'Gengi á dánardegi', + description: '', + }, + stocksRateOfChangePrePaid: { + id: 'ir.application:stocksRateOfChangePrePaid', defaultMessage: 'Gengi', description: '', }, diff --git a/libs/application/templates/inheritance-report/src/types.ts b/libs/application/templates/inheritance-report/src/types.ts index 5b4b730a8fbb..c23a10263a35 100644 --- a/libs/application/templates/inheritance-report/src/types.ts +++ b/libs/application/templates/inheritance-report/src/types.ts @@ -305,4 +305,5 @@ export enum DebtTypes { PropertyFees = 'Fasteignagjöld', OtherDebts = 'Aðrar skuldir', PublicCharges = 'Opinber gjöld', + InsuranceInstitute = 'Tryggingarstofnun ríkisins', } From 19553a3e988a977fd9c65b04cf591f0b66df755b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=B3berta=20Andersen?= Date: Fri, 20 Dec 2024 10:34:15 +0000 Subject: [PATCH 16/19] Uni pr (#17308) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: cd test app * fix: rename app * fix: name * fix: added dummy queries and codegen target * fix: eslint * fix: style * fix: import query * fix: import shared type * fix: affect shared/types * fix: add docker-next target * fix: nx project and infra * chore: eslint * chore: eslint * chore: remove jsx-a11y * fix: ignore css * chore: nx format:write update dirty files * Chore: adding unicorn to uber chart * fix: support values file per service * fix: cleanup * fix: added values per service * fix: only a single service please * fix: set service toplevel * fix: format * fix: use yaml not js-yaml * fix: remove release-tag arg * fix: console message * chore: remove helm values refactor * chore: remove helm values * chore: yarn charts * chore: charts update dirty files * chore: nx format:write update dirty files * chore: charts update dirty files * fix: make unaffected * Unicorn pipe (#17094) * chore: Adding pipeline for unicorns * unicorn pipe * unicorn pipe * unicorn pipe * unicorn pipe * pipetest * pipetest * pipetest * chore: nx format:write update dirty files * Update .github/workflows/unicorns.yml Co-authored-by: Jón Levy * suggestions * chore: nx format:write update dirty files * unicorn pipe * suggestions --------- Co-authored-by: andes-it Co-authored-by: Jón Levy * chore: charts update dirty files * chore: Adding pipeline to detect unicorns (#17165) * chore: Adding tests if pipeline should run on unicorns * chore: nx format:write update dirty files * chore: find release branch * chore: find release branch * chore: find release branch * chore: find release branch * chore: find release branch * chore: find release branch * chore: find release branch * chore: find release branch * chore: find release branch * chore: find release branch * chore: find release branch * chore: find release branch * chore: find release branch * chore: find release branch * chore: find release branch * chore: find release branch * chore: nx format:write update dirty files * chore: find release branch * chore: find release branch * cleanup * branch sync * mergequeue * chore: testing pipeline * chore: nx format:write update dirty files * ignore * ignore * ignore * testing pipeline * testing pipeline * testing pipeline --------- Co-authored-by: andes-it * chore: nx format:write update dirty files * chore: testing mergequeue * Adding mergequeue ci * chore: testing mergequeue * chore: testing mergequeue * Uni pr (#17280) * chore: testing mergequeue * Adding mergequeue ci * chore: testing mergequeue * chore: testing mergequeue * chore: testing mergequeue * chore: testing mergequeue * chore: nx format:write update dirty files * chore: nx format:write update dirty files * chore: testing mergequeue * chore: testing mergequeue * Uni pr (#17280) * chore: testing mergequeue * Adding mergequeue ci * chore: testing mergequeue * chore: testing mergequeue * chore: testing mergequeue --------- Co-authored-by: Jón Levy Co-authored-by: andes-it --- .github/workflows/config-values.yaml | 5 + .github/workflows/external-checks.yml | 4 +- .github/workflows/merge-queue-ci.yml | 19 + .github/workflows/pullrequest-close.yml | 3 + .github/workflows/pullrequest-lint.yml | 3 + .github/workflows/pullrequest.yml | 5 +- .github/workflows/push.yml | 5 + .github/workflows/revert-pr.yaml | 4 + .github/workflows/unicorns.yml | 106 +++++ .gitignore | 1 + apps/unicorn-app/.eslintrc.json | 55 +++ apps/unicorn-app/codegen.yml | 24 + apps/unicorn-app/index.d.ts | 6 + apps/unicorn-app/infra/infra.ts | 34 ++ apps/unicorn-app/jest.config.ts | 11 + apps/unicorn-app/next-env.d.ts | 5 + apps/unicorn-app/next.config.js | 22 + apps/unicorn-app/project.json | 101 +++++ apps/unicorn-app/public/.gitkeep | 0 apps/unicorn-app/public/favicon.ico | Bin 0 -> 15086 bytes apps/unicorn-app/server.ts | 6 + apps/unicorn-app/specs/index.spec.tsx | 11 + apps/unicorn-app/src/app/api/hello/route.ts | 3 + apps/unicorn-app/src/app/global.css | 409 ++++++++++++++++++ apps/unicorn-app/src/app/layout.tsx | 18 + apps/unicorn-app/src/app/page.module.css | 2 + apps/unicorn-app/src/app/page.tsx | 17 + apps/unicorn-app/tsconfig.json | 30 ++ apps/unicorn-app/tsconfig.server.json | 11 + apps/unicorn-app/tsconfig.spec.json | 21 + apps/unicorn-app/webpack.config.js | 8 + charts/islandis/values.dev.yaml | 64 +++ charts/islandis/values.prod.yaml | 64 +++ charts/islandis/values.staging.yaml | 64 +++ charts/services/unicorn-app/values.dev.yaml | 81 ++++ charts/services/unicorn-app/values.prod.yaml | 81 ++++ .../services/unicorn-app/values.staging.yaml | 81 ++++ infra/src/uber-charts/islandis.ts | 6 + scripts/ci/create-release.mjs | 35 ++ scripts/ci/get-last-release.mjs | 17 + scripts/ci/unicorn-utils.mjs | 17 + tsconfig.base.json | 1 + 42 files changed, 1458 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/merge-queue-ci.yml create mode 100644 .github/workflows/unicorns.yml create mode 100644 apps/unicorn-app/.eslintrc.json create mode 100644 apps/unicorn-app/codegen.yml create mode 100644 apps/unicorn-app/index.d.ts create mode 100644 apps/unicorn-app/infra/infra.ts create mode 100644 apps/unicorn-app/jest.config.ts create mode 100644 apps/unicorn-app/next-env.d.ts create mode 100644 apps/unicorn-app/next.config.js create mode 100644 apps/unicorn-app/project.json create mode 100644 apps/unicorn-app/public/.gitkeep create mode 100644 apps/unicorn-app/public/favicon.ico create mode 100644 apps/unicorn-app/server.ts create mode 100644 apps/unicorn-app/specs/index.spec.tsx create mode 100644 apps/unicorn-app/src/app/api/hello/route.ts create mode 100644 apps/unicorn-app/src/app/global.css create mode 100644 apps/unicorn-app/src/app/layout.tsx create mode 100644 apps/unicorn-app/src/app/page.module.css create mode 100644 apps/unicorn-app/src/app/page.tsx create mode 100644 apps/unicorn-app/tsconfig.json create mode 100644 apps/unicorn-app/tsconfig.server.json create mode 100644 apps/unicorn-app/tsconfig.spec.json create mode 100644 apps/unicorn-app/webpack.config.js create mode 100644 charts/services/unicorn-app/values.dev.yaml create mode 100644 charts/services/unicorn-app/values.prod.yaml create mode 100644 charts/services/unicorn-app/values.staging.yaml create mode 100644 scripts/ci/create-release.mjs create mode 100644 scripts/ci/get-last-release.mjs create mode 100644 scripts/ci/unicorn-utils.mjs diff --git a/.github/workflows/config-values.yaml b/.github/workflows/config-values.yaml index 883861366f8b..e349d4ca9c26 100644 --- a/.github/workflows/config-values.yaml +++ b/.github/workflows/config-values.yaml @@ -6,12 +6,17 @@ on: - 'main' - 'release/**' - 'pre-release/**' + - '!unicorn-pipe-rel3' + - '!feature/unicorn-app' paths: - 'charts/**' - 'infra/**' - '**/infra/**' workflow_dispatch: {} pull_request: + branches: + - '!uni-pr' + - '!feature/unicorn-app' paths: - 'charts/**' - 'infra/**' diff --git a/.github/workflows/external-checks.yml b/.github/workflows/external-checks.yml index 5707795d4f9b..816915e60af3 100644 --- a/.github/workflows/external-checks.yml +++ b/.github/workflows/external-checks.yml @@ -1,7 +1,9 @@ name: External checks on: - pull_request: {} + pull_request: + branches-ignore: + - 'feature/unicorn-app' workflow_dispatch: {} defaults: diff --git a/.github/workflows/merge-queue-ci.yml b/.github/workflows/merge-queue-ci.yml new file mode 100644 index 000000000000..2e96f313443e --- /dev/null +++ b/.github/workflows/merge-queue-ci.yml @@ -0,0 +1,19 @@ +name: Validate code in the merge queue + +on: + merge_group: + +jobs: + validate-pr: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v3 + - name: Display info + run: | + echo "Hallo Merge Queue" + pwd + tree -a -I '.git' + git status + - name: Run slow CI (emulated by a long sleep) + run: sleep 300 diff --git a/.github/workflows/pullrequest-close.yml b/.github/workflows/pullrequest-close.yml index 5d3e066f3866..76e88bbcba19 100644 --- a/.github/workflows/pullrequest-close.yml +++ b/.github/workflows/pullrequest-close.yml @@ -4,6 +4,9 @@ on: pull_request: types: - closed + branches-ignore: + - 'uni-pr' + - 'feature/unicorn-app' defaults: run: diff --git a/.github/workflows/pullrequest-lint.yml b/.github/workflows/pullrequest-lint.yml index f7b04171a1c8..41e94022f624 100644 --- a/.github/workflows/pullrequest-lint.yml +++ b/.github/workflows/pullrequest-lint.yml @@ -6,6 +6,9 @@ on: - reopened - edited - synchronize + branches-ignore: + - 'uni-pr' + - 'feature/unicorn-app' defaults: run: diff --git a/.github/workflows/pullrequest.yml b/.github/workflows/pullrequest.yml index 71eace37971b..b0de5846c2a2 100644 --- a/.github/workflows/pullrequest.yml +++ b/.github/workflows/pullrequest.yml @@ -1,7 +1,10 @@ name: Monorepo pipeline - pull request on: - pull_request: {} + pull_request: + branches-ignore: + - 'uni-pr' + - 'feature/unicorn-app' workflow_dispatch: {} defaults: diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index 15225953329b..526291f1f2fa 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -6,6 +6,8 @@ on: - 'main' - 'release/**' - 'pre-release/**' + - '!uni-pr' + - '!feature/unicorn-app' paths-ignore: - '**/*.md' tags: @@ -13,6 +15,9 @@ on: workflow_dispatch: create: pull_request: + branches: + - '!uni-pr' + - '!feature/unicorn-app' types: - opened - synchronize diff --git a/.github/workflows/revert-pr.yaml b/.github/workflows/revert-pr.yaml index 492ca4300e92..5945712005b5 100644 --- a/.github/workflows/revert-pr.yaml +++ b/.github/workflows/revert-pr.yaml @@ -8,7 +8,11 @@ on: - completed branches: - main + - '!uni-pr' + - '!feature/unicorn-app' pull_request: + branches: + - '!uni-pr' types: - opened - synchronize diff --git a/.github/workflows/unicorns.yml b/.github/workflows/unicorns.yml new file mode 100644 index 000000000000..2bf69e51d595 --- /dev/null +++ b/.github/workflows/unicorns.yml @@ -0,0 +1,106 @@ +name: Unicorn CI/CD pipeline +on: + workflow_dispatch: + create: + pull_request: + types: + - opened + - synchronize + - labeled + - closed + pull_request_review: + types: [submitted] +concurrency: + # See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#example-using-a-fallback-value + group: push-unicorn${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +defaults: + run: + shell: bash +jobs: + printJob: + name: Print event + runs-on: ubuntu-latest + steps: + - name: Dump GitHub context + env: + GITHUB_CONTEXT: ${{ toJson(github) }} + run: | + echo "$GITHUB_CONTEXT" + check-approved: + name: Is PR approved + runs-on: ubuntu-latest + outputs: + IS_APPROVED: ${{ steps.check-approved.outputs.result }} + steps: + - name: Check if PR is approved + id: check-approved + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + if (!context || !context.payload || !context.payload.pull_request) { + return false; + } + var reviews = await github.rest.pulls.listReviews({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: context.issue.number + }); + + if(!reviews || !reviews.data == null) { + return false; + } + var declined = reviews.data.filter(review => review.state != 'APPROVED').length > 0; + var pending = context.payload.pull_request.requested_teams.length > 0 + console.log(!(declined && pending) ? 'Pr is approved' : 'Pr is not approved'); + return !(declined && pending); + - name: output + run: echo "IS_APPROVED=${{ steps.check-approved.outputs.result }}" >> $GITHUB_OUTPUT + + check-unicorn: + name: Is this a unicorn PR + needs: check-approved + if: ${{ needs.check-approved.outputs.IS_APPROVED }}" + runs-on: ec2-runners + container: + image: public.ecr.aws/m3u4c4h9/island-is/actions-runner-public:latest + timeout-minutes: 10 + steps: + - run: echo "selected runner = ${{ runner.name }}" + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - uses: actions/setup-node@v4 + with: + node-version-file: 'package.json' + + - name: Setup yarn + run: corepack enable + + - name: Get cache + id: get-cache + uses: ./.github/actions/get-cache + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + enable-cache: 'node_modules,generated-files' + + - name: Check unicorn affected + id: unicorn-affected + env: + BaseRef: ${{ github.base_ref }} #The target branch e.g. main (feature/unicorn-app) + HeadRef: ${{ github.head_ref }} #The branch being merged e.g. (unicorn-pipe-rel3) + run: | + echo "Comparing nx affected for $HeadRef using origin/$BaseRef as base branch" + echo IS_UNICORN=$(node scripts/ci/unicorn-utils.mjs "{\"baseBranch\": \"origin/$BaseRef\", \"mergeBranch\": \"$HeadRef\" }") >> "$GITHUB_OUTPUT" + - name: Results + run: | + echo "Unicorn = ${{ steps.unicorn-affected.outputs.IS_UNICORN }}" + + - name: Find Latest Release Branch + run: | + node scripts/ci/get-last-release.mjs $(git branch -r) + + - run: "echo 'latest release: ${{ steps.get_latest_release.outputs.data }}'" diff --git a/.gitignore b/.gitignore index 46c71ffd1796..a31e5dd70833 100644 --- a/.gitignore +++ b/.gitignore @@ -31,6 +31,7 @@ TODO .env .env.* .envrc.private +nx-cloud.env .nvmrc .node-version # IDE - VSCode diff --git a/apps/unicorn-app/.eslintrc.json b/apps/unicorn-app/.eslintrc.json new file mode 100644 index 000000000000..b0b7131a6c3a --- /dev/null +++ b/apps/unicorn-app/.eslintrc.json @@ -0,0 +1,55 @@ +{ + "extends": ["plugin:@nx/react", "../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "rules": { + "@nx/enforce-module-boundaries": [ + "error", + { + "enforceBuildableLibDependency": true, + "allowCircularSelfDependency": true, + "allow": ["../../../infra/src/dsl"], + "depConstraints": [ + { + "sourceTag": "*", + "onlyDependOnLibsWithTags": ["*"] + } + ] + } + ], + "simple-import-sort/imports": [ + "warn", + { + "groups": [ + // React related packages come first, followed by all external imports. + ["^react", "^\\w", "^@(?!island).+"], + // Then island.is packages. + ["^(@island.is).*"], + // Then all other imports in this order: "../", "./", "./css" + [ + "^\\.\\.(?!/?$)", + "^\\.\\./?$", + "^\\./(?=.*/)(?!/?$)", + "^\\.(?!/?$)", + "^\\./?$", + "^.+\\.?(css)$" + ] + ] + } + ] + }, + "plugins": ["simple-import-sort"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/apps/unicorn-app/codegen.yml b/apps/unicorn-app/codegen.yml new file mode 100644 index 000000000000..bcc1fe6a884d --- /dev/null +++ b/apps/unicorn-app/codegen.yml @@ -0,0 +1,24 @@ +schema: + - apps/api/src/api.graphql +documents: apps/unicorn-app/src/queries/*.{ts,tsx} +generates: + apps/web/graphql/schema.ts: + plugins: + - typescript + - typescript-operations + config: + exportFragmentSpreadSubTypes: true + scalars: + DateTime: Date + JSON: '{ [key: string]: any }' + namingConvention: + typeNames: change-case#pascalCase + apps/web/graphql/fragmentTypes.json: + plugins: + - fragment-matcher + config: + module: commonjs + apolloClientVersion: 3 +hooks: + afterAllFileWrite: + - prettier --write diff --git a/apps/unicorn-app/index.d.ts b/apps/unicorn-app/index.d.ts new file mode 100644 index 000000000000..a9ab53489c7f --- /dev/null +++ b/apps/unicorn-app/index.d.ts @@ -0,0 +1,6 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +declare module '*.svg' { + const content: any + export const ReactComponent: any + export default content +} diff --git a/apps/unicorn-app/infra/infra.ts b/apps/unicorn-app/infra/infra.ts new file mode 100644 index 000000000000..47fdfe93c611 --- /dev/null +++ b/apps/unicorn-app/infra/infra.ts @@ -0,0 +1,34 @@ +import { service, ServiceBuilder } from '../../../infra/src/dsl/dsl' +const serviceName = 'unicorn-app' +export const serviceSetup = (): ServiceBuilder => + service(serviceName) + .image(serviceName) + .namespace(serviceName) + .serviceAccount(serviceName) + .ingress({ + primary: { + host: { + dev: serviceName, + staging: serviceName, + prod: serviceName, + }, + paths: ['/'], + }, + }) + .replicaCount({ + default: 1, + min: 1, + max: 10, + }) + .resources({ + limits: { + cpu: '200m', + memory: '256Mi', + }, + requests: { + cpu: '50m', + memory: '128Mi', + }, + }) + .liveness('/liveness') + .readiness('/readiness') diff --git a/apps/unicorn-app/jest.config.ts b/apps/unicorn-app/jest.config.ts new file mode 100644 index 000000000000..5705a6bac0c1 --- /dev/null +++ b/apps/unicorn-app/jest.config.ts @@ -0,0 +1,11 @@ +/* eslint-disable */ +export default { + displayName: 'unicorn-app', + preset: '../../jest.preset.js', + transform: { + '^(?!.*\\.(js|jsx|ts|tsx|css|json)$)': '@nx/react/plugins/jest', + '^.+\\.[tj]sx?$': ['babel-jest', { presets: ['@nx/next/babel'] }], + }, + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], + coverageDirectory: '../../coverage/apps/unicorn-app', +} diff --git a/apps/unicorn-app/next-env.d.ts b/apps/unicorn-app/next-env.d.ts new file mode 100644 index 000000000000..4f11a03dc6cc --- /dev/null +++ b/apps/unicorn-app/next-env.d.ts @@ -0,0 +1,5 @@ +/// +/// + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/basic-features/typescript for more information. diff --git a/apps/unicorn-app/next.config.js b/apps/unicorn-app/next.config.js new file mode 100644 index 000000000000..5dd157afdc66 --- /dev/null +++ b/apps/unicorn-app/next.config.js @@ -0,0 +1,22 @@ +//@ts-check + +// eslint-disable-next-line @typescript-eslint/no-var-requires +const { composePlugins, withNx } = require('@nx/next') + +/** + * @type {import('@nx/next/plugins/with-nx').WithNxOptions} + **/ +const nextConfig = { + nx: { + // Set this to true if you would like to use SVGR + // See: https://github.com/gregberge/svgr + svgr: false, + }, +} + +const plugins = [ + // Add more Next.js plugins to this list if needed. + withNx, +] + +module.exports = composePlugins(...plugins)(nextConfig) diff --git a/apps/unicorn-app/project.json b/apps/unicorn-app/project.json new file mode 100644 index 000000000000..c7b92fdd580a --- /dev/null +++ b/apps/unicorn-app/project.json @@ -0,0 +1,101 @@ +{ + "name": "unicorn-app", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "apps/unicorn-app", + "projectType": "application", + "tags": ["scope:react-next"], + "targets": { + "build": { + "executor": "@nx/next:build", + "outputs": ["{options.outputPath}"], + "defaultConfiguration": "production", + "options": { + "outputPath": "dist/apps/unicorn-app" + }, + "configurations": { + "development": { + "outputPath": "apps/unicorn-app" + }, + "production": {} + }, + "dependsOn": ["build-custom-server"] + }, + "build-custom-server": { + "executor": "@nx/webpack:webpack", + "defaultConfiguration": "production", + "options": { + "outputPath": "dist/apps/unicorn-app", + "main": "apps/unicorn-app/server.ts", + "tsConfig": "apps/unicorn-app/tsconfig.server.json", + "maxWorkers": 2, + "assets": [], + "compiler": "tsc", + "target": "node", + "webpackConfig": "apps/unicorn-app/webpack.config.js" + }, + "configurations": { + "development": {}, + "production": { + "optimization": true, + "extractLicenses": true, + "inspect": false + } + } + }, + "serve": { + "executor": "@nx/next:server", + "defaultConfiguration": "development", + "options": { + "buildTarget": "unicorn-app:build", + "dev": true, + "customServerTarget": "unicorn-app:serve-custom-server" + }, + "configurations": { + "development": { + "buildTarget": "unicorn-app:build:development", + "dev": true, + "customServerTarget": "unicorn-app:serve-custom-server:development" + }, + "production": { + "buildTarget": "unicorn-app:build:production", + "dev": false, + "customServerTarget": "unicorn-app:serve-custom-server:production" + } + } + }, + "serve-custom-server": { + "executor": "@nx/js:node", + "defaultConfiguration": "development", + "options": { + "buildTarget": "unicorn-app:build-custom-server" + }, + "configurations": { + "development": { + "buildTarget": "unicorn-app:build-custom-server:development" + }, + "production": { + "buildTarget": "unicorn-app:build-custom-server:production" + } + } + }, + "export": { + "executor": "@nx/next:export", + "options": { + "buildTarget": "unicorn-app:build:production" + } + }, + "test": { + "executor": "@nx/jest:jest", + "outputs": ["{workspaceRoot}/coverage/apps/unicorn-app"], + "options": { + "jestConfig": "apps/unicorn-app/jest.config.ts" + } + }, + "lint": { + "executor": "@nx/eslint:lint" + }, + "docker-next": { + "executor": "Intentionally left blank, only so this target is valid when using `nx show projects --with-target docker-next`" + } + } +} diff --git a/apps/unicorn-app/public/.gitkeep b/apps/unicorn-app/public/.gitkeep new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/apps/unicorn-app/public/favicon.ico b/apps/unicorn-app/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..317ebcb2336e0833a22dddf0ab287849f26fda57 GIT binary patch literal 15086 zcmeI332;U^%p|z7g|#(P)qFEA@4f!_@qOK2 z_lJl}!lhL!VT_U|uN7%8B2iKH??xhDa;*`g{yjTFWHvXn;2s{4R7kH|pKGdy(7z!K zgftM+Ku7~24TLlh(!g)gz|foI94G^t2^IO$uvX$3(OR0<_5L2sB)lMAMy|+`xodJ{ z_Uh_1m)~h?a;2W{dmhM;u!YGo=)OdmId_B<%^V^{ovI@y`7^g1_V9G}*f# zNzAtvou}I!W1#{M^@ROc(BZ! z+F!!_aR&Px3_reO(EW+TwlW~tv*2zr?iP7(d~a~yA|@*a89IUke+c472NXM0wiX{- zl`UrZC^1XYyf%1u)-Y)jj9;MZ!SLfd2Hl?o|80Su%Z?To_=^g_Jt0oa#CT*tjx>BI z16wec&AOWNK<#i0Qd=1O$fymLRoUR*%;h@*@v7}wApDl^w*h}!sYq%kw+DKDY)@&A z@9$ULEB3qkR#85`lb8#WZw=@})#kQig9oqy^I$dj&k4jU&^2(M3q{n1AKeGUKPFbr z1^<)aH;VsG@J|B&l>UtU#Ejv3GIqERzYgL@UOAWtW<{p#zy`WyJgpCy8$c_e%wYJL zyGHRRx38)HyjU3y{-4z6)pzb>&Q1pR)B&u01F-|&Gx4EZWK$nkUkOI|(D4UHOXg_- zw{OBf!oWQUn)Pe(=f=nt=zkmdjpO^o8ZZ9o_|4tW1ni+Un9iCW47*-ut$KQOww!;u z`0q)$s6IZO!~9$e_P9X!hqLxu`fpcL|2f^I5d4*a@Dq28;@2271v_N+5HqYZ>x;&O z05*7JT)mUe&%S0@UD)@&8SmQrMtsDfZT;fkdA!r(S=}Oz>iP)w=W508=Rc#nNn7ym z1;42c|8($ALY8#a({%1#IXbWn9-Y|0eDY$_L&j{63?{?AH{);EzcqfydD$@-B`Y3<%IIj7S7rK_N}je^=dEk%JQ4c z!tBdTPE3Tse;oYF>cnrapWq*o)m47X1`~6@(!Y29#>-#8zm&LXrXa(3=7Z)ElaQqj z-#0JJy3Fi(C#Rx(`=VXtJ63E2_bZGCz+QRa{W0e2(m3sI?LOcUBx)~^YCqZ{XEPX)C>G>U4tfqeH8L(3|pQR*zbL1 zT9e~4Tb5p9_G}$y4t`i*4t_Mr9QYvL9C&Ah*}t`q*}S+VYh0M6GxTTSXI)hMpMpIq zD1ImYqJLzbj0}~EpE-aH#VCH_udYEW#`P2zYmi&xSPs_{n6tBj=MY|-XrA;SGA_>y zGtU$?HXm$gYj*!N)_nQ59%lQdXtQZS3*#PC-{iB_sm+ytD*7j`D*k(P&IH2GHT}Eh z5697eQECVIGQAUe#eU2I!yI&%0CP#>%6MWV z@zS!p@+Y1i1b^QuuEF*13CuB zu69dve5k7&Wgb+^s|UB08Dr3u`h@yM0NTj4h7MnHo-4@xmyr7(*4$rpPwsCDZ@2be zRz9V^GnV;;?^Lk%ynzq&K(Aix`mWmW`^152Hoy$CTYVehpD-S1-W^#k#{0^L`V6CN+E z!w+xte;2vu4AmVNEFUOBmrBL>6MK@!O2*N|2=d|Y;oN&A&qv=qKn73lDD zI(+oJAdgv>Yr}8(&@ZuAZE%XUXmX(U!N+Z_sjL<1vjy1R+1IeHt`79fnYdOL{$ci7 z%3f0A*;Zt@ED&Gjm|OFTYBDe%bbo*xXAQsFz+Q`fVBH!N2)kaxN8P$c>sp~QXnv>b zwq=W3&Mtmih7xkR$YA)1Yi?avHNR6C99!u6fh=cL|KQ&PwF!n@ud^n(HNIImHD!h87!i*t?G|p0o+eelJ?B@A64_9%SBhNaJ64EvKgD&%LjLCYnNfc; znj?%*p@*?dq#NqcQFmmX($wms@CSAr9#>hUR^=I+=0B)vvGX%T&#h$kmX*s=^M2E!@N9#m?LhMvz}YB+kd zG~mbP|D(;{s_#;hsKK9lbVK&Lo734x7SIFJ9V_}2$@q?zm^7?*XH94w5Qae{7zOMUF z^?%F%)c1Y)Q?Iy?I>knw*8gYW#ok|2gdS=YYZLiD=CW|Nj;n^x!=S#iJ#`~Ld79+xXpVmUK^B(xO_vO!btA9y7w3L3-0j-y4 z?M-V{%z;JI`bk7yFDcP}OcCd*{Q9S5$iGA7*E1@tfkyjAi!;wP^O71cZ^Ep)qrQ)N z#wqw0_HS;T7x3y|`P==i3hEwK%|>fZ)c&@kgKO1~5<5xBSk?iZV?KI6&i72H6S9A* z=U(*e)EqEs?Oc04)V-~K5AUmh|62H4*`UAtItO$O(q5?6jj+K^oD!04r=6#dsxp?~}{`?&sXn#q2 zGuY~7>O2=!u@@Kfu7q=W*4egu@qPMRM>(eyYyaIE<|j%d=iWNdGsx%c!902v#ngNg z@#U-O_4xN$s_9?(`{>{>7~-6FgWpBpqXb`Ydc3OFL#&I}Irse9F_8R@4zSS*Y*o*B zXL?6*Aw!AfkNCgcr#*yj&p3ZDe2y>v$>FUdKIy_2N~}6AbHc7gA3`6$g@1o|dE>vz z4pl(j9;kyMsjaw}lO?(?Xg%4k!5%^t#@5n=WVc&JRa+XT$~#@rldvN3S1rEpU$;XgxVny7mki3 z-Hh|jUCHrUXuLr!)`w>wgO0N%KTB-1di>cj(x3Bav`7v z3G7EIbU$z>`Nad7Rk_&OT-W{;qg)-GXV-aJT#(ozdmnA~Rq3GQ_3mby(>q6Ocb-RgTUhTN)))x>m&eD;$J5Bg zo&DhY36Yg=J=$Z>t}RJ>o|@hAcwWzN#r(WJ52^g$lh^!63@hh+dR$&_dEGu&^CR*< z!oFqSqO@>xZ*nC2oiOd0eS*F^IL~W-rsrO`J`ej{=ou_q^_(<$&-3f^J z&L^MSYWIe{&pYq&9eGaArA~*kA { + it('should render successfully', () => { + const { baseElement } = render() + expect(baseElement).toBeTruthy() + }) +}) diff --git a/apps/unicorn-app/src/app/api/hello/route.ts b/apps/unicorn-app/src/app/api/hello/route.ts new file mode 100644 index 000000000000..4a0d8ceed763 --- /dev/null +++ b/apps/unicorn-app/src/app/api/hello/route.ts @@ -0,0 +1,3 @@ +export async function GET(request: Request) { + return new Response('Hello, hello, from API!') +} diff --git a/apps/unicorn-app/src/app/global.css b/apps/unicorn-app/src/app/global.css new file mode 100644 index 000000000000..2a8880df280a --- /dev/null +++ b/apps/unicorn-app/src/app/global.css @@ -0,0 +1,409 @@ +html { + -webkit-text-size-adjust: 100%; + font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, + Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif, + Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji; + line-height: 1.5; + tab-size: 4; + scroll-behavior: smooth; +} +body { + font-family: inherit; + line-height: inherit; + margin: 0; +} +h1, +h2, +p, +pre { + margin: 0; +} +*, +::before, +::after { + box-sizing: border-box; + border-width: 0; + border-style: solid; + border-color: currentColor; +} +h1, +h2 { + font-size: inherit; + font-weight: inherit; +} +a { + color: inherit; + text-decoration: inherit; +} +pre { + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, + Liberation Mono, Courier New, monospace; +} +svg { + display: block; + vertical-align: middle; + shape-rendering: auto; + text-rendering: optimizeLegibility; +} +pre { + background-color: rgba(55, 65, 81, 1); + border-radius: 0.25rem; + color: rgba(229, 231, 235, 1); + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, + Liberation Mono, Courier New, monospace; + overflow: scroll; + padding: 0.5rem 0.75rem; +} + +.shadow { + box-shadow: 0 0 #0000, 0 0 #0000, 0 10px 15px -3px rgba(0, 0, 0, 0.1), + 0 4px 6px -2px rgba(0, 0, 0, 0.05); +} +.rounded { + border-radius: 1.5rem; +} +.wrapper { + width: 100%; +} +.container { + margin-left: auto; + margin-right: auto; + max-width: 768px; + padding-bottom: 3rem; + padding-left: 1rem; + padding-right: 1rem; + color: rgba(55, 65, 81, 1); + width: 100%; +} +#welcome { + margin-top: 2.5rem; +} +#welcome h1 { + font-size: 3rem; + font-weight: 500; + letter-spacing: -0.025em; + line-height: 1; +} +#welcome span { + display: block; + font-size: 3.875rem; + font-weight: 300; + line-height: 2.25rem; + margin-bottom: 0.5rem; +} +#hero { + align-items: center; + background-color: hsla(214, 62%, 21%, 1); + border: none; + box-sizing: border-box; + color: rgba(55, 65, 81, 1); + display: grid; + grid-template-columns: 1fr; + margin-top: 3.5rem; +} +#hero .text-container { + color: rgba(255, 255, 255, 1); + padding: 3rem 2rem; +} +#hero .text-container h2 { + font-size: 1.5rem; + line-height: 2rem; + position: relative; +} +#hero .text-container h2 svg { + color: hsla(162, 47%, 50%, 1); + height: 2rem; + left: -0.25rem; + position: absolute; + top: 0; + width: 2rem; +} +#hero .text-container h2 span { + margin-left: 2.5rem; +} +#hero .text-container a { + background-color: rgba(255, 255, 255, 1); + border-radius: 0.75rem; + color: rgba(55, 65, 81, 1); + display: inline-block; + margin-top: 1.5rem; + padding: 1rem 2rem; + text-decoration: inherit; +} +#hero .logo-container { + display: none; + justify-content: center; + padding-left: 2rem; + padding-right: 2rem; +} +#hero .logo-container svg { + color: rgba(255, 255, 255, 1); + width: 66.666667%; +} +#middle-content { + align-items: flex-start; + display: grid; + gap: 4rem; + grid-template-columns: 1fr; + margin-top: 3.5rem; +} +#learning-materials { + padding: 2.5rem 2rem; +} +#learning-materials h2 { + font-weight: 500; + font-size: 1.25rem; + letter-spacing: -0.025em; + line-height: 1.75rem; + padding-left: 1rem; + padding-right: 1rem; +} +.list-item-link { + align-items: center; + border-radius: 0.75rem; + display: flex; + margin-top: 1rem; + padding: 1rem; + transition-property: background-color, border-color, color, fill, stroke, + opacity, box-shadow, transform, filter, backdrop-filter, + -webkit-backdrop-filter; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; + width: 100%; +} +.list-item-link svg:first-child { + margin-right: 1rem; + height: 1.5rem; + transition-property: background-color, border-color, color, fill, stroke, + opacity, box-shadow, transform, filter, backdrop-filter, + -webkit-backdrop-filter; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; + width: 1.5rem; +} +.list-item-link > span { + flex-grow: 1; + font-weight: 400; + transition-property: background-color, border-color, color, fill, stroke, + opacity, box-shadow, transform, filter, backdrop-filter, + -webkit-backdrop-filter; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; +} +.list-item-link > span > span { + color: rgba(107, 114, 128, 1); + display: block; + flex-grow: 1; + font-size: 0.75rem; + font-weight: 300; + line-height: 1rem; + transition-property: background-color, border-color, color, fill, stroke, + opacity, box-shadow, transform, filter, backdrop-filter, + -webkit-backdrop-filter; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; +} +.list-item-link svg:last-child { + height: 1rem; + transition-property: all; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; + width: 1rem; +} +.list-item-link:hover { + color: rgba(255, 255, 255, 1); + background-color: hsla(162, 47%, 50%, 1); +} +.list-item-link:hover > span { +} +.list-item-link:hover > span > span { + color: rgba(243, 244, 246, 1); +} +.list-item-link:hover svg:last-child { + transform: translateX(0.25rem); +} +#other-links { +} +.button-pill { + padding: 1.5rem 2rem; + transition-duration: 300ms; + transition-property: background-color, border-color, color, fill, stroke, + opacity, box-shadow, transform, filter, backdrop-filter, + -webkit-backdrop-filter; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + align-items: center; + display: flex; +} +.button-pill svg { + transition-property: background-color, border-color, color, fill, stroke, + opacity, box-shadow, transform, filter, backdrop-filter, + -webkit-backdrop-filter; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; + flex-shrink: 0; + width: 3rem; +} +.button-pill > span { + letter-spacing: -0.025em; + font-weight: 400; + font-size: 1.125rem; + line-height: 1.75rem; + padding-left: 1rem; + padding-right: 1rem; +} +.button-pill span span { + display: block; + font-size: 0.875rem; + font-weight: 300; + line-height: 1.25rem; +} +.button-pill:hover svg, +.button-pill:hover { + color: rgba(255, 255, 255, 1) !important; +} +#nx-console:hover { + background-color: rgba(0, 122, 204, 1); +} +#nx-console svg { + color: rgba(0, 122, 204, 1); +} +#nx-console-jetbrains { + margin-top: 2rem; +} +#nx-console-jetbrains:hover { + background-color: rgba(255, 49, 140, 1); +} +#nx-console-jetbrains svg { + color: rgba(255, 49, 140, 1); +} +#nx-repo:hover { + background-color: rgba(24, 23, 23, 1); +} +#nx-repo svg { + color: rgba(24, 23, 23, 1); +} +#nx-cloud { + margin-bottom: 2rem; + margin-top: 2rem; + padding: 2.5rem 2rem; +} +#nx-cloud > div { + align-items: center; + display: flex; +} +#nx-cloud > div svg { + border-radius: 0.375rem; + flex-shrink: 0; + width: 3rem; +} +#nx-cloud > div h2 { + font-size: 1.125rem; + font-weight: 400; + letter-spacing: -0.025em; + line-height: 1.75rem; + padding-left: 1rem; + padding-right: 1rem; +} +#nx-cloud > div h2 span { + display: block; + font-size: 0.875rem; + font-weight: 300; + line-height: 1.25rem; +} +#nx-cloud p { + font-size: 1rem; + line-height: 1.5rem; + margin-top: 1rem; +} +#nx-cloud pre { + margin-top: 1rem; +} +#nx-cloud a { + color: rgba(107, 114, 128, 1); + display: block; + font-size: 0.875rem; + line-height: 1.25rem; + margin-top: 1.5rem; + text-align: right; +} +#nx-cloud a:hover { + text-decoration: underline; +} +#commands { + padding: 2.5rem 2rem; + margin-top: 3.5rem; +} +#commands h2 { + font-size: 1.25rem; + font-weight: 400; + letter-spacing: -0.025em; + line-height: 1.75rem; + padding-left: 1rem; + padding-right: 1rem; +} +#commands p { + font-size: 1rem; + font-weight: 300; + line-height: 1.5rem; + margin-top: 1rem; + padding-left: 1rem; + padding-right: 1rem; +} +details { + align-items: center; + display: flex; + margin-top: 1rem; + padding-left: 1rem; + padding-right: 1rem; + width: 100%; +} +details pre > span { + color: rgba(181, 181, 181, 1); + display: block; +} +summary { + border-radius: 0.5rem; + display: flex; + font-weight: 400; + padding: 0.5rem; + cursor: pointer; + transition-property: background-color, border-color, color, fill, stroke, + opacity, box-shadow, transform, filter, backdrop-filter, + -webkit-backdrop-filter; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; +} +summary:hover { + background-color: rgba(243, 244, 246, 1); +} +summary svg { + height: 1.5rem; + margin-right: 1rem; + width: 1.5rem; +} +#love { + color: rgba(107, 114, 128, 1); + font-size: 0.875rem; + line-height: 1.25rem; + margin-top: 3.5rem; + opacity: 0.6; + text-align: center; +} +#love svg { + color: rgba(252, 165, 165, 1); + width: 1.25rem; + height: 1.25rem; + display: inline; + margin-top: -0.25rem; +} +@media screen and (min-width: 768px) { + #hero { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + #hero .logo-container { + display: flex; + } + #middle-content { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } +} diff --git a/apps/unicorn-app/src/app/layout.tsx b/apps/unicorn-app/src/app/layout.tsx new file mode 100644 index 000000000000..43b373a18bf6 --- /dev/null +++ b/apps/unicorn-app/src/app/layout.tsx @@ -0,0 +1,18 @@ +import './global.css' + +export const metadata = { + title: 'Welcome to Unicorn app', + description: 'Generated by create-nx-workspace', +} + +export default function RootLayout({ + children, +}: { + children: React.ReactNode +}) { + return ( + + {children} + + ) +} diff --git a/apps/unicorn-app/src/app/page.module.css b/apps/unicorn-app/src/app/page.module.css new file mode 100644 index 000000000000..8a13e21cb311 --- /dev/null +++ b/apps/unicorn-app/src/app/page.module.css @@ -0,0 +1,2 @@ +.page { +} diff --git a/apps/unicorn-app/src/app/page.tsx b/apps/unicorn-app/src/app/page.tsx new file mode 100644 index 000000000000..5d9289cb0058 --- /dev/null +++ b/apps/unicorn-app/src/app/page.tsx @@ -0,0 +1,17 @@ +export default function Index() { + return ( +
+
+
+
+

+ + Welcome unicorn 🦄 + +

+
+
+
+
+ ) +} diff --git a/apps/unicorn-app/tsconfig.json b/apps/unicorn-app/tsconfig.json new file mode 100644 index 000000000000..37e77ed8c826 --- /dev/null +++ b/apps/unicorn-app/tsconfig.json @@ -0,0 +1,30 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "jsx": "preserve", + "allowJs": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "types": ["node", "jest"], + "strict": false, + "forceConsistentCasingInFileNames": true, + "noEmit": true, + "resolveJsonModule": true, + "isolatedModules": true, + "incremental": true, + "plugins": [ + { + "name": "next" + } + ] + }, + "include": [ + "**/*.ts", + "**/*.tsx", + "**/*.js", + "**/*.jsx", + "next-env.d.ts", + ".next/types/**/*.ts" + ], + "exclude": ["node_modules", "jest.config.ts"] +} diff --git a/apps/unicorn-app/tsconfig.server.json b/apps/unicorn-app/tsconfig.server.json new file mode 100644 index 000000000000..8fafdb4be36d --- /dev/null +++ b/apps/unicorn-app/tsconfig.server.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "noEmit": false, + "incremental": true, + "tsBuildInfoFile": "../../tmp/buildcache/apps/unicorn-app/server", + "types": ["node"] + }, + "include": ["server.ts"] +} diff --git a/apps/unicorn-app/tsconfig.spec.json b/apps/unicorn-app/tsconfig.spec.json new file mode 100644 index 000000000000..214b2cc208c1 --- /dev/null +++ b/apps/unicorn-app/tsconfig.spec.json @@ -0,0 +1,21 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "module": "commonjs", + "types": ["jest", "node"], + "jsx": "react" + }, + "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/unicorn-app/webpack.config.js b/apps/unicorn-app/webpack.config.js new file mode 100644 index 000000000000..95e68d080f5b --- /dev/null +++ b/apps/unicorn-app/webpack.config.js @@ -0,0 +1,8 @@ +const { composePlugins, withNx } = require('@nx/webpack') + +// Nx plugins for webpack. +module.exports = composePlugins(withNx(), (config) => { + // Note: This was added by an Nx migration. Webpack builds are required to have a corresponding Webpack config file. + // See: https://nx.dev/recipes/webpack/webpack-config-setup + return config +}) diff --git a/charts/islandis/values.dev.yaml b/charts/islandis/values.dev.yaml index e0f5e81d1937..29be8c5e25f0 100644 --- a/charts/islandis/values.dev.yaml +++ b/charts/islandis/values.dev.yaml @@ -1810,6 +1810,7 @@ namespaces: - 'services-sessions' - 'contentful-apps' - 'services-university-gateway' + - 'unicorn-app' - 'portals-my-pages' portals-admin: enabled: true @@ -3154,6 +3155,69 @@ skilavottord-ws: securityContext: allowPrivilegeEscalation: false privileged: false +unicorn-app: + enabled: true + env: + LOG_LEVEL: 'info' + NODE_OPTIONS: '--max-old-space-size=230 -r dd-trace/init' + SERVERSIDE_FEATURES_ON: '' + grantNamespaces: [] + grantNamespacesEnabled: false + healthCheck: + liveness: + initialDelaySeconds: 3 + path: '/liveness' + timeoutSeconds: 3 + readiness: + initialDelaySeconds: 3 + path: '/readiness' + timeoutSeconds: 3 + hpa: + scaling: + metric: + cpuAverageUtilization: 90 + nginxRequestsIrate: 5 + replicas: + max: 10 + min: 1 + image: + repository: '821090935708.dkr.ecr.eu-west-1.amazonaws.com/unicorn-app' + ingress: + primary-alb: + annotations: + kubernetes.io/ingress.class: 'nginx-external-alb' + nginx.ingress.kubernetes.io/service-upstream: 'true' + hosts: + - host: 'unicorn-app.dev01.devland.is' + paths: + - '/' + namespace: 'unicorn-app' + podDisruptionBudget: + maxUnavailable: 1 + podSecurityContext: + fsGroup: 65534 + pvcs: [] + replicaCount: + default: 1 + max: 10 + min: 1 + resources: + limits: + cpu: '200m' + memory: '256Mi' + requests: + cpu: '50m' + memory: '128Mi' + secrets: + CONFIGCAT_SDK_KEY: '/k8s/configcat/CONFIGCAT_SDK_KEY' + securityContext: + allowPrivilegeEscalation: false + privileged: false + serviceAccount: + annotations: + eks.amazonaws.com/role-arn: 'arn:aws:iam::013313053092:role/unicorn-app' + create: true + name: 'unicorn-app' user-notification: args: - '--no-experimental-fetch' diff --git a/charts/islandis/values.prod.yaml b/charts/islandis/values.prod.yaml index d08668a62457..ac8712e81ede 100644 --- a/charts/islandis/values.prod.yaml +++ b/charts/islandis/values.prod.yaml @@ -1674,6 +1674,7 @@ namespaces: - 'services-university-gateway' - 'contentful-apps' - 'contentful-entry-tagger' + - 'unicorn-app' - 'portals-my-pages' portals-admin: enabled: true @@ -3033,6 +3034,69 @@ skilavottord-ws: securityContext: allowPrivilegeEscalation: false privileged: false +unicorn-app: + enabled: true + env: + LOG_LEVEL: 'info' + NODE_OPTIONS: '--max-old-space-size=230 -r dd-trace/init' + SERVERSIDE_FEATURES_ON: 'driving-license-use-v1-endpoint-for-v2-comms' + grantNamespaces: [] + grantNamespacesEnabled: false + healthCheck: + liveness: + initialDelaySeconds: 3 + path: '/liveness' + timeoutSeconds: 3 + readiness: + initialDelaySeconds: 3 + path: '/readiness' + timeoutSeconds: 3 + hpa: + scaling: + metric: + cpuAverageUtilization: 90 + nginxRequestsIrate: 5 + replicas: + max: 10 + min: 1 + image: + repository: '821090935708.dkr.ecr.eu-west-1.amazonaws.com/unicorn-app' + ingress: + primary-alb: + annotations: + kubernetes.io/ingress.class: 'nginx-external-alb' + nginx.ingress.kubernetes.io/service-upstream: 'true' + hosts: + - host: 'unicorn-app.island.is' + paths: + - '/' + namespace: 'unicorn-app' + podDisruptionBudget: + maxUnavailable: 1 + podSecurityContext: + fsGroup: 65534 + pvcs: [] + replicaCount: + default: 1 + max: 10 + min: 1 + resources: + limits: + cpu: '200m' + memory: '256Mi' + requests: + cpu: '50m' + memory: '128Mi' + secrets: + CONFIGCAT_SDK_KEY: '/k8s/configcat/CONFIGCAT_SDK_KEY' + securityContext: + allowPrivilegeEscalation: false + privileged: false + serviceAccount: + annotations: + eks.amazonaws.com/role-arn: 'arn:aws:iam::251502586493:role/unicorn-app' + create: true + name: 'unicorn-app' user-notification: args: - '--no-experimental-fetch' diff --git a/charts/islandis/values.staging.yaml b/charts/islandis/values.staging.yaml index d1c2a12649f8..2a2928d07132 100644 --- a/charts/islandis/values.staging.yaml +++ b/charts/islandis/values.staging.yaml @@ -1548,6 +1548,7 @@ namespaces: - 'license-api' - 'services-sessions' - 'services-university-gateway' + - 'unicorn-app' - 'portals-my-pages' portals-admin: enabled: true @@ -2892,6 +2893,69 @@ skilavottord-ws: securityContext: allowPrivilegeEscalation: false privileged: false +unicorn-app: + enabled: true + env: + LOG_LEVEL: 'info' + NODE_OPTIONS: '--max-old-space-size=230 -r dd-trace/init' + SERVERSIDE_FEATURES_ON: '' + grantNamespaces: [] + grantNamespacesEnabled: false + healthCheck: + liveness: + initialDelaySeconds: 3 + path: '/liveness' + timeoutSeconds: 3 + readiness: + initialDelaySeconds: 3 + path: '/readiness' + timeoutSeconds: 3 + hpa: + scaling: + metric: + cpuAverageUtilization: 90 + nginxRequestsIrate: 5 + replicas: + max: 3 + min: 1 + image: + repository: '821090935708.dkr.ecr.eu-west-1.amazonaws.com/unicorn-app' + ingress: + primary-alb: + annotations: + kubernetes.io/ingress.class: 'nginx-external-alb' + nginx.ingress.kubernetes.io/service-upstream: 'true' + hosts: + - host: 'unicorn-app.staging01.devland.is' + paths: + - '/' + namespace: 'unicorn-app' + podDisruptionBudget: + maxUnavailable: 1 + podSecurityContext: + fsGroup: 65534 + pvcs: [] + replicaCount: + default: 1 + max: 3 + min: 1 + resources: + limits: + cpu: '200m' + memory: '256Mi' + requests: + cpu: '50m' + memory: '128Mi' + secrets: + CONFIGCAT_SDK_KEY: '/k8s/configcat/CONFIGCAT_SDK_KEY' + securityContext: + allowPrivilegeEscalation: false + privileged: false + serviceAccount: + annotations: + eks.amazonaws.com/role-arn: 'arn:aws:iam::261174024191:role/unicorn-app' + create: true + name: 'unicorn-app' user-notification: args: - '--no-experimental-fetch' diff --git a/charts/services/unicorn-app/values.dev.yaml b/charts/services/unicorn-app/values.dev.yaml new file mode 100644 index 000000000000..345b91dd5ddb --- /dev/null +++ b/charts/services/unicorn-app/values.dev.yaml @@ -0,0 +1,81 @@ +##################################################################### +# +# Do not edit this file manually, it is automatically generated. +# Run "yarn charts" instead. +# +##################################################################### + +global: + env: + AUDIT_GROUP_NAME: '/island-is/audit-log' + AWS_REGION: 'eu-west-1' + NPM_CONFIG_UPDATE_NOTIFIER: 'false' + PORT: '3333' + name: 'dev' + initContainer: + env: + AWS_REGION: 'eu-west-1' + NPM_CONFIG_UPDATE_NOTIFIER: 'false' +name: 'unicorn-app' +enabled: true +env: + LOG_LEVEL: 'info' + NODE_OPTIONS: '--max-old-space-size=230 -r dd-trace/init' + SERVERSIDE_FEATURES_ON: '' +grantNamespaces: [] +grantNamespacesEnabled: false +healthCheck: + liveness: + initialDelaySeconds: 3 + path: '/liveness' + timeoutSeconds: 3 + readiness: + initialDelaySeconds: 3 + path: '/readiness' + timeoutSeconds: 3 +hpa: + scaling: + metric: + cpuAverageUtilization: 90 + nginxRequestsIrate: 5 + replicas: + max: 10 + min: 1 +image: + repository: '821090935708.dkr.ecr.eu-west-1.amazonaws.com/unicorn-app' +ingress: + primary-alb: + annotations: + kubernetes.io/ingress.class: 'nginx-external-alb' + nginx.ingress.kubernetes.io/service-upstream: 'true' + hosts: + - host: 'unicorn-app.dev01.devland.is' + paths: + - '/' +namespace: 'unicorn-app' +podDisruptionBudget: + maxUnavailable: 1 +podSecurityContext: + fsGroup: 65534 +pvcs: [] +replicaCount: + default: 1 + max: 10 + min: 1 +resources: + limits: + cpu: '200m' + memory: '256Mi' + requests: + cpu: '50m' + memory: '128Mi' +secrets: + CONFIGCAT_SDK_KEY: '/k8s/configcat/CONFIGCAT_SDK_KEY' +securityContext: + allowPrivilegeEscalation: false + privileged: false +serviceAccount: + annotations: + eks.amazonaws.com/role-arn: 'arn:aws:iam::013313053092:role/unicorn-app' + create: true + name: 'unicorn-app' diff --git a/charts/services/unicorn-app/values.prod.yaml b/charts/services/unicorn-app/values.prod.yaml new file mode 100644 index 000000000000..f9782daf09fc --- /dev/null +++ b/charts/services/unicorn-app/values.prod.yaml @@ -0,0 +1,81 @@ +##################################################################### +# +# Do not edit this file manually, it is automatically generated. +# Run "yarn charts" instead. +# +##################################################################### + +global: + env: + AUDIT_GROUP_NAME: '/island-is/audit-log' + AWS_REGION: 'eu-west-1' + NPM_CONFIG_UPDATE_NOTIFIER: 'false' + PORT: '3333' + name: 'prod' + initContainer: + env: + AWS_REGION: 'eu-west-1' + NPM_CONFIG_UPDATE_NOTIFIER: 'false' +name: 'unicorn-app' +enabled: true +env: + LOG_LEVEL: 'info' + NODE_OPTIONS: '--max-old-space-size=230 -r dd-trace/init' + SERVERSIDE_FEATURES_ON: 'driving-license-use-v1-endpoint-for-v2-comms' +grantNamespaces: [] +grantNamespacesEnabled: false +healthCheck: + liveness: + initialDelaySeconds: 3 + path: '/liveness' + timeoutSeconds: 3 + readiness: + initialDelaySeconds: 3 + path: '/readiness' + timeoutSeconds: 3 +hpa: + scaling: + metric: + cpuAverageUtilization: 90 + nginxRequestsIrate: 5 + replicas: + max: 10 + min: 1 +image: + repository: '821090935708.dkr.ecr.eu-west-1.amazonaws.com/unicorn-app' +ingress: + primary-alb: + annotations: + kubernetes.io/ingress.class: 'nginx-external-alb' + nginx.ingress.kubernetes.io/service-upstream: 'true' + hosts: + - host: 'unicorn-app.island.is' + paths: + - '/' +namespace: 'unicorn-app' +podDisruptionBudget: + maxUnavailable: 1 +podSecurityContext: + fsGroup: 65534 +pvcs: [] +replicaCount: + default: 1 + max: 10 + min: 1 +resources: + limits: + cpu: '200m' + memory: '256Mi' + requests: + cpu: '50m' + memory: '128Mi' +secrets: + CONFIGCAT_SDK_KEY: '/k8s/configcat/CONFIGCAT_SDK_KEY' +securityContext: + allowPrivilegeEscalation: false + privileged: false +serviceAccount: + annotations: + eks.amazonaws.com/role-arn: 'arn:aws:iam::251502586493:role/unicorn-app' + create: true + name: 'unicorn-app' diff --git a/charts/services/unicorn-app/values.staging.yaml b/charts/services/unicorn-app/values.staging.yaml new file mode 100644 index 000000000000..50960c303bef --- /dev/null +++ b/charts/services/unicorn-app/values.staging.yaml @@ -0,0 +1,81 @@ +##################################################################### +# +# Do not edit this file manually, it is automatically generated. +# Run "yarn charts" instead. +# +##################################################################### + +global: + env: + AUDIT_GROUP_NAME: '/island-is/audit-log' + AWS_REGION: 'eu-west-1' + NPM_CONFIG_UPDATE_NOTIFIER: 'false' + PORT: '3333' + name: 'staging' + initContainer: + env: + AWS_REGION: 'eu-west-1' + NPM_CONFIG_UPDATE_NOTIFIER: 'false' +name: 'unicorn-app' +enabled: true +env: + LOG_LEVEL: 'info' + NODE_OPTIONS: '--max-old-space-size=230 -r dd-trace/init' + SERVERSIDE_FEATURES_ON: '' +grantNamespaces: [] +grantNamespacesEnabled: false +healthCheck: + liveness: + initialDelaySeconds: 3 + path: '/liveness' + timeoutSeconds: 3 + readiness: + initialDelaySeconds: 3 + path: '/readiness' + timeoutSeconds: 3 +hpa: + scaling: + metric: + cpuAverageUtilization: 90 + nginxRequestsIrate: 5 + replicas: + max: 3 + min: 1 +image: + repository: '821090935708.dkr.ecr.eu-west-1.amazonaws.com/unicorn-app' +ingress: + primary-alb: + annotations: + kubernetes.io/ingress.class: 'nginx-external-alb' + nginx.ingress.kubernetes.io/service-upstream: 'true' + hosts: + - host: 'unicorn-app.staging01.devland.is' + paths: + - '/' +namespace: 'unicorn-app' +podDisruptionBudget: + maxUnavailable: 1 +podSecurityContext: + fsGroup: 65534 +pvcs: [] +replicaCount: + default: 1 + max: 3 + min: 1 +resources: + limits: + cpu: '200m' + memory: '256Mi' + requests: + cpu: '50m' + memory: '128Mi' +secrets: + CONFIGCAT_SDK_KEY: '/k8s/configcat/CONFIGCAT_SDK_KEY' +securityContext: + allowPrivilegeEscalation: false + privileged: false +serviceAccount: + annotations: + eks.amazonaws.com/role-arn: 'arn:aws:iam::261174024191:role/unicorn-app' + create: true + name: 'unicorn-app' diff --git a/infra/src/uber-charts/islandis.ts b/infra/src/uber-charts/islandis.ts index 569b921bc2bf..db2d1ecc6940 100644 --- a/infra/src/uber-charts/islandis.ts +++ b/infra/src/uber-charts/islandis.ts @@ -62,6 +62,7 @@ import { } from '../../../apps/services/sessions/infra/sessions' import { serviceSetup as authAdminApiSetup } from '../../../apps/services/auth/admin-api/infra/auth-admin-api' +import { serviceSetup as unicornAppSetup } from '../../../apps/unicorn-app/infra/infra' import { EnvironmentServices } from '.././dsl/types/charts' import { ServiceBuilder } from '../dsl/dsl' @@ -146,6 +147,8 @@ const userNotificationWorkerService = userNotificationWorkerSetup({ const userNotificationCleanupWorkerService = userNotificationCleanUpWorkerSetup() +const unicornApp = unicornAppSetup() + const githubActionsCache = githubActionsCacheSetup() const externalContractsTests = externalContractsTestsSetup() @@ -186,6 +189,7 @@ export const Services: EnvironmentServices = { contentfulApps, contentfulEntryTagger, bffAdminPortalService, + unicornApp, bffServicePortalService, ], staging: [ @@ -221,6 +225,7 @@ export const Services: EnvironmentServices = { universityGatewayService, universityGatewayWorker, bffAdminPortalService, + unicornApp, bffServicePortalService, ], dev: [ @@ -260,6 +265,7 @@ export const Services: EnvironmentServices = { universityGatewayService, universityGatewayWorker, bffAdminPortalService, + unicornApp, bffServicePortalService, ], } diff --git a/scripts/ci/create-release.mjs b/scripts/ci/create-release.mjs new file mode 100644 index 000000000000..85d818b14889 --- /dev/null +++ b/scripts/ci/create-release.mjs @@ -0,0 +1,35 @@ +import { Octokit } from '@octokit/rest' + +const { GITHUB_TOKEN, GITHUB_REPOSITORY, GITHUB_REF } = process.env +const octokit = new Octokit({ auth: GITHUB_TOKEN }) +const [owner, repo] = GITHUB_REPOSITORY?.split('/') || [] + +const arg = process.argv.slice(2) + +const { data: pullRequest } = await octokit.rest.pulls.get({ + owner: owner, + repo: repo, + pull_number: arg[0], +}) + +const SHA = pullRequest.head.sha +// This is a temporary commit that is created behind the scenes for +// the test merge that validated no conflicts exist with the base branch. +// It is not committed to the repository. +// After the PR is merged, this value instead represents the SHA of the merge commit +octokit.rest.repos + .createRelease({ + owner: owner, + repo: repo, + target_commitish: SHA, + tag_name: 'SomeTag', + name: 'Test 123', + generate_release_notes: true, + }) + .then(({ data }) => { + console.log(data) + }) + .catch((error) => { + console.log(error) + process.exit(1) + }) diff --git a/scripts/ci/get-last-release.mjs b/scripts/ci/get-last-release.mjs new file mode 100644 index 000000000000..03feaf63f4d0 --- /dev/null +++ b/scripts/ci/get-last-release.mjs @@ -0,0 +1,17 @@ +const releases = process.argv + .slice(2) + .map((release) => release.replace('origin/release/', '')) + .filter((release) => /^\d+\.\d+\.\d+$/.test(release)) + .sort((a, b) => { + const [aMajor, aMinor, aPatch] = a.split('.').map(Number) + const [bMajor, bMinor, bPatch] = b.split('.').map(Number) + + if (aMajor !== bMajor) { + return bMajor - aMajor + } else if (aMinor !== bMinor) { + return bMinor - aMinor + } else { + return bPatch - aPatch + } + }) +console.log(releases[0]) diff --git a/scripts/ci/unicorn-utils.mjs b/scripts/ci/unicorn-utils.mjs new file mode 100644 index 000000000000..22078efe215d --- /dev/null +++ b/scripts/ci/unicorn-utils.mjs @@ -0,0 +1,17 @@ +import { execSync } from 'child_process' +import { workspaceRoot } from '@nx/devkit' + +const unicornApps = ['unicorn-app'] + +const arg = JSON.parse(process.argv.slice(2)) +try { + const affected = JSON.parse( + execSync( + `cd ${workspaceRoot} && yarn nx show projects --affected --base ${arg.baseBranch} --json | jq -r`, + ).toString(), + ) + console.log(affected.some((item) => unicornApps.includes(item))) +} catch (e) { + console.error(e.message) + process.exit(1) +} diff --git a/tsconfig.base.json b/tsconfig.base.json index 7be0f1c28845..32541263a4c8 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -1126,6 +1126,7 @@ "@island.is/testing/e2e": ["libs/testing/e2e/src/index.ts"], "@island.is/testing/fixtures": ["libs/testing/fixtures/src/index.ts"], "@island.is/testing/nest": ["libs/testing/nest/src/index.ts"], + "@island.is/unicorn-app/*": ["apps/unicorn-app/*"], "@island.is/university-gateway": ["libs/university-gateway/src/index.ts"], "@island.is/user-monitoring": ["libs/user-monitoring/src/index.ts"], "@island.is/web/*": ["apps/web/*"], From 27a704f8757b6a60b3524e41358197b8643dcaaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=B3berta=20Andersen?= Date: Fri, 20 Dec 2024 10:35:13 +0000 Subject: [PATCH 17/19] Revert "Uni pr (#17308)" (#17309) This reverts commit 19553a3e988a977fd9c65b04cf591f0b66df755b. --- .github/workflows/config-values.yaml | 5 - .github/workflows/external-checks.yml | 4 +- .github/workflows/merge-queue-ci.yml | 19 - .github/workflows/pullrequest-close.yml | 3 - .github/workflows/pullrequest-lint.yml | 3 - .github/workflows/pullrequest.yml | 5 +- .github/workflows/push.yml | 5 - .github/workflows/revert-pr.yaml | 4 - .github/workflows/unicorns.yml | 106 ----- .gitignore | 1 - apps/unicorn-app/.eslintrc.json | 55 --- apps/unicorn-app/codegen.yml | 24 - apps/unicorn-app/index.d.ts | 6 - apps/unicorn-app/infra/infra.ts | 34 -- apps/unicorn-app/jest.config.ts | 11 - apps/unicorn-app/next-env.d.ts | 5 - apps/unicorn-app/next.config.js | 22 - apps/unicorn-app/project.json | 101 ----- apps/unicorn-app/public/.gitkeep | 0 apps/unicorn-app/public/favicon.ico | Bin 15086 -> 0 bytes apps/unicorn-app/server.ts | 6 - apps/unicorn-app/specs/index.spec.tsx | 11 - apps/unicorn-app/src/app/api/hello/route.ts | 3 - apps/unicorn-app/src/app/global.css | 409 ------------------ apps/unicorn-app/src/app/layout.tsx | 18 - apps/unicorn-app/src/app/page.module.css | 2 - apps/unicorn-app/src/app/page.tsx | 17 - apps/unicorn-app/tsconfig.json | 30 -- apps/unicorn-app/tsconfig.server.json | 11 - apps/unicorn-app/tsconfig.spec.json | 21 - apps/unicorn-app/webpack.config.js | 8 - charts/islandis/values.dev.yaml | 64 --- charts/islandis/values.prod.yaml | 64 --- charts/islandis/values.staging.yaml | 64 --- charts/services/unicorn-app/values.dev.yaml | 81 ---- charts/services/unicorn-app/values.prod.yaml | 81 ---- .../services/unicorn-app/values.staging.yaml | 81 ---- infra/src/uber-charts/islandis.ts | 6 - scripts/ci/create-release.mjs | 35 -- scripts/ci/get-last-release.mjs | 17 - scripts/ci/unicorn-utils.mjs | 17 - tsconfig.base.json | 1 - 42 files changed, 2 insertions(+), 1458 deletions(-) delete mode 100644 .github/workflows/merge-queue-ci.yml delete mode 100644 .github/workflows/unicorns.yml delete mode 100644 apps/unicorn-app/.eslintrc.json delete mode 100644 apps/unicorn-app/codegen.yml delete mode 100644 apps/unicorn-app/index.d.ts delete mode 100644 apps/unicorn-app/infra/infra.ts delete mode 100644 apps/unicorn-app/jest.config.ts delete mode 100644 apps/unicorn-app/next-env.d.ts delete mode 100644 apps/unicorn-app/next.config.js delete mode 100644 apps/unicorn-app/project.json delete mode 100644 apps/unicorn-app/public/.gitkeep delete mode 100644 apps/unicorn-app/public/favicon.ico delete mode 100644 apps/unicorn-app/server.ts delete mode 100644 apps/unicorn-app/specs/index.spec.tsx delete mode 100644 apps/unicorn-app/src/app/api/hello/route.ts delete mode 100644 apps/unicorn-app/src/app/global.css delete mode 100644 apps/unicorn-app/src/app/layout.tsx delete mode 100644 apps/unicorn-app/src/app/page.module.css delete mode 100644 apps/unicorn-app/src/app/page.tsx delete mode 100644 apps/unicorn-app/tsconfig.json delete mode 100644 apps/unicorn-app/tsconfig.server.json delete mode 100644 apps/unicorn-app/tsconfig.spec.json delete mode 100644 apps/unicorn-app/webpack.config.js delete mode 100644 charts/services/unicorn-app/values.dev.yaml delete mode 100644 charts/services/unicorn-app/values.prod.yaml delete mode 100644 charts/services/unicorn-app/values.staging.yaml delete mode 100644 scripts/ci/create-release.mjs delete mode 100644 scripts/ci/get-last-release.mjs delete mode 100644 scripts/ci/unicorn-utils.mjs diff --git a/.github/workflows/config-values.yaml b/.github/workflows/config-values.yaml index e349d4ca9c26..883861366f8b 100644 --- a/.github/workflows/config-values.yaml +++ b/.github/workflows/config-values.yaml @@ -6,17 +6,12 @@ on: - 'main' - 'release/**' - 'pre-release/**' - - '!unicorn-pipe-rel3' - - '!feature/unicorn-app' paths: - 'charts/**' - 'infra/**' - '**/infra/**' workflow_dispatch: {} pull_request: - branches: - - '!uni-pr' - - '!feature/unicorn-app' paths: - 'charts/**' - 'infra/**' diff --git a/.github/workflows/external-checks.yml b/.github/workflows/external-checks.yml index 816915e60af3..5707795d4f9b 100644 --- a/.github/workflows/external-checks.yml +++ b/.github/workflows/external-checks.yml @@ -1,9 +1,7 @@ name: External checks on: - pull_request: - branches-ignore: - - 'feature/unicorn-app' + pull_request: {} workflow_dispatch: {} defaults: diff --git a/.github/workflows/merge-queue-ci.yml b/.github/workflows/merge-queue-ci.yml deleted file mode 100644 index 2e96f313443e..000000000000 --- a/.github/workflows/merge-queue-ci.yml +++ /dev/null @@ -1,19 +0,0 @@ -name: Validate code in the merge queue - -on: - merge_group: - -jobs: - validate-pr: - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v3 - - name: Display info - run: | - echo "Hallo Merge Queue" - pwd - tree -a -I '.git' - git status - - name: Run slow CI (emulated by a long sleep) - run: sleep 300 diff --git a/.github/workflows/pullrequest-close.yml b/.github/workflows/pullrequest-close.yml index 76e88bbcba19..5d3e066f3866 100644 --- a/.github/workflows/pullrequest-close.yml +++ b/.github/workflows/pullrequest-close.yml @@ -4,9 +4,6 @@ on: pull_request: types: - closed - branches-ignore: - - 'uni-pr' - - 'feature/unicorn-app' defaults: run: diff --git a/.github/workflows/pullrequest-lint.yml b/.github/workflows/pullrequest-lint.yml index 41e94022f624..f7b04171a1c8 100644 --- a/.github/workflows/pullrequest-lint.yml +++ b/.github/workflows/pullrequest-lint.yml @@ -6,9 +6,6 @@ on: - reopened - edited - synchronize - branches-ignore: - - 'uni-pr' - - 'feature/unicorn-app' defaults: run: diff --git a/.github/workflows/pullrequest.yml b/.github/workflows/pullrequest.yml index b0de5846c2a2..71eace37971b 100644 --- a/.github/workflows/pullrequest.yml +++ b/.github/workflows/pullrequest.yml @@ -1,10 +1,7 @@ name: Monorepo pipeline - pull request on: - pull_request: - branches-ignore: - - 'uni-pr' - - 'feature/unicorn-app' + pull_request: {} workflow_dispatch: {} defaults: diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index 526291f1f2fa..15225953329b 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -6,8 +6,6 @@ on: - 'main' - 'release/**' - 'pre-release/**' - - '!uni-pr' - - '!feature/unicorn-app' paths-ignore: - '**/*.md' tags: @@ -15,9 +13,6 @@ on: workflow_dispatch: create: pull_request: - branches: - - '!uni-pr' - - '!feature/unicorn-app' types: - opened - synchronize diff --git a/.github/workflows/revert-pr.yaml b/.github/workflows/revert-pr.yaml index 5945712005b5..492ca4300e92 100644 --- a/.github/workflows/revert-pr.yaml +++ b/.github/workflows/revert-pr.yaml @@ -8,11 +8,7 @@ on: - completed branches: - main - - '!uni-pr' - - '!feature/unicorn-app' pull_request: - branches: - - '!uni-pr' types: - opened - synchronize diff --git a/.github/workflows/unicorns.yml b/.github/workflows/unicorns.yml deleted file mode 100644 index 2bf69e51d595..000000000000 --- a/.github/workflows/unicorns.yml +++ /dev/null @@ -1,106 +0,0 @@ -name: Unicorn CI/CD pipeline -on: - workflow_dispatch: - create: - pull_request: - types: - - opened - - synchronize - - labeled - - closed - pull_request_review: - types: [submitted] -concurrency: - # See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#example-using-a-fallback-value - group: push-unicorn${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -defaults: - run: - shell: bash -jobs: - printJob: - name: Print event - runs-on: ubuntu-latest - steps: - - name: Dump GitHub context - env: - GITHUB_CONTEXT: ${{ toJson(github) }} - run: | - echo "$GITHUB_CONTEXT" - check-approved: - name: Is PR approved - runs-on: ubuntu-latest - outputs: - IS_APPROVED: ${{ steps.check-approved.outputs.result }} - steps: - - name: Check if PR is approved - id: check-approved - uses: actions/github-script@v7 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - if (!context || !context.payload || !context.payload.pull_request) { - return false; - } - var reviews = await github.rest.pulls.listReviews({ - owner: context.repo.owner, - repo: context.repo.repo, - pull_number: context.issue.number - }); - - if(!reviews || !reviews.data == null) { - return false; - } - var declined = reviews.data.filter(review => review.state != 'APPROVED').length > 0; - var pending = context.payload.pull_request.requested_teams.length > 0 - console.log(!(declined && pending) ? 'Pr is approved' : 'Pr is not approved'); - return !(declined && pending); - - name: output - run: echo "IS_APPROVED=${{ steps.check-approved.outputs.result }}" >> $GITHUB_OUTPUT - - check-unicorn: - name: Is this a unicorn PR - needs: check-approved - if: ${{ needs.check-approved.outputs.IS_APPROVED }}" - runs-on: ec2-runners - container: - image: public.ecr.aws/m3u4c4h9/island-is/actions-runner-public:latest - timeout-minutes: 10 - steps: - - run: echo "selected runner = ${{ runner.name }}" - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - uses: actions/setup-node@v4 - with: - node-version-file: 'package.json' - - - name: Setup yarn - run: corepack enable - - - name: Get cache - id: get-cache - uses: ./.github/actions/get-cache - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - enable-cache: 'node_modules,generated-files' - - - name: Check unicorn affected - id: unicorn-affected - env: - BaseRef: ${{ github.base_ref }} #The target branch e.g. main (feature/unicorn-app) - HeadRef: ${{ github.head_ref }} #The branch being merged e.g. (unicorn-pipe-rel3) - run: | - echo "Comparing nx affected for $HeadRef using origin/$BaseRef as base branch" - echo IS_UNICORN=$(node scripts/ci/unicorn-utils.mjs "{\"baseBranch\": \"origin/$BaseRef\", \"mergeBranch\": \"$HeadRef\" }") >> "$GITHUB_OUTPUT" - - name: Results - run: | - echo "Unicorn = ${{ steps.unicorn-affected.outputs.IS_UNICORN }}" - - - name: Find Latest Release Branch - run: | - node scripts/ci/get-last-release.mjs $(git branch -r) - - - run: "echo 'latest release: ${{ steps.get_latest_release.outputs.data }}'" diff --git a/.gitignore b/.gitignore index a31e5dd70833..46c71ffd1796 100644 --- a/.gitignore +++ b/.gitignore @@ -31,7 +31,6 @@ TODO .env .env.* .envrc.private -nx-cloud.env .nvmrc .node-version # IDE - VSCode diff --git a/apps/unicorn-app/.eslintrc.json b/apps/unicorn-app/.eslintrc.json deleted file mode 100644 index b0b7131a6c3a..000000000000 --- a/apps/unicorn-app/.eslintrc.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "extends": ["plugin:@nx/react", "../../.eslintrc.json"], - "ignorePatterns": ["!**/*"], - "rules": { - "@nx/enforce-module-boundaries": [ - "error", - { - "enforceBuildableLibDependency": true, - "allowCircularSelfDependency": true, - "allow": ["../../../infra/src/dsl"], - "depConstraints": [ - { - "sourceTag": "*", - "onlyDependOnLibsWithTags": ["*"] - } - ] - } - ], - "simple-import-sort/imports": [ - "warn", - { - "groups": [ - // React related packages come first, followed by all external imports. - ["^react", "^\\w", "^@(?!island).+"], - // Then island.is packages. - ["^(@island.is).*"], - // Then all other imports in this order: "../", "./", "./css" - [ - "^\\.\\.(?!/?$)", - "^\\.\\./?$", - "^\\./(?=.*/)(?!/?$)", - "^\\.(?!/?$)", - "^\\./?$", - "^.+\\.?(css)$" - ] - ] - } - ] - }, - "plugins": ["simple-import-sort"], - "overrides": [ - { - "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], - "rules": {} - }, - { - "files": ["*.ts", "*.tsx"], - "rules": {} - }, - { - "files": ["*.js", "*.jsx"], - "rules": {} - } - ] -} diff --git a/apps/unicorn-app/codegen.yml b/apps/unicorn-app/codegen.yml deleted file mode 100644 index bcc1fe6a884d..000000000000 --- a/apps/unicorn-app/codegen.yml +++ /dev/null @@ -1,24 +0,0 @@ -schema: - - apps/api/src/api.graphql -documents: apps/unicorn-app/src/queries/*.{ts,tsx} -generates: - apps/web/graphql/schema.ts: - plugins: - - typescript - - typescript-operations - config: - exportFragmentSpreadSubTypes: true - scalars: - DateTime: Date - JSON: '{ [key: string]: any }' - namingConvention: - typeNames: change-case#pascalCase - apps/web/graphql/fragmentTypes.json: - plugins: - - fragment-matcher - config: - module: commonjs - apolloClientVersion: 3 -hooks: - afterAllFileWrite: - - prettier --write diff --git a/apps/unicorn-app/index.d.ts b/apps/unicorn-app/index.d.ts deleted file mode 100644 index a9ab53489c7f..000000000000 --- a/apps/unicorn-app/index.d.ts +++ /dev/null @@ -1,6 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -declare module '*.svg' { - const content: any - export const ReactComponent: any - export default content -} diff --git a/apps/unicorn-app/infra/infra.ts b/apps/unicorn-app/infra/infra.ts deleted file mode 100644 index 47fdfe93c611..000000000000 --- a/apps/unicorn-app/infra/infra.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { service, ServiceBuilder } from '../../../infra/src/dsl/dsl' -const serviceName = 'unicorn-app' -export const serviceSetup = (): ServiceBuilder => - service(serviceName) - .image(serviceName) - .namespace(serviceName) - .serviceAccount(serviceName) - .ingress({ - primary: { - host: { - dev: serviceName, - staging: serviceName, - prod: serviceName, - }, - paths: ['/'], - }, - }) - .replicaCount({ - default: 1, - min: 1, - max: 10, - }) - .resources({ - limits: { - cpu: '200m', - memory: '256Mi', - }, - requests: { - cpu: '50m', - memory: '128Mi', - }, - }) - .liveness('/liveness') - .readiness('/readiness') diff --git a/apps/unicorn-app/jest.config.ts b/apps/unicorn-app/jest.config.ts deleted file mode 100644 index 5705a6bac0c1..000000000000 --- a/apps/unicorn-app/jest.config.ts +++ /dev/null @@ -1,11 +0,0 @@ -/* eslint-disable */ -export default { - displayName: 'unicorn-app', - preset: '../../jest.preset.js', - transform: { - '^(?!.*\\.(js|jsx|ts|tsx|css|json)$)': '@nx/react/plugins/jest', - '^.+\\.[tj]sx?$': ['babel-jest', { presets: ['@nx/next/babel'] }], - }, - moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], - coverageDirectory: '../../coverage/apps/unicorn-app', -} diff --git a/apps/unicorn-app/next-env.d.ts b/apps/unicorn-app/next-env.d.ts deleted file mode 100644 index 4f11a03dc6cc..000000000000 --- a/apps/unicorn-app/next-env.d.ts +++ /dev/null @@ -1,5 +0,0 @@ -/// -/// - -// NOTE: This file should not be edited -// see https://nextjs.org/docs/basic-features/typescript for more information. diff --git a/apps/unicorn-app/next.config.js b/apps/unicorn-app/next.config.js deleted file mode 100644 index 5dd157afdc66..000000000000 --- a/apps/unicorn-app/next.config.js +++ /dev/null @@ -1,22 +0,0 @@ -//@ts-check - -// eslint-disable-next-line @typescript-eslint/no-var-requires -const { composePlugins, withNx } = require('@nx/next') - -/** - * @type {import('@nx/next/plugins/with-nx').WithNxOptions} - **/ -const nextConfig = { - nx: { - // Set this to true if you would like to use SVGR - // See: https://github.com/gregberge/svgr - svgr: false, - }, -} - -const plugins = [ - // Add more Next.js plugins to this list if needed. - withNx, -] - -module.exports = composePlugins(...plugins)(nextConfig) diff --git a/apps/unicorn-app/project.json b/apps/unicorn-app/project.json deleted file mode 100644 index c7b92fdd580a..000000000000 --- a/apps/unicorn-app/project.json +++ /dev/null @@ -1,101 +0,0 @@ -{ - "name": "unicorn-app", - "$schema": "../../node_modules/nx/schemas/project-schema.json", - "sourceRoot": "apps/unicorn-app", - "projectType": "application", - "tags": ["scope:react-next"], - "targets": { - "build": { - "executor": "@nx/next:build", - "outputs": ["{options.outputPath}"], - "defaultConfiguration": "production", - "options": { - "outputPath": "dist/apps/unicorn-app" - }, - "configurations": { - "development": { - "outputPath": "apps/unicorn-app" - }, - "production": {} - }, - "dependsOn": ["build-custom-server"] - }, - "build-custom-server": { - "executor": "@nx/webpack:webpack", - "defaultConfiguration": "production", - "options": { - "outputPath": "dist/apps/unicorn-app", - "main": "apps/unicorn-app/server.ts", - "tsConfig": "apps/unicorn-app/tsconfig.server.json", - "maxWorkers": 2, - "assets": [], - "compiler": "tsc", - "target": "node", - "webpackConfig": "apps/unicorn-app/webpack.config.js" - }, - "configurations": { - "development": {}, - "production": { - "optimization": true, - "extractLicenses": true, - "inspect": false - } - } - }, - "serve": { - "executor": "@nx/next:server", - "defaultConfiguration": "development", - "options": { - "buildTarget": "unicorn-app:build", - "dev": true, - "customServerTarget": "unicorn-app:serve-custom-server" - }, - "configurations": { - "development": { - "buildTarget": "unicorn-app:build:development", - "dev": true, - "customServerTarget": "unicorn-app:serve-custom-server:development" - }, - "production": { - "buildTarget": "unicorn-app:build:production", - "dev": false, - "customServerTarget": "unicorn-app:serve-custom-server:production" - } - } - }, - "serve-custom-server": { - "executor": "@nx/js:node", - "defaultConfiguration": "development", - "options": { - "buildTarget": "unicorn-app:build-custom-server" - }, - "configurations": { - "development": { - "buildTarget": "unicorn-app:build-custom-server:development" - }, - "production": { - "buildTarget": "unicorn-app:build-custom-server:production" - } - } - }, - "export": { - "executor": "@nx/next:export", - "options": { - "buildTarget": "unicorn-app:build:production" - } - }, - "test": { - "executor": "@nx/jest:jest", - "outputs": ["{workspaceRoot}/coverage/apps/unicorn-app"], - "options": { - "jestConfig": "apps/unicorn-app/jest.config.ts" - } - }, - "lint": { - "executor": "@nx/eslint:lint" - }, - "docker-next": { - "executor": "Intentionally left blank, only so this target is valid when using `nx show projects --with-target docker-next`" - } - } -} diff --git a/apps/unicorn-app/public/.gitkeep b/apps/unicorn-app/public/.gitkeep deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/apps/unicorn-app/public/favicon.ico b/apps/unicorn-app/public/favicon.ico deleted file mode 100644 index 317ebcb2336e0833a22dddf0ab287849f26fda57..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15086 zcmeI332;U^%p|z7g|#(P)qFEA@4f!_@qOK2 z_lJl}!lhL!VT_U|uN7%8B2iKH??xhDa;*`g{yjTFWHvXn;2s{4R7kH|pKGdy(7z!K zgftM+Ku7~24TLlh(!g)gz|foI94G^t2^IO$uvX$3(OR0<_5L2sB)lMAMy|+`xodJ{ z_Uh_1m)~h?a;2W{dmhM;u!YGo=)OdmId_B<%^V^{ovI@y`7^g1_V9G}*f# zNzAtvou}I!W1#{M^@ROc(BZ! z+F!!_aR&Px3_reO(EW+TwlW~tv*2zr?iP7(d~a~yA|@*a89IUke+c472NXM0wiX{- zl`UrZC^1XYyf%1u)-Y)jj9;MZ!SLfd2Hl?o|80Su%Z?To_=^g_Jt0oa#CT*tjx>BI z16wec&AOWNK<#i0Qd=1O$fymLRoUR*%;h@*@v7}wApDl^w*h}!sYq%kw+DKDY)@&A z@9$ULEB3qkR#85`lb8#WZw=@})#kQig9oqy^I$dj&k4jU&^2(M3q{n1AKeGUKPFbr z1^<)aH;VsG@J|B&l>UtU#Ejv3GIqERzYgL@UOAWtW<{p#zy`WyJgpCy8$c_e%wYJL zyGHRRx38)HyjU3y{-4z6)pzb>&Q1pR)B&u01F-|&Gx4EZWK$nkUkOI|(D4UHOXg_- zw{OBf!oWQUn)Pe(=f=nt=zkmdjpO^o8ZZ9o_|4tW1ni+Un9iCW47*-ut$KQOww!;u z`0q)$s6IZO!~9$e_P9X!hqLxu`fpcL|2f^I5d4*a@Dq28;@2271v_N+5HqYZ>x;&O z05*7JT)mUe&%S0@UD)@&8SmQrMtsDfZT;fkdA!r(S=}Oz>iP)w=W508=Rc#nNn7ym z1;42c|8($ALY8#a({%1#IXbWn9-Y|0eDY$_L&j{63?{?AH{);EzcqfydD$@-B`Y3<%IIj7S7rK_N}je^=dEk%JQ4c z!tBdTPE3Tse;oYF>cnrapWq*o)m47X1`~6@(!Y29#>-#8zm&LXrXa(3=7Z)ElaQqj z-#0JJy3Fi(C#Rx(`=VXtJ63E2_bZGCz+QRa{W0e2(m3sI?LOcUBx)~^YCqZ{XEPX)C>G>U4tfqeH8L(3|pQR*zbL1 zT9e~4Tb5p9_G}$y4t`i*4t_Mr9QYvL9C&Ah*}t`q*}S+VYh0M6GxTTSXI)hMpMpIq zD1ImYqJLzbj0}~EpE-aH#VCH_udYEW#`P2zYmi&xSPs_{n6tBj=MY|-XrA;SGA_>y zGtU$?HXm$gYj*!N)_nQ59%lQdXtQZS3*#PC-{iB_sm+ytD*7j`D*k(P&IH2GHT}Eh z5697eQECVIGQAUe#eU2I!yI&%0CP#>%6MWV z@zS!p@+Y1i1b^QuuEF*13CuB zu69dve5k7&Wgb+^s|UB08Dr3u`h@yM0NTj4h7MnHo-4@xmyr7(*4$rpPwsCDZ@2be zRz9V^GnV;;?^Lk%ynzq&K(Aix`mWmW`^152Hoy$CTYVehpD-S1-W^#k#{0^L`V6CN+E z!w+xte;2vu4AmVNEFUOBmrBL>6MK@!O2*N|2=d|Y;oN&A&qv=qKn73lDD zI(+oJAdgv>Yr}8(&@ZuAZE%XUXmX(U!N+Z_sjL<1vjy1R+1IeHt`79fnYdOL{$ci7 z%3f0A*;Zt@ED&Gjm|OFTYBDe%bbo*xXAQsFz+Q`fVBH!N2)kaxN8P$c>sp~QXnv>b zwq=W3&Mtmih7xkR$YA)1Yi?avHNR6C99!u6fh=cL|KQ&PwF!n@ud^n(HNIImHD!h87!i*t?G|p0o+eelJ?B@A64_9%SBhNaJ64EvKgD&%LjLCYnNfc; znj?%*p@*?dq#NqcQFmmX($wms@CSAr9#>hUR^=I+=0B)vvGX%T&#h$kmX*s=^M2E!@N9#m?LhMvz}YB+kd zG~mbP|D(;{s_#;hsKK9lbVK&Lo734x7SIFJ9V_}2$@q?zm^7?*XH94w5Qae{7zOMUF z^?%F%)c1Y)Q?Iy?I>knw*8gYW#ok|2gdS=YYZLiD=CW|Nj;n^x!=S#iJ#`~Ld79+xXpVmUK^B(xO_vO!btA9y7w3L3-0j-y4 z?M-V{%z;JI`bk7yFDcP}OcCd*{Q9S5$iGA7*E1@tfkyjAi!;wP^O71cZ^Ep)qrQ)N z#wqw0_HS;T7x3y|`P==i3hEwK%|>fZ)c&@kgKO1~5<5xBSk?iZV?KI6&i72H6S9A* z=U(*e)EqEs?Oc04)V-~K5AUmh|62H4*`UAtItO$O(q5?6jj+K^oD!04r=6#dsxp?~}{`?&sXn#q2 zGuY~7>O2=!u@@Kfu7q=W*4egu@qPMRM>(eyYyaIE<|j%d=iWNdGsx%c!902v#ngNg z@#U-O_4xN$s_9?(`{>{>7~-6FgWpBpqXb`Ydc3OFL#&I}Irse9F_8R@4zSS*Y*o*B zXL?6*Aw!AfkNCgcr#*yj&p3ZDe2y>v$>FUdKIy_2N~}6AbHc7gA3`6$g@1o|dE>vz z4pl(j9;kyMsjaw}lO?(?Xg%4k!5%^t#@5n=WVc&JRa+XT$~#@rldvN3S1rEpU$;XgxVny7mki3 z-Hh|jUCHrUXuLr!)`w>wgO0N%KTB-1di>cj(x3Bav`7v z3G7EIbU$z>`Nad7Rk_&OT-W{;qg)-GXV-aJT#(ozdmnA~Rq3GQ_3mby(>q6Ocb-RgTUhTN)))x>m&eD;$J5Bg zo&DhY36Yg=J=$Z>t}RJ>o|@hAcwWzN#r(WJ52^g$lh^!63@hh+dR$&_dEGu&^CR*< z!oFqSqO@>xZ*nC2oiOd0eS*F^IL~W-rsrO`J`ej{=ou_q^_(<$&-3f^J z&L^MSYWIe{&pYq&9eGaArA~*kA { - it('should render successfully', () => { - const { baseElement } = render() - expect(baseElement).toBeTruthy() - }) -}) diff --git a/apps/unicorn-app/src/app/api/hello/route.ts b/apps/unicorn-app/src/app/api/hello/route.ts deleted file mode 100644 index 4a0d8ceed763..000000000000 --- a/apps/unicorn-app/src/app/api/hello/route.ts +++ /dev/null @@ -1,3 +0,0 @@ -export async function GET(request: Request) { - return new Response('Hello, hello, from API!') -} diff --git a/apps/unicorn-app/src/app/global.css b/apps/unicorn-app/src/app/global.css deleted file mode 100644 index 2a8880df280a..000000000000 --- a/apps/unicorn-app/src/app/global.css +++ /dev/null @@ -1,409 +0,0 @@ -html { - -webkit-text-size-adjust: 100%; - font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, - Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif, - Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji; - line-height: 1.5; - tab-size: 4; - scroll-behavior: smooth; -} -body { - font-family: inherit; - line-height: inherit; - margin: 0; -} -h1, -h2, -p, -pre { - margin: 0; -} -*, -::before, -::after { - box-sizing: border-box; - border-width: 0; - border-style: solid; - border-color: currentColor; -} -h1, -h2 { - font-size: inherit; - font-weight: inherit; -} -a { - color: inherit; - text-decoration: inherit; -} -pre { - font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, - Liberation Mono, Courier New, monospace; -} -svg { - display: block; - vertical-align: middle; - shape-rendering: auto; - text-rendering: optimizeLegibility; -} -pre { - background-color: rgba(55, 65, 81, 1); - border-radius: 0.25rem; - color: rgba(229, 231, 235, 1); - font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, - Liberation Mono, Courier New, monospace; - overflow: scroll; - padding: 0.5rem 0.75rem; -} - -.shadow { - box-shadow: 0 0 #0000, 0 0 #0000, 0 10px 15px -3px rgba(0, 0, 0, 0.1), - 0 4px 6px -2px rgba(0, 0, 0, 0.05); -} -.rounded { - border-radius: 1.5rem; -} -.wrapper { - width: 100%; -} -.container { - margin-left: auto; - margin-right: auto; - max-width: 768px; - padding-bottom: 3rem; - padding-left: 1rem; - padding-right: 1rem; - color: rgba(55, 65, 81, 1); - width: 100%; -} -#welcome { - margin-top: 2.5rem; -} -#welcome h1 { - font-size: 3rem; - font-weight: 500; - letter-spacing: -0.025em; - line-height: 1; -} -#welcome span { - display: block; - font-size: 3.875rem; - font-weight: 300; - line-height: 2.25rem; - margin-bottom: 0.5rem; -} -#hero { - align-items: center; - background-color: hsla(214, 62%, 21%, 1); - border: none; - box-sizing: border-box; - color: rgba(55, 65, 81, 1); - display: grid; - grid-template-columns: 1fr; - margin-top: 3.5rem; -} -#hero .text-container { - color: rgba(255, 255, 255, 1); - padding: 3rem 2rem; -} -#hero .text-container h2 { - font-size: 1.5rem; - line-height: 2rem; - position: relative; -} -#hero .text-container h2 svg { - color: hsla(162, 47%, 50%, 1); - height: 2rem; - left: -0.25rem; - position: absolute; - top: 0; - width: 2rem; -} -#hero .text-container h2 span { - margin-left: 2.5rem; -} -#hero .text-container a { - background-color: rgba(255, 255, 255, 1); - border-radius: 0.75rem; - color: rgba(55, 65, 81, 1); - display: inline-block; - margin-top: 1.5rem; - padding: 1rem 2rem; - text-decoration: inherit; -} -#hero .logo-container { - display: none; - justify-content: center; - padding-left: 2rem; - padding-right: 2rem; -} -#hero .logo-container svg { - color: rgba(255, 255, 255, 1); - width: 66.666667%; -} -#middle-content { - align-items: flex-start; - display: grid; - gap: 4rem; - grid-template-columns: 1fr; - margin-top: 3.5rem; -} -#learning-materials { - padding: 2.5rem 2rem; -} -#learning-materials h2 { - font-weight: 500; - font-size: 1.25rem; - letter-spacing: -0.025em; - line-height: 1.75rem; - padding-left: 1rem; - padding-right: 1rem; -} -.list-item-link { - align-items: center; - border-radius: 0.75rem; - display: flex; - margin-top: 1rem; - padding: 1rem; - transition-property: background-color, border-color, color, fill, stroke, - opacity, box-shadow, transform, filter, backdrop-filter, - -webkit-backdrop-filter; - transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - transition-duration: 150ms; - width: 100%; -} -.list-item-link svg:first-child { - margin-right: 1rem; - height: 1.5rem; - transition-property: background-color, border-color, color, fill, stroke, - opacity, box-shadow, transform, filter, backdrop-filter, - -webkit-backdrop-filter; - transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - transition-duration: 150ms; - width: 1.5rem; -} -.list-item-link > span { - flex-grow: 1; - font-weight: 400; - transition-property: background-color, border-color, color, fill, stroke, - opacity, box-shadow, transform, filter, backdrop-filter, - -webkit-backdrop-filter; - transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - transition-duration: 150ms; -} -.list-item-link > span > span { - color: rgba(107, 114, 128, 1); - display: block; - flex-grow: 1; - font-size: 0.75rem; - font-weight: 300; - line-height: 1rem; - transition-property: background-color, border-color, color, fill, stroke, - opacity, box-shadow, transform, filter, backdrop-filter, - -webkit-backdrop-filter; - transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - transition-duration: 150ms; -} -.list-item-link svg:last-child { - height: 1rem; - transition-property: all; - transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - transition-duration: 150ms; - width: 1rem; -} -.list-item-link:hover { - color: rgba(255, 255, 255, 1); - background-color: hsla(162, 47%, 50%, 1); -} -.list-item-link:hover > span { -} -.list-item-link:hover > span > span { - color: rgba(243, 244, 246, 1); -} -.list-item-link:hover svg:last-child { - transform: translateX(0.25rem); -} -#other-links { -} -.button-pill { - padding: 1.5rem 2rem; - transition-duration: 300ms; - transition-property: background-color, border-color, color, fill, stroke, - opacity, box-shadow, transform, filter, backdrop-filter, - -webkit-backdrop-filter; - transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - align-items: center; - display: flex; -} -.button-pill svg { - transition-property: background-color, border-color, color, fill, stroke, - opacity, box-shadow, transform, filter, backdrop-filter, - -webkit-backdrop-filter; - transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - transition-duration: 150ms; - flex-shrink: 0; - width: 3rem; -} -.button-pill > span { - letter-spacing: -0.025em; - font-weight: 400; - font-size: 1.125rem; - line-height: 1.75rem; - padding-left: 1rem; - padding-right: 1rem; -} -.button-pill span span { - display: block; - font-size: 0.875rem; - font-weight: 300; - line-height: 1.25rem; -} -.button-pill:hover svg, -.button-pill:hover { - color: rgba(255, 255, 255, 1) !important; -} -#nx-console:hover { - background-color: rgba(0, 122, 204, 1); -} -#nx-console svg { - color: rgba(0, 122, 204, 1); -} -#nx-console-jetbrains { - margin-top: 2rem; -} -#nx-console-jetbrains:hover { - background-color: rgba(255, 49, 140, 1); -} -#nx-console-jetbrains svg { - color: rgba(255, 49, 140, 1); -} -#nx-repo:hover { - background-color: rgba(24, 23, 23, 1); -} -#nx-repo svg { - color: rgba(24, 23, 23, 1); -} -#nx-cloud { - margin-bottom: 2rem; - margin-top: 2rem; - padding: 2.5rem 2rem; -} -#nx-cloud > div { - align-items: center; - display: flex; -} -#nx-cloud > div svg { - border-radius: 0.375rem; - flex-shrink: 0; - width: 3rem; -} -#nx-cloud > div h2 { - font-size: 1.125rem; - font-weight: 400; - letter-spacing: -0.025em; - line-height: 1.75rem; - padding-left: 1rem; - padding-right: 1rem; -} -#nx-cloud > div h2 span { - display: block; - font-size: 0.875rem; - font-weight: 300; - line-height: 1.25rem; -} -#nx-cloud p { - font-size: 1rem; - line-height: 1.5rem; - margin-top: 1rem; -} -#nx-cloud pre { - margin-top: 1rem; -} -#nx-cloud a { - color: rgba(107, 114, 128, 1); - display: block; - font-size: 0.875rem; - line-height: 1.25rem; - margin-top: 1.5rem; - text-align: right; -} -#nx-cloud a:hover { - text-decoration: underline; -} -#commands { - padding: 2.5rem 2rem; - margin-top: 3.5rem; -} -#commands h2 { - font-size: 1.25rem; - font-weight: 400; - letter-spacing: -0.025em; - line-height: 1.75rem; - padding-left: 1rem; - padding-right: 1rem; -} -#commands p { - font-size: 1rem; - font-weight: 300; - line-height: 1.5rem; - margin-top: 1rem; - padding-left: 1rem; - padding-right: 1rem; -} -details { - align-items: center; - display: flex; - margin-top: 1rem; - padding-left: 1rem; - padding-right: 1rem; - width: 100%; -} -details pre > span { - color: rgba(181, 181, 181, 1); - display: block; -} -summary { - border-radius: 0.5rem; - display: flex; - font-weight: 400; - padding: 0.5rem; - cursor: pointer; - transition-property: background-color, border-color, color, fill, stroke, - opacity, box-shadow, transform, filter, backdrop-filter, - -webkit-backdrop-filter; - transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - transition-duration: 150ms; -} -summary:hover { - background-color: rgba(243, 244, 246, 1); -} -summary svg { - height: 1.5rem; - margin-right: 1rem; - width: 1.5rem; -} -#love { - color: rgba(107, 114, 128, 1); - font-size: 0.875rem; - line-height: 1.25rem; - margin-top: 3.5rem; - opacity: 0.6; - text-align: center; -} -#love svg { - color: rgba(252, 165, 165, 1); - width: 1.25rem; - height: 1.25rem; - display: inline; - margin-top: -0.25rem; -} -@media screen and (min-width: 768px) { - #hero { - grid-template-columns: repeat(2, minmax(0, 1fr)); - } - #hero .logo-container { - display: flex; - } - #middle-content { - grid-template-columns: repeat(2, minmax(0, 1fr)); - } -} diff --git a/apps/unicorn-app/src/app/layout.tsx b/apps/unicorn-app/src/app/layout.tsx deleted file mode 100644 index 43b373a18bf6..000000000000 --- a/apps/unicorn-app/src/app/layout.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import './global.css' - -export const metadata = { - title: 'Welcome to Unicorn app', - description: 'Generated by create-nx-workspace', -} - -export default function RootLayout({ - children, -}: { - children: React.ReactNode -}) { - return ( - - {children} - - ) -} diff --git a/apps/unicorn-app/src/app/page.module.css b/apps/unicorn-app/src/app/page.module.css deleted file mode 100644 index 8a13e21cb311..000000000000 --- a/apps/unicorn-app/src/app/page.module.css +++ /dev/null @@ -1,2 +0,0 @@ -.page { -} diff --git a/apps/unicorn-app/src/app/page.tsx b/apps/unicorn-app/src/app/page.tsx deleted file mode 100644 index 5d9289cb0058..000000000000 --- a/apps/unicorn-app/src/app/page.tsx +++ /dev/null @@ -1,17 +0,0 @@ -export default function Index() { - return ( -
-
-
-
-

- - Welcome unicorn 🦄 - -

-
-
-
-
- ) -} diff --git a/apps/unicorn-app/tsconfig.json b/apps/unicorn-app/tsconfig.json deleted file mode 100644 index 37e77ed8c826..000000000000 --- a/apps/unicorn-app/tsconfig.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "extends": "../../tsconfig.base.json", - "compilerOptions": { - "jsx": "preserve", - "allowJs": true, - "esModuleInterop": true, - "allowSyntheticDefaultImports": true, - "types": ["node", "jest"], - "strict": false, - "forceConsistentCasingInFileNames": true, - "noEmit": true, - "resolveJsonModule": true, - "isolatedModules": true, - "incremental": true, - "plugins": [ - { - "name": "next" - } - ] - }, - "include": [ - "**/*.ts", - "**/*.tsx", - "**/*.js", - "**/*.jsx", - "next-env.d.ts", - ".next/types/**/*.ts" - ], - "exclude": ["node_modules", "jest.config.ts"] -} diff --git a/apps/unicorn-app/tsconfig.server.json b/apps/unicorn-app/tsconfig.server.json deleted file mode 100644 index 8fafdb4be36d..000000000000 --- a/apps/unicorn-app/tsconfig.server.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "module": "commonjs", - "noEmit": false, - "incremental": true, - "tsBuildInfoFile": "../../tmp/buildcache/apps/unicorn-app/server", - "types": ["node"] - }, - "include": ["server.ts"] -} diff --git a/apps/unicorn-app/tsconfig.spec.json b/apps/unicorn-app/tsconfig.spec.json deleted file mode 100644 index 214b2cc208c1..000000000000 --- a/apps/unicorn-app/tsconfig.spec.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "outDir": "../../dist/out-tsc", - "module": "commonjs", - "types": ["jest", "node"], - "jsx": "react" - }, - "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/unicorn-app/webpack.config.js b/apps/unicorn-app/webpack.config.js deleted file mode 100644 index 95e68d080f5b..000000000000 --- a/apps/unicorn-app/webpack.config.js +++ /dev/null @@ -1,8 +0,0 @@ -const { composePlugins, withNx } = require('@nx/webpack') - -// Nx plugins for webpack. -module.exports = composePlugins(withNx(), (config) => { - // Note: This was added by an Nx migration. Webpack builds are required to have a corresponding Webpack config file. - // See: https://nx.dev/recipes/webpack/webpack-config-setup - return config -}) diff --git a/charts/islandis/values.dev.yaml b/charts/islandis/values.dev.yaml index 29be8c5e25f0..e0f5e81d1937 100644 --- a/charts/islandis/values.dev.yaml +++ b/charts/islandis/values.dev.yaml @@ -1810,7 +1810,6 @@ namespaces: - 'services-sessions' - 'contentful-apps' - 'services-university-gateway' - - 'unicorn-app' - 'portals-my-pages' portals-admin: enabled: true @@ -3155,69 +3154,6 @@ skilavottord-ws: securityContext: allowPrivilegeEscalation: false privileged: false -unicorn-app: - enabled: true - env: - LOG_LEVEL: 'info' - NODE_OPTIONS: '--max-old-space-size=230 -r dd-trace/init' - SERVERSIDE_FEATURES_ON: '' - grantNamespaces: [] - grantNamespacesEnabled: false - healthCheck: - liveness: - initialDelaySeconds: 3 - path: '/liveness' - timeoutSeconds: 3 - readiness: - initialDelaySeconds: 3 - path: '/readiness' - timeoutSeconds: 3 - hpa: - scaling: - metric: - cpuAverageUtilization: 90 - nginxRequestsIrate: 5 - replicas: - max: 10 - min: 1 - image: - repository: '821090935708.dkr.ecr.eu-west-1.amazonaws.com/unicorn-app' - ingress: - primary-alb: - annotations: - kubernetes.io/ingress.class: 'nginx-external-alb' - nginx.ingress.kubernetes.io/service-upstream: 'true' - hosts: - - host: 'unicorn-app.dev01.devland.is' - paths: - - '/' - namespace: 'unicorn-app' - podDisruptionBudget: - maxUnavailable: 1 - podSecurityContext: - fsGroup: 65534 - pvcs: [] - replicaCount: - default: 1 - max: 10 - min: 1 - resources: - limits: - cpu: '200m' - memory: '256Mi' - requests: - cpu: '50m' - memory: '128Mi' - secrets: - CONFIGCAT_SDK_KEY: '/k8s/configcat/CONFIGCAT_SDK_KEY' - securityContext: - allowPrivilegeEscalation: false - privileged: false - serviceAccount: - annotations: - eks.amazonaws.com/role-arn: 'arn:aws:iam::013313053092:role/unicorn-app' - create: true - name: 'unicorn-app' user-notification: args: - '--no-experimental-fetch' diff --git a/charts/islandis/values.prod.yaml b/charts/islandis/values.prod.yaml index ac8712e81ede..d08668a62457 100644 --- a/charts/islandis/values.prod.yaml +++ b/charts/islandis/values.prod.yaml @@ -1674,7 +1674,6 @@ namespaces: - 'services-university-gateway' - 'contentful-apps' - 'contentful-entry-tagger' - - 'unicorn-app' - 'portals-my-pages' portals-admin: enabled: true @@ -3034,69 +3033,6 @@ skilavottord-ws: securityContext: allowPrivilegeEscalation: false privileged: false -unicorn-app: - enabled: true - env: - LOG_LEVEL: 'info' - NODE_OPTIONS: '--max-old-space-size=230 -r dd-trace/init' - SERVERSIDE_FEATURES_ON: 'driving-license-use-v1-endpoint-for-v2-comms' - grantNamespaces: [] - grantNamespacesEnabled: false - healthCheck: - liveness: - initialDelaySeconds: 3 - path: '/liveness' - timeoutSeconds: 3 - readiness: - initialDelaySeconds: 3 - path: '/readiness' - timeoutSeconds: 3 - hpa: - scaling: - metric: - cpuAverageUtilization: 90 - nginxRequestsIrate: 5 - replicas: - max: 10 - min: 1 - image: - repository: '821090935708.dkr.ecr.eu-west-1.amazonaws.com/unicorn-app' - ingress: - primary-alb: - annotations: - kubernetes.io/ingress.class: 'nginx-external-alb' - nginx.ingress.kubernetes.io/service-upstream: 'true' - hosts: - - host: 'unicorn-app.island.is' - paths: - - '/' - namespace: 'unicorn-app' - podDisruptionBudget: - maxUnavailable: 1 - podSecurityContext: - fsGroup: 65534 - pvcs: [] - replicaCount: - default: 1 - max: 10 - min: 1 - resources: - limits: - cpu: '200m' - memory: '256Mi' - requests: - cpu: '50m' - memory: '128Mi' - secrets: - CONFIGCAT_SDK_KEY: '/k8s/configcat/CONFIGCAT_SDK_KEY' - securityContext: - allowPrivilegeEscalation: false - privileged: false - serviceAccount: - annotations: - eks.amazonaws.com/role-arn: 'arn:aws:iam::251502586493:role/unicorn-app' - create: true - name: 'unicorn-app' user-notification: args: - '--no-experimental-fetch' diff --git a/charts/islandis/values.staging.yaml b/charts/islandis/values.staging.yaml index 2a2928d07132..d1c2a12649f8 100644 --- a/charts/islandis/values.staging.yaml +++ b/charts/islandis/values.staging.yaml @@ -1548,7 +1548,6 @@ namespaces: - 'license-api' - 'services-sessions' - 'services-university-gateway' - - 'unicorn-app' - 'portals-my-pages' portals-admin: enabled: true @@ -2893,69 +2892,6 @@ skilavottord-ws: securityContext: allowPrivilegeEscalation: false privileged: false -unicorn-app: - enabled: true - env: - LOG_LEVEL: 'info' - NODE_OPTIONS: '--max-old-space-size=230 -r dd-trace/init' - SERVERSIDE_FEATURES_ON: '' - grantNamespaces: [] - grantNamespacesEnabled: false - healthCheck: - liveness: - initialDelaySeconds: 3 - path: '/liveness' - timeoutSeconds: 3 - readiness: - initialDelaySeconds: 3 - path: '/readiness' - timeoutSeconds: 3 - hpa: - scaling: - metric: - cpuAverageUtilization: 90 - nginxRequestsIrate: 5 - replicas: - max: 3 - min: 1 - image: - repository: '821090935708.dkr.ecr.eu-west-1.amazonaws.com/unicorn-app' - ingress: - primary-alb: - annotations: - kubernetes.io/ingress.class: 'nginx-external-alb' - nginx.ingress.kubernetes.io/service-upstream: 'true' - hosts: - - host: 'unicorn-app.staging01.devland.is' - paths: - - '/' - namespace: 'unicorn-app' - podDisruptionBudget: - maxUnavailable: 1 - podSecurityContext: - fsGroup: 65534 - pvcs: [] - replicaCount: - default: 1 - max: 3 - min: 1 - resources: - limits: - cpu: '200m' - memory: '256Mi' - requests: - cpu: '50m' - memory: '128Mi' - secrets: - CONFIGCAT_SDK_KEY: '/k8s/configcat/CONFIGCAT_SDK_KEY' - securityContext: - allowPrivilegeEscalation: false - privileged: false - serviceAccount: - annotations: - eks.amazonaws.com/role-arn: 'arn:aws:iam::261174024191:role/unicorn-app' - create: true - name: 'unicorn-app' user-notification: args: - '--no-experimental-fetch' diff --git a/charts/services/unicorn-app/values.dev.yaml b/charts/services/unicorn-app/values.dev.yaml deleted file mode 100644 index 345b91dd5ddb..000000000000 --- a/charts/services/unicorn-app/values.dev.yaml +++ /dev/null @@ -1,81 +0,0 @@ -##################################################################### -# -# Do not edit this file manually, it is automatically generated. -# Run "yarn charts" instead. -# -##################################################################### - -global: - env: - AUDIT_GROUP_NAME: '/island-is/audit-log' - AWS_REGION: 'eu-west-1' - NPM_CONFIG_UPDATE_NOTIFIER: 'false' - PORT: '3333' - name: 'dev' - initContainer: - env: - AWS_REGION: 'eu-west-1' - NPM_CONFIG_UPDATE_NOTIFIER: 'false' -name: 'unicorn-app' -enabled: true -env: - LOG_LEVEL: 'info' - NODE_OPTIONS: '--max-old-space-size=230 -r dd-trace/init' - SERVERSIDE_FEATURES_ON: '' -grantNamespaces: [] -grantNamespacesEnabled: false -healthCheck: - liveness: - initialDelaySeconds: 3 - path: '/liveness' - timeoutSeconds: 3 - readiness: - initialDelaySeconds: 3 - path: '/readiness' - timeoutSeconds: 3 -hpa: - scaling: - metric: - cpuAverageUtilization: 90 - nginxRequestsIrate: 5 - replicas: - max: 10 - min: 1 -image: - repository: '821090935708.dkr.ecr.eu-west-1.amazonaws.com/unicorn-app' -ingress: - primary-alb: - annotations: - kubernetes.io/ingress.class: 'nginx-external-alb' - nginx.ingress.kubernetes.io/service-upstream: 'true' - hosts: - - host: 'unicorn-app.dev01.devland.is' - paths: - - '/' -namespace: 'unicorn-app' -podDisruptionBudget: - maxUnavailable: 1 -podSecurityContext: - fsGroup: 65534 -pvcs: [] -replicaCount: - default: 1 - max: 10 - min: 1 -resources: - limits: - cpu: '200m' - memory: '256Mi' - requests: - cpu: '50m' - memory: '128Mi' -secrets: - CONFIGCAT_SDK_KEY: '/k8s/configcat/CONFIGCAT_SDK_KEY' -securityContext: - allowPrivilegeEscalation: false - privileged: false -serviceAccount: - annotations: - eks.amazonaws.com/role-arn: 'arn:aws:iam::013313053092:role/unicorn-app' - create: true - name: 'unicorn-app' diff --git a/charts/services/unicorn-app/values.prod.yaml b/charts/services/unicorn-app/values.prod.yaml deleted file mode 100644 index f9782daf09fc..000000000000 --- a/charts/services/unicorn-app/values.prod.yaml +++ /dev/null @@ -1,81 +0,0 @@ -##################################################################### -# -# Do not edit this file manually, it is automatically generated. -# Run "yarn charts" instead. -# -##################################################################### - -global: - env: - AUDIT_GROUP_NAME: '/island-is/audit-log' - AWS_REGION: 'eu-west-1' - NPM_CONFIG_UPDATE_NOTIFIER: 'false' - PORT: '3333' - name: 'prod' - initContainer: - env: - AWS_REGION: 'eu-west-1' - NPM_CONFIG_UPDATE_NOTIFIER: 'false' -name: 'unicorn-app' -enabled: true -env: - LOG_LEVEL: 'info' - NODE_OPTIONS: '--max-old-space-size=230 -r dd-trace/init' - SERVERSIDE_FEATURES_ON: 'driving-license-use-v1-endpoint-for-v2-comms' -grantNamespaces: [] -grantNamespacesEnabled: false -healthCheck: - liveness: - initialDelaySeconds: 3 - path: '/liveness' - timeoutSeconds: 3 - readiness: - initialDelaySeconds: 3 - path: '/readiness' - timeoutSeconds: 3 -hpa: - scaling: - metric: - cpuAverageUtilization: 90 - nginxRequestsIrate: 5 - replicas: - max: 10 - min: 1 -image: - repository: '821090935708.dkr.ecr.eu-west-1.amazonaws.com/unicorn-app' -ingress: - primary-alb: - annotations: - kubernetes.io/ingress.class: 'nginx-external-alb' - nginx.ingress.kubernetes.io/service-upstream: 'true' - hosts: - - host: 'unicorn-app.island.is' - paths: - - '/' -namespace: 'unicorn-app' -podDisruptionBudget: - maxUnavailable: 1 -podSecurityContext: - fsGroup: 65534 -pvcs: [] -replicaCount: - default: 1 - max: 10 - min: 1 -resources: - limits: - cpu: '200m' - memory: '256Mi' - requests: - cpu: '50m' - memory: '128Mi' -secrets: - CONFIGCAT_SDK_KEY: '/k8s/configcat/CONFIGCAT_SDK_KEY' -securityContext: - allowPrivilegeEscalation: false - privileged: false -serviceAccount: - annotations: - eks.amazonaws.com/role-arn: 'arn:aws:iam::251502586493:role/unicorn-app' - create: true - name: 'unicorn-app' diff --git a/charts/services/unicorn-app/values.staging.yaml b/charts/services/unicorn-app/values.staging.yaml deleted file mode 100644 index 50960c303bef..000000000000 --- a/charts/services/unicorn-app/values.staging.yaml +++ /dev/null @@ -1,81 +0,0 @@ -##################################################################### -# -# Do not edit this file manually, it is automatically generated. -# Run "yarn charts" instead. -# -##################################################################### - -global: - env: - AUDIT_GROUP_NAME: '/island-is/audit-log' - AWS_REGION: 'eu-west-1' - NPM_CONFIG_UPDATE_NOTIFIER: 'false' - PORT: '3333' - name: 'staging' - initContainer: - env: - AWS_REGION: 'eu-west-1' - NPM_CONFIG_UPDATE_NOTIFIER: 'false' -name: 'unicorn-app' -enabled: true -env: - LOG_LEVEL: 'info' - NODE_OPTIONS: '--max-old-space-size=230 -r dd-trace/init' - SERVERSIDE_FEATURES_ON: '' -grantNamespaces: [] -grantNamespacesEnabled: false -healthCheck: - liveness: - initialDelaySeconds: 3 - path: '/liveness' - timeoutSeconds: 3 - readiness: - initialDelaySeconds: 3 - path: '/readiness' - timeoutSeconds: 3 -hpa: - scaling: - metric: - cpuAverageUtilization: 90 - nginxRequestsIrate: 5 - replicas: - max: 3 - min: 1 -image: - repository: '821090935708.dkr.ecr.eu-west-1.amazonaws.com/unicorn-app' -ingress: - primary-alb: - annotations: - kubernetes.io/ingress.class: 'nginx-external-alb' - nginx.ingress.kubernetes.io/service-upstream: 'true' - hosts: - - host: 'unicorn-app.staging01.devland.is' - paths: - - '/' -namespace: 'unicorn-app' -podDisruptionBudget: - maxUnavailable: 1 -podSecurityContext: - fsGroup: 65534 -pvcs: [] -replicaCount: - default: 1 - max: 3 - min: 1 -resources: - limits: - cpu: '200m' - memory: '256Mi' - requests: - cpu: '50m' - memory: '128Mi' -secrets: - CONFIGCAT_SDK_KEY: '/k8s/configcat/CONFIGCAT_SDK_KEY' -securityContext: - allowPrivilegeEscalation: false - privileged: false -serviceAccount: - annotations: - eks.amazonaws.com/role-arn: 'arn:aws:iam::261174024191:role/unicorn-app' - create: true - name: 'unicorn-app' diff --git a/infra/src/uber-charts/islandis.ts b/infra/src/uber-charts/islandis.ts index db2d1ecc6940..569b921bc2bf 100644 --- a/infra/src/uber-charts/islandis.ts +++ b/infra/src/uber-charts/islandis.ts @@ -62,7 +62,6 @@ import { } from '../../../apps/services/sessions/infra/sessions' import { serviceSetup as authAdminApiSetup } from '../../../apps/services/auth/admin-api/infra/auth-admin-api' -import { serviceSetup as unicornAppSetup } from '../../../apps/unicorn-app/infra/infra' import { EnvironmentServices } from '.././dsl/types/charts' import { ServiceBuilder } from '../dsl/dsl' @@ -147,8 +146,6 @@ const userNotificationWorkerService = userNotificationWorkerSetup({ const userNotificationCleanupWorkerService = userNotificationCleanUpWorkerSetup() -const unicornApp = unicornAppSetup() - const githubActionsCache = githubActionsCacheSetup() const externalContractsTests = externalContractsTestsSetup() @@ -189,7 +186,6 @@ export const Services: EnvironmentServices = { contentfulApps, contentfulEntryTagger, bffAdminPortalService, - unicornApp, bffServicePortalService, ], staging: [ @@ -225,7 +221,6 @@ export const Services: EnvironmentServices = { universityGatewayService, universityGatewayWorker, bffAdminPortalService, - unicornApp, bffServicePortalService, ], dev: [ @@ -265,7 +260,6 @@ export const Services: EnvironmentServices = { universityGatewayService, universityGatewayWorker, bffAdminPortalService, - unicornApp, bffServicePortalService, ], } diff --git a/scripts/ci/create-release.mjs b/scripts/ci/create-release.mjs deleted file mode 100644 index 85d818b14889..000000000000 --- a/scripts/ci/create-release.mjs +++ /dev/null @@ -1,35 +0,0 @@ -import { Octokit } from '@octokit/rest' - -const { GITHUB_TOKEN, GITHUB_REPOSITORY, GITHUB_REF } = process.env -const octokit = new Octokit({ auth: GITHUB_TOKEN }) -const [owner, repo] = GITHUB_REPOSITORY?.split('/') || [] - -const arg = process.argv.slice(2) - -const { data: pullRequest } = await octokit.rest.pulls.get({ - owner: owner, - repo: repo, - pull_number: arg[0], -}) - -const SHA = pullRequest.head.sha -// This is a temporary commit that is created behind the scenes for -// the test merge that validated no conflicts exist with the base branch. -// It is not committed to the repository. -// After the PR is merged, this value instead represents the SHA of the merge commit -octokit.rest.repos - .createRelease({ - owner: owner, - repo: repo, - target_commitish: SHA, - tag_name: 'SomeTag', - name: 'Test 123', - generate_release_notes: true, - }) - .then(({ data }) => { - console.log(data) - }) - .catch((error) => { - console.log(error) - process.exit(1) - }) diff --git a/scripts/ci/get-last-release.mjs b/scripts/ci/get-last-release.mjs deleted file mode 100644 index 03feaf63f4d0..000000000000 --- a/scripts/ci/get-last-release.mjs +++ /dev/null @@ -1,17 +0,0 @@ -const releases = process.argv - .slice(2) - .map((release) => release.replace('origin/release/', '')) - .filter((release) => /^\d+\.\d+\.\d+$/.test(release)) - .sort((a, b) => { - const [aMajor, aMinor, aPatch] = a.split('.').map(Number) - const [bMajor, bMinor, bPatch] = b.split('.').map(Number) - - if (aMajor !== bMajor) { - return bMajor - aMajor - } else if (aMinor !== bMinor) { - return bMinor - aMinor - } else { - return bPatch - aPatch - } - }) -console.log(releases[0]) diff --git a/scripts/ci/unicorn-utils.mjs b/scripts/ci/unicorn-utils.mjs deleted file mode 100644 index 22078efe215d..000000000000 --- a/scripts/ci/unicorn-utils.mjs +++ /dev/null @@ -1,17 +0,0 @@ -import { execSync } from 'child_process' -import { workspaceRoot } from '@nx/devkit' - -const unicornApps = ['unicorn-app'] - -const arg = JSON.parse(process.argv.slice(2)) -try { - const affected = JSON.parse( - execSync( - `cd ${workspaceRoot} && yarn nx show projects --affected --base ${arg.baseBranch} --json | jq -r`, - ).toString(), - ) - console.log(affected.some((item) => unicornApps.includes(item))) -} catch (e) { - console.error(e.message) - process.exit(1) -} diff --git a/tsconfig.base.json b/tsconfig.base.json index 32541263a4c8..7be0f1c28845 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -1126,7 +1126,6 @@ "@island.is/testing/e2e": ["libs/testing/e2e/src/index.ts"], "@island.is/testing/fixtures": ["libs/testing/fixtures/src/index.ts"], "@island.is/testing/nest": ["libs/testing/nest/src/index.ts"], - "@island.is/unicorn-app/*": ["apps/unicorn-app/*"], "@island.is/university-gateway": ["libs/university-gateway/src/index.ts"], "@island.is/user-monitoring": ["libs/user-monitoring/src/index.ts"], "@island.is/web/*": ["apps/web/*"], From 95f602a03618e3388d245e086c05fb75fe2afda6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81sd=C3=ADs=20Erna=20Gu=C3=B0mundsd=C3=B3ttir?= Date: Fri, 20 Dec 2024 10:52:10 +0000 Subject: [PATCH 18/19] feat(web): Grant - Mobile view and sidemenu refactor (#17214) * WIP action card & alert banner for grant * feat: add text from cms * fix: UI tweas and starting mobile menu * feat: mobile view * feat: add disabled to buttons * fix: format * fix: icon minwidth --------- Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- apps/web/screens/Grants/Grant/Grant.css.ts | 7 + apps/web/screens/Grants/Grant/Grant.tsx | 68 +++++-- .../web/screens/Grants/Grant/GrantSidebar.tsx | 185 ------------------ .../Grants/Grant/GrantSidebar/DetailPanel.tsx | 116 +++++++++++ .../Grants/Grant/GrantSidebar/ExtraPanel.tsx | 80 ++++++++ .../Grant/GrantSidebar/GrantSidebar.tsx | 73 +++++++ .../Grants/Grant/GrantSidebar/PanelLine.tsx | 15 ++ apps/web/screens/Grants/messages.ts | 6 +- apps/web/screens/Grants/utils.ts | 4 +- .../core/src/lib/Breadcrumbs/Breadcrumbs.tsx | 1 + .../src/lib/InfoCardGrid/DetailedInfoCard.tsx | 3 +- .../core/src/lib/InfoCardGrid/InfoCard.css.ts | 8 +- 12 files changed, 362 insertions(+), 204 deletions(-) create mode 100644 apps/web/screens/Grants/Grant/Grant.css.ts delete mode 100644 apps/web/screens/Grants/Grant/GrantSidebar.tsx create mode 100644 apps/web/screens/Grants/Grant/GrantSidebar/DetailPanel.tsx create mode 100644 apps/web/screens/Grants/Grant/GrantSidebar/ExtraPanel.tsx create mode 100644 apps/web/screens/Grants/Grant/GrantSidebar/GrantSidebar.tsx create mode 100644 apps/web/screens/Grants/Grant/GrantSidebar/PanelLine.tsx diff --git a/apps/web/screens/Grants/Grant/Grant.css.ts b/apps/web/screens/Grants/Grant/Grant.css.ts new file mode 100644 index 000000000000..6c18ae3d5e8f --- /dev/null +++ b/apps/web/screens/Grants/Grant/Grant.css.ts @@ -0,0 +1,7 @@ +import { globalStyle, style } from '@vanilla-extract/css' + +export const noUnderline = style({}) + +globalStyle(`${noUnderline} span`, { + boxShadow: 'none', +}) diff --git a/apps/web/screens/Grants/Grant/Grant.tsx b/apps/web/screens/Grants/Grant/Grant.tsx index 03184ecdfccc..27bcc2ba1020 100644 --- a/apps/web/screens/Grants/Grant/Grant.tsx +++ b/apps/web/screens/Grants/Grant/Grant.tsx @@ -1,3 +1,4 @@ +import { useMemo } from 'react' import { useIntl } from 'react-intl' import NextLink from 'next/link' import { useRouter } from 'next/router' @@ -8,11 +9,12 @@ import { Box, Breadcrumbs, Divider, + Hidden, Stack, Text, } from '@island.is/island-ui/core' import { Locale } from '@island.is/shared/types' -import { GrantWrapper } from '@island.is/web/components' +import { GrantWrapper, InstitutionPanel } from '@island.is/web/components' import { ContentLanguage, CustomPageUniqueIdentifier, @@ -32,7 +34,10 @@ import { import SidebarLayout from '../../Layouts/SidebarLayout' import { GET_GRANT_QUERY } from '../../queries' import { m } from '../messages' -import { GrantSidebar } from './GrantSidebar' +import { generateStatusTag, parseStatus } from '../utils' +import DetailPanel from './GrantSidebar/DetailPanel' +import ExtraPanel from './GrantSidebar/ExtraPanel' +import { GrantSidebar } from './GrantSidebar/GrantSidebar' const GrantSinglePage: CustomScreen = ({ grant, locale }) => { const { formatMessage } = useIntl() @@ -67,6 +72,11 @@ const GrantSinglePage: CustomScreen = ({ grant, locale }) => { }, ] + const status = useMemo( + () => (grant ? parseStatus(grant, formatMessage, locale) : null), + [grant, formatMessage, locale], + ) + if (!grant) { return null } @@ -100,21 +110,37 @@ const GrantSinglePage: CustomScreen = ({ grant, locale }) => { {grant.description} - router.push(grant.applicationUrl?.slug ?? ''), - icon: 'open', - iconType: 'outline', - }} - /> + + + + + router.push(grant.applicationUrl?.slug ?? ''), + icon: 'open', + iconType: 'outline', + }} + /> + {grant.specialEmphasis?.length ? ( <> + {status?.note && {status.note}} {webRichText( grant.specialEmphasis as SliceType[], undefined, @@ -164,6 +190,20 @@ const GrantSinglePage: CustomScreen = ({ grant, locale }) => { )} ) : undefined} + + + + + + diff --git a/apps/web/screens/Grants/Grant/GrantSidebar.tsx b/apps/web/screens/Grants/Grant/GrantSidebar.tsx deleted file mode 100644 index 5a1941ee5b2a..000000000000 --- a/apps/web/screens/Grants/Grant/GrantSidebar.tsx +++ /dev/null @@ -1,185 +0,0 @@ -import { useMemo } from 'react' -import { useIntl } from 'react-intl' - -import { - Box, - BoxProps, - Button, - LinkV2, - Stack, - Text, -} from '@island.is/island-ui/core' -import { Locale } from '@island.is/shared/types' -import { isDefined } from '@island.is/shared/utils' -import { InstitutionPanel } from '@island.is/web/components' -import { Grant } from '@island.is/web/graphql/schema' -import { LinkType, useLinkResolver } from '@island.is/web/hooks' - -import { m } from '../messages' -import { generateStatusTag, parseStatus } from '../utils' - -interface Props { - grant: Grant - locale: Locale -} - -const generateLine = (heading: string, content?: React.ReactNode) => { - if (!content) { - return null - } - return ( - - - {heading} - - {content} - - ) -} - -const generateSidebarPanel = ( - data: Array, - background: BoxProps['background'], -) => { - if (!data.length) { - return undefined - } - return ( - - {data} - - ) -} - -export const GrantSidebar = ({ grant, locale }: Props) => { - const { linkResolver } = useLinkResolver() - const { formatMessage } = useIntl() - - const status = useMemo( - () => parseStatus(grant, formatMessage, locale), - [grant, formatMessage, locale], - ) - - const detailPanelData = useMemo( - () => - [ - generateLine( - formatMessage(m.single.fund), - grant?.fund?.link?.slug ? ( - - - {grant.fund.title} - - - ) : undefined, - ), - generateLine( - formatMessage(m.single.category), - grant?.categoryTags ? ( - - {grant.categoryTags - .map((ct) => ct.title) - .filter(isDefined) - .join(', ')} - - ) : undefined, - ), - generateLine( - formatMessage(m.single.type), - grant?.typeTag?.title ? ( - {grant.typeTag?.title} - ) : undefined, - ), - generateLine( - formatMessage(m.single.status), - grant?.status ? ( - - { - generateStatusTag(status.applicationStatus, formatMessage) - ?.label - } - - ) : undefined, - ), - ].filter(isDefined) ?? [], - [grant, formatMessage, linkResolver, status], - ) - - const filesPanelData = useMemo( - () => - grant.files - ?.map((f, index) => { - if (!f.url) { - return null - } - return ( - - - - ) - }) - .filter(isDefined) ?? [], - [grant.files], - ) - - const supportLinksPanelData = useMemo( - () => - grant.supportLinks - ?.map((link) => { - if (!link.url || !link.text || !link.id) { - return null - } - return ( - - - - ) - }) - .filter(isDefined) ?? [], - [grant.supportLinks], - ) - - return ( - - - {generateSidebarPanel(detailPanelData, 'blue100')} - {generateSidebarPanel(filesPanelData, 'red100')} - {generateSidebarPanel(supportLinksPanelData, 'purple100')} - - ) -} diff --git a/apps/web/screens/Grants/Grant/GrantSidebar/DetailPanel.tsx b/apps/web/screens/Grants/Grant/GrantSidebar/DetailPanel.tsx new file mode 100644 index 000000000000..f6e78a0a6061 --- /dev/null +++ b/apps/web/screens/Grants/Grant/GrantSidebar/DetailPanel.tsx @@ -0,0 +1,116 @@ +import { useMemo } from 'react' +import { useIntl } from 'react-intl' +import { useRouter } from 'next/router' + +import { Box, Button, LinkV2, Stack, Text } from '@island.is/island-ui/core' +import { Locale } from '@island.is/shared/types' +import { isDefined } from '@island.is/shared/utils' +import { Grant } from '@island.is/web/graphql/schema' +import { LinkType, useLinkResolver } from '@island.is/web/hooks' + +import { m } from '../../messages' +import { generateStatusTag, parseStatus } from '../../utils' +import { generateLine } from './PanelLine' + +interface DetailPanelProps { + grant: Grant + locale: Locale + button?: boolean +} + +const DetailPanel: React.FC = ({ + grant, + locale, + button = false, +}) => { + const { linkResolver } = useLinkResolver() + const { formatMessage } = useIntl() + const router = useRouter() + + const status = useMemo( + () => parseStatus(grant, formatMessage, locale), + [grant, formatMessage, locale], + ) + + const detailPanelData = useMemo( + () => + [ + generateLine( + formatMessage(m.single.fund), + grant?.fund?.link?.slug ? ( + + + {grant.fund.title} + + + ) : undefined, + ), + generateLine( + formatMessage(m.single.status), + grant?.status ? ( + + { + generateStatusTag(status.applicationStatus, formatMessage) + ?.label + } + + ) : undefined, + ), + generateLine( + formatMessage(m.single.deadline), + status.deadlineStatus ? ( + {status.deadlineStatus} + ) : undefined, + ), + generateLine( + formatMessage(m.single.category), + grant?.categoryTags ? ( + + {grant.categoryTags + .map((ct) => ct.title) + .filter(isDefined) + .join(', ')} + + ) : undefined, + ), + generateLine( + formatMessage(m.single.type), + grant?.typeTag?.title ? ( + {grant.typeTag?.title} + ) : undefined, + ), + ].filter(isDefined) ?? [], + [grant, formatMessage, linkResolver, status], + ) + return ( + + {detailPanelData} + {button && ( + + + + )} + + ) +} + +export default DetailPanel diff --git a/apps/web/screens/Grants/Grant/GrantSidebar/ExtraPanel.tsx b/apps/web/screens/Grants/Grant/GrantSidebar/ExtraPanel.tsx new file mode 100644 index 000000000000..3719b83d0ffd --- /dev/null +++ b/apps/web/screens/Grants/Grant/GrantSidebar/ExtraPanel.tsx @@ -0,0 +1,80 @@ +import React, { useMemo } from 'react' + +import { Box, Button, LinkV2, Stack } from '@island.is/island-ui/core' +import { isDefined } from '@island.is/shared/utils' +import { Grant } from '@island.is/web/graphql/schema' + +interface ExtraPanelProps { + grant: Grant +} + +const ExtraPanel: React.FC = ({ grant }) => { + const supportLinksPanelData = useMemo( + () => + grant.supportLinks + ?.map((link) => { + if (!link.url || !link.text || !link.id) { + return null + } + return ( + + + + ) + }) + .filter(isDefined) ?? [], + [grant.supportLinks], + ) + + const filesPanelData = useMemo( + () => + grant.files + ?.map((f, index) => { + if (!f.url) { + return null + } + return ( + + + + ) + }) + .concat(supportLinksPanelData) + .filter(isDefined) ?? [], + [grant.files, supportLinksPanelData], + ) + + return ( + + {filesPanelData} + + ) +} + +export default ExtraPanel diff --git a/apps/web/screens/Grants/Grant/GrantSidebar/GrantSidebar.tsx b/apps/web/screens/Grants/Grant/GrantSidebar/GrantSidebar.tsx new file mode 100644 index 000000000000..2579aab80c1a --- /dev/null +++ b/apps/web/screens/Grants/Grant/GrantSidebar/GrantSidebar.tsx @@ -0,0 +1,73 @@ +import { useIntl } from 'react-intl' + +import { Box, BoxProps, Button, LinkV2, Stack } from '@island.is/island-ui/core' +import { Locale } from '@island.is/shared/types' +import { InstitutionPanel } from '@island.is/web/components' +import { Grant } from '@island.is/web/graphql/schema' + +import { m } from '../../messages' +import DetailPanel from './DetailPanel' +import ExtraPanel from './ExtraPanel' +import * as styles from '../Grant.css' + +interface Props { + grant: Grant + locale: Locale +} + +export const generateSidebarPanel = ( + data: Array, + background: BoxProps['background'], +) => { + if (!data.length) { + return undefined + } + return ( + + {data} + + ) +} + +export const GrantSidebar = ({ grant, locale }: Props) => { + const { formatMessage } = useIntl() + + const goBackToDashboard = () => { + return ( + + + + + + ) + } + return ( + <> + {goBackToDashboard()} + + + + + + + ) +} diff --git a/apps/web/screens/Grants/Grant/GrantSidebar/PanelLine.tsx b/apps/web/screens/Grants/Grant/GrantSidebar/PanelLine.tsx new file mode 100644 index 000000000000..ad3aaba858fc --- /dev/null +++ b/apps/web/screens/Grants/Grant/GrantSidebar/PanelLine.tsx @@ -0,0 +1,15 @@ +import { Box, Text } from '@island.is/island-ui/core' + +export const generateLine = (heading: string, content?: React.ReactNode) => { + if (!content) { + return null + } + return ( + + + {heading} + + {content} + + ) +} diff --git a/apps/web/screens/Grants/messages.ts b/apps/web/screens/Grants/messages.ts index d6a5ce251693..ff6e5c75d6b2 100644 --- a/apps/web/screens/Grants/messages.ts +++ b/apps/web/screens/Grants/messages.ts @@ -6,6 +6,10 @@ export const m = { id: 'web.grants:general.seeMore', defaultMessage: 'Skoða nánar', }, + goBack: { + id: 'web.grants:general.goBack', + defaultMessage: 'Til baka', + }, displayGrid: { id: 'web.grants:general.displayGrid', defaultMessage: 'Sýna sem spjöld', @@ -82,7 +86,7 @@ export const m = { }, applicationWasOpenToAndWith: { id: 'web.grants:search.applicationWasOpenToAndWith', - defaultMessage: 'Frestur var til og með {arg}', + defaultMessage: 'Frestur var til {arg}', }, applicationAlwaysOpen: { id: 'web.grants:search.applicationAlwaysOpen', diff --git a/apps/web/screens/Grants/utils.ts b/apps/web/screens/Grants/utils.ts index dbc65ab84978..97eb9bf5d7a5 100644 --- a/apps/web/screens/Grants/utils.ts +++ b/apps/web/screens/Grants/utils.ts @@ -17,7 +17,7 @@ interface Status { const formatDate = ( date: Date, locale: Locale, - stringFormat = 'dd.MMMM yyyy', + stringFormat = 'dd. MMMM yyyy', ): string | undefined => { try { return format(date, stringFormat, { @@ -100,7 +100,7 @@ export const parseStatus = ( } case GrantStatus.Open: { const date = grant.dateTo - ? formatDate(new Date(grant.dateTo), locale, 'dd.MMMM.') + ? formatDate(new Date(grant.dateTo), locale, 'dd. MMMM.') : undefined return { applicationStatus: 'open', diff --git a/libs/island-ui/core/src/lib/Breadcrumbs/Breadcrumbs.tsx b/libs/island-ui/core/src/lib/Breadcrumbs/Breadcrumbs.tsx index 4c6adaac0d1e..28952dcef45c 100644 --- a/libs/island-ui/core/src/lib/Breadcrumbs/Breadcrumbs.tsx +++ b/libs/island-ui/core/src/lib/Breadcrumbs/Breadcrumbs.tsx @@ -38,6 +38,7 @@ export const Breadcrumbs: FC> = ({ {d.text} diff --git a/libs/island-ui/core/src/lib/InfoCardGrid/InfoCard.css.ts b/libs/island-ui/core/src/lib/InfoCardGrid/InfoCard.css.ts index 4c27d84947de..d010a5ddeecc 100644 --- a/libs/island-ui/core/src/lib/InfoCardGrid/InfoCard.css.ts +++ b/libs/island-ui/core/src/lib/InfoCardGrid/InfoCard.css.ts @@ -1,4 +1,4 @@ -import { style } from '@vanilla-extract/css' +import { globalStyle, style } from '@vanilla-extract/css' export const infoCardSmall = style({ maxWidth: 310, @@ -18,3 +18,9 @@ export const infoCardWide = style({ export const wideTitleBox = style({ flexGrow: 2, }) + +export const iconBox = style({}) + +globalStyle(`${iconBox} > svg`, { + minWidth: 24, +}) From 93643238a02749c90e8c3def70e67e518535b0b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gunnlaugur=20Gu=C3=B0mundsson?= <34029342+GunnlaugurG@users.noreply.github.com> Date: Fri, 20 Dec 2024 11:18:50 +0000 Subject: [PATCH 19/19] fix(auth-delegations): Fix missing domains for legal representative delegations (#17304) * attempt to fix delegation bugs * add filterOutForDelegationType to utils func * Utils func changes --------- Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../delegations/delegation-scope.service.ts | 15 +++++++++- .../delegations-incoming-custom.service.ts | 21 ++++---------- .../utils/filterByScopeCustomScopeRule.ts | 21 ++++++++++++++ .../resources/delegation-resources.service.ts | 28 ++++++++++++++++--- .../src/lib/resources/resources.module.ts | 2 ++ 5 files changed, 67 insertions(+), 20 deletions(-) create mode 100644 libs/auth-api-lib/src/lib/delegations/utils/filterByScopeCustomScopeRule.ts diff --git a/libs/auth-api-lib/src/lib/delegations/delegation-scope.service.ts b/libs/auth-api-lib/src/lib/delegations/delegation-scope.service.ts index bb74ed0a4d6e..701fa0785810 100644 --- a/libs/auth-api-lib/src/lib/delegations/delegation-scope.service.ts +++ b/libs/auth-api-lib/src/lib/delegations/delegation-scope.service.ts @@ -27,8 +27,10 @@ import { DelegationDelegationType } from './models/delegation-delegation-type.mo import { DelegationScope } from './models/delegation-scope.model' import { DelegationTypeModel } from './models/delegation-type.model' import { Delegation } from './models/delegation.model' +import { ApiScopeInfo } from './delegations-incoming.service' import type { User } from '@island.is/auth-nest-tools' +import filterByCustomScopeRule from './utils/filterByScopeCustomScopeRule' @Injectable() export class DelegationScopeService { @@ -222,7 +224,18 @@ export class DelegationScopeService { }, ], }) - .then((apiScopes) => apiScopes.map((apiScope) => apiScope.name)) + .then((apiScopes) => + apiScopes + .filter((scope) => + // Remove scopes that are not allowed for the delegation type + filterByCustomScopeRule( + scope, + [AuthDelegationType.GeneralMandate], + this.delegationConfig.customScopeRules, + ), + ) + .map((apiScope) => apiScope.name), + ) } private async findAllNationalRegistryScopes(): Promise { diff --git a/libs/auth-api-lib/src/lib/delegations/delegations-incoming-custom.service.ts b/libs/auth-api-lib/src/lib/delegations/delegations-incoming-custom.service.ts index e11e509095bd..2eac4bf93fe7 100644 --- a/libs/auth-api-lib/src/lib/delegations/delegations-incoming-custom.service.ts +++ b/libs/auth-api-lib/src/lib/delegations/delegations-incoming-custom.service.ts @@ -26,6 +26,7 @@ import { Delegation } from './models/delegation.model' import { NationalRegistryV3FeatureService } from './national-registry-v3-feature.service' import { DelegationValidity } from './types/delegationValidity' import { getScopeValidityWhereClause } from './utils/scopes' +import filterByCustomScopeRule from './utils/filterByScopeCustomScopeRule' type FindAllValidIncomingOptions = { nationalId: string @@ -182,20 +183,6 @@ export class DelegationsIncomingCustomService { }) } - private filterByCustomScopeRule(scope: ApiScopeInfo) { - const foundCSR = this.delegationConfig.customScopeRules.find( - (csr) => csr.scopeName === scope.name, - ) - - if (!foundCSR) { - return true - } - - return foundCSR.onlyForDelegationType.includes( - AuthDelegationType.GeneralMandate, - ) - } - /** * Finds all companies that have a general mandate for the user. * @param user @@ -233,7 +220,11 @@ export class DelegationsIncomingCustomService { const customApiScopes = clientAllowedApiScopes.filter( (s) => !s.isAccessControlled && - this.filterByCustomScopeRule(s) && + filterByCustomScopeRule( + s, + [AuthDelegationType.GeneralMandate], + this.delegationConfig.customScopeRules, + ) && s.supportedDelegationTypes?.some((dt) => supportedDelegationTypes.includes( dt.delegationType as AuthDelegationType, diff --git a/libs/auth-api-lib/src/lib/delegations/utils/filterByScopeCustomScopeRule.ts b/libs/auth-api-lib/src/lib/delegations/utils/filterByScopeCustomScopeRule.ts new file mode 100644 index 000000000000..87fbd1d7adff --- /dev/null +++ b/libs/auth-api-lib/src/lib/delegations/utils/filterByScopeCustomScopeRule.ts @@ -0,0 +1,21 @@ +import { AuthDelegationType } from '@island.is/shared/types' +import { ApiScopeInfo } from '../delegations-incoming.service' + +export default function filterByCustomScopeRule( + scope: ApiScopeInfo, + filterOutForDelegationType: AuthDelegationType[], + customScopeRules: { + scopeName: string + onlyForDelegationType: string[] + }[], +): boolean { + const foundCSR = customScopeRules.find((csr) => csr.scopeName === scope.name) + + if (!foundCSR) { + return true + } + + return foundCSR.onlyForDelegationType.some((type) => + filterOutForDelegationType.includes(type as AuthDelegationType), + ) +} diff --git a/libs/auth-api-lib/src/lib/resources/delegation-resources.service.ts b/libs/auth-api-lib/src/lib/resources/delegation-resources.service.ts index 3f5b80d434dd..7de2870f58bd 100644 --- a/libs/auth-api-lib/src/lib/resources/delegation-resources.service.ts +++ b/libs/auth-api-lib/src/lib/resources/delegation-resources.service.ts @@ -25,6 +25,7 @@ import { mapToScopeTree } from './utils/scope-tree.mapper' import type { Attributes, WhereOptions } from 'sequelize' import type { ConfigType } from '@island.is/nest/config' +import { ApiScopeDelegationType } from './models/api-scope-delegation-type.model' type DelegationConfigType = ConfigType type ScopeRule = DelegationConfigType['customScopeRules'] extends Array< @@ -42,6 +43,8 @@ export class DelegationResourcesService { private domainModel: typeof Domain, @InjectModel(DelegationScope) private delegationScopeModel: typeof DelegationScope, + @InjectModel(ApiScopeDelegationType) + private apiScopeDelegationTypeModel: typeof ApiScopeDelegationType, private resourceTranslationService: ResourceTranslationService, @Inject(DelegationConfig.KEY) private delegationConfig: ConfigType, @@ -304,20 +307,37 @@ export class DelegationResourcesService { } private async delegationTypeFilter(user: User, prefix?: string) { - if (!user.delegationType || !user.actor) { + if (!user.delegationType) { return [] } // We currently only support access control for company (delegation) actors. // Actors for individuals should not have the scope required to reach this // point, but we assert it just to be safe. + // EDIT: This is no longer true, as we now support LegalRepresentative delegations for individuals. if (!isCompany(user.nationalId)) { - throw new ForbiddenException( - 'Actors for individuals should not be able to manage delegations.', - ) + if ( + !user.delegationType.includes(AuthDelegationType.LegalRepresentative) + ) { + throw new ForbiddenException( + 'Actors for individuals should not be able to manage delegations.', + ) + } } const delegationOr: Array> = [] + if (user.delegationType.includes(AuthDelegationType.LegalRepresentative)) { + const scopes = await this.apiScopeDelegationTypeModel.findAll({ + attributes: ['apiScopeName'], + where: { + delegationType: AuthDelegationType.LegalRepresentative, + }, + }) + + delegationOr.push({ + [col(prefix, 'name')]: scopes.map((scope) => scope.apiScopeName), + }) + } if (user.delegationType.includes(AuthDelegationType.ProcurationHolder)) { delegationOr.push({ [col(prefix, 'grantToProcuringHolders')]: true }) } diff --git a/libs/auth-api-lib/src/lib/resources/resources.module.ts b/libs/auth-api-lib/src/lib/resources/resources.module.ts index c7198b3ecef4..541380575a33 100644 --- a/libs/auth-api-lib/src/lib/resources/resources.module.ts +++ b/libs/auth-api-lib/src/lib/resources/resources.module.ts @@ -26,6 +26,7 @@ import { ResourceTranslationService } from './resource-translation.service' import { ResourcesService } from './resources.service' import { ScopeService } from './scope.service' import { TenantsService } from './tenants.service' +import { ApiScopeDelegationType } from './models/api-scope-delegation-type.model' @Module({ imports: [ @@ -39,6 +40,7 @@ import { TenantsService } from './tenants.service' ApiScopeUser, ApiScopeUserAccess, ApiResourceScope, + ApiScopeDelegationType, IdentityResourceUserClaim, ApiScopeUserClaim, ApiResourceUserClaim,