diff --git a/apps/ungdomsytelse-deltaker/api-mock/mock-server.cjs b/apps/ungdomsytelse-deltaker/api-mock/mock-server.cjs index ab8c3658ce..cdcd12d18e 100644 --- a/apps/ungdomsytelse-deltaker/api-mock/mock-server.cjs +++ b/apps/ungdomsytelse-deltaker/api-mock/mock-server.cjs @@ -57,6 +57,7 @@ const mockPath = `${__dirname}/data`; const soker = 'søker1'; const søkerFileName = `søker-mock.json`; +const barnFileName = `barn-mock.json`; const innvilgetVedtakFileName = `innvilget-vedtak-mock.json`; const ikkeInnvilgetVedtakFileName = `ikke-innvilget-vedtak-mock.json`; @@ -79,13 +80,19 @@ const startExpressServer = () => { }, 250); }); + server.get('/oppslag/barn', (req, res) => { + setTimeout(() => { + readMockFile(barnFileName, res); + }, 250); + }); + server.get('/deltakelse/register/hent/alle', (req, res) => { const response = [ { id: '123', programperiodeFraOgMed: '2024-07-01', programperiodeTilOgMed: '2025-06-30', - harSøkt: true, + harSøkt: false, rapporteringsPerioder: [ { fraOgMed: '2024-07-01', diff --git a/apps/ungdomsytelse-deltaker/mock/msw/browser.ts b/apps/ungdomsytelse-deltaker/mock/msw/browser.ts new file mode 100644 index 0000000000..a8721ac9dc --- /dev/null +++ b/apps/ungdomsytelse-deltaker/mock/msw/browser.ts @@ -0,0 +1,4 @@ +import { setupWorker } from 'msw/browser'; +import { handlers } from './handlers/handlers'; + +export const worker = setupWorker(...handlers); diff --git a/apps/ungdomsytelse-deltaker/mock/msw/handlers/handlers.ts b/apps/ungdomsytelse-deltaker/mock/msw/handlers/handlers.ts new file mode 100644 index 0000000000..4d8a362038 --- /dev/null +++ b/apps/ungdomsytelse-deltaker/mock/msw/handlers/handlers.ts @@ -0,0 +1,50 @@ +import { delay, http, HttpResponse } from 'msw'; +import { søker1Mock } from '../mocks/soker1'; +import { deltakelserIkkeSøkt } from '../mocks/soker1/deltakelser/ikkeSøkt'; +import { deltakelserHarSøkt } from '../mocks/soker1/deltakelser/harSøkt'; + +const MellomlagringStorageKey = 'mellomlagring-ungdomsytelse-deltaker-soknad'; + +export const handlers = [ + http.post('*amplitude*', () => new HttpResponse(null, { status: 200 })), + http.post('*hotjar*', () => new HttpResponse(null, { status: 200 })), + http.get('**/oppslag/soker', () => { + return HttpResponse.json(søker1Mock.søker); + }), + http.get('**/oppslag/barn', () => { + return HttpResponse.json(søker1Mock.barn); + }), + http.get('**/oppslag/arbeidsgiver', () => { + return HttpResponse.json(søker1Mock.arbeidsgiver); + }), + http.post('**/soknad/innsending', () => { + return HttpResponse.json({}); + }), + http.get('**/deltakelse/register/hent/alle', () => { + const harSøkt = false; + return HttpResponse.json(harSøkt ? deltakelserHarSøkt : deltakelserIkkeSøkt); + }), + http.get('**/person/personopplysninger-api/personalia', () => { + return HttpResponse.json(søker1Mock.personalia); + }), + http.get(`**/mellomlagring/UNGDOMSYTELSE_DELTAKER_SOKNAD`, async () => { + const data = localStorage.getItem(MellomlagringStorageKey); + const jsonData = JSON.parse(JSON.stringify(data) || '{}'); + await delay(350); + return new HttpResponse(jsonData, { status: 200 }); + }), + http.post(`**/mellomlagring/UNGDOMSYTELSE_DELTAKER_SOKNAD`, async ({ request }) => { + const data = await request.text(); + localStorage.setItem(MellomlagringStorageKey, data); + return new HttpResponse(null, { status: 200 }); + }), + http.put(`**/mellomlagring/UNGDOMSYTELSE_DELTAKER_SOKNAD`, async ({ request }) => { + const data = await request.text(); + localStorage.setItem(MellomlagringStorageKey, data); + return new HttpResponse(null, { status: 200 }); + }), + http.delete(`**/mellomlagring/UNGDOMSYTELSE_DELTAKER_SOKNAD`, () => { + localStorage.setItem(MellomlagringStorageKey, ''); + return new HttpResponse(null, { status: 200 }); + }), +]; diff --git a/apps/ungdomsytelse-deltaker/mock/msw/mocks/soker1/arbeidsgiverMock.ts b/apps/ungdomsytelse-deltaker/mock/msw/mocks/soker1/arbeidsgiverMock.ts new file mode 100644 index 0000000000..1279cf6680 --- /dev/null +++ b/apps/ungdomsytelse-deltaker/mock/msw/mocks/soker1/arbeidsgiverMock.ts @@ -0,0 +1,10 @@ +export const arbeidsgiverMock = { + organisasjoner: [ + { + navn: 'HAUGEN AS', + organisasjonsnummer: '123451234', + ansattFom: '2019-09-25', + ansattTom: null, + }, + ], +}; diff --git a/apps/ungdomsytelse-deltaker/mock/msw/mocks/soker1/barnMock.ts b/apps/ungdomsytelse-deltaker/mock/msw/mocks/soker1/barnMock.ts new file mode 100644 index 0000000000..59ed845cfa --- /dev/null +++ b/apps/ungdomsytelse-deltaker/mock/msw/mocks/soker1/barnMock.ts @@ -0,0 +1,11 @@ +export const barnMock = { + barn: [ + { + fornavn: 'ALFABETISK', + etternavn: 'TURLØYPE', + aktørId: '2811762539343', + fødselsdato: '2019-06-08', + fødselsnummer: '08861999573', + }, + ], +}; diff --git "a/apps/ungdomsytelse-deltaker/mock/msw/mocks/soker1/deltakelser/harS\303\270kt.ts" "b/apps/ungdomsytelse-deltaker/mock/msw/mocks/soker1/deltakelser/harS\303\270kt.ts" new file mode 100644 index 0000000000..a689a08a3a --- /dev/null +++ "b/apps/ungdomsytelse-deltaker/mock/msw/mocks/soker1/deltakelser/harS\303\270kt.ts" @@ -0,0 +1,82 @@ +export const deltakelserHarSøkt = [ + { + id: '123', + programperiodeFraOgMed: '2024-07-01', + programperiodeTilOgMed: '2025-06-30', + harSøkt: true, + rapporteringsPerioder: [ + { + fraOgMed: '2024-07-01', + tilOgMed: '2024-07-31', + harRapportert: false, + inntekt: null, + }, + { + fraOgMed: '2024-08-01', + tilOgMed: '2024-08-31', + harRapportert: false, + inntekt: null, + }, + { + fraOgMed: '2024-09-01', + tilOgMed: '2024-09-30', + harRapportert: false, + inntekt: null, + }, + { + fraOgMed: '2024-10-01', + tilOgMed: '2024-10-31', + harRapportert: false, + inntekt: null, + }, + { + fraOgMed: '2024-11-01', + tilOgMed: '2024-11-30', + harRapportert: false, + inntekt: null, + }, + { + fraOgMed: '2024-12-01', + tilOgMed: '2024-12-31', + harRapportert: false, + inntekt: null, + }, + { + fraOgMed: '2025-01-01', + tilOgMed: '2025-01-31', + harRapportert: false, + inntekt: null, + }, + { + fraOgMed: '2025-02-01', + tilOgMed: '2025-02-28', + harRapportert: false, + inntekt: null, + }, + { + fraOgMed: '2025-03-01', + tilOgMed: '2025-03-31', + harRapportert: false, + inntekt: null, + }, + { + fraOgMed: '2025-04-01', + tilOgMed: '2025-04-30', + harRapportert: false, + inntekt: null, + }, + { + fraOgMed: '2025-05-01', + tilOgMed: '2025-05-31', + harRapportert: false, + inntekt: null, + }, + { + fraOgMed: '2025-06-01', + tilOgMed: '2025-06-30', + harRapportert: false, + inntekt: null, + }, + ], + }, +]; diff --git "a/apps/ungdomsytelse-deltaker/mock/msw/mocks/soker1/deltakelser/ikkeS\303\270kt.ts" "b/apps/ungdomsytelse-deltaker/mock/msw/mocks/soker1/deltakelser/ikkeS\303\270kt.ts" new file mode 100644 index 0000000000..b1e1f3835b --- /dev/null +++ "b/apps/ungdomsytelse-deltaker/mock/msw/mocks/soker1/deltakelser/ikkeS\303\270kt.ts" @@ -0,0 +1,82 @@ +export const deltakelserIkkeSøkt = [ + { + id: '123', + programperiodeFraOgMed: '2024-07-01', + programperiodeTilOgMed: '2025-06-30', + harSøkt: false, + rapporteringsPerioder: [ + { + fraOgMed: '2024-07-01', + tilOgMed: '2024-07-31', + harRapportert: false, + inntekt: null, + }, + { + fraOgMed: '2024-08-01', + tilOgMed: '2024-08-31', + harRapportert: false, + inntekt: null, + }, + { + fraOgMed: '2024-09-01', + tilOgMed: '2024-09-30', + harRapportert: false, + inntekt: null, + }, + { + fraOgMed: '2024-10-01', + tilOgMed: '2024-10-31', + harRapportert: false, + inntekt: null, + }, + { + fraOgMed: '2024-11-01', + tilOgMed: '2024-11-30', + harRapportert: false, + inntekt: null, + }, + { + fraOgMed: '2024-12-01', + tilOgMed: '2024-12-31', + harRapportert: false, + inntekt: null, + }, + { + fraOgMed: '2025-01-01', + tilOgMed: '2025-01-31', + harRapportert: false, + inntekt: null, + }, + { + fraOgMed: '2025-02-01', + tilOgMed: '2025-02-28', + harRapportert: false, + inntekt: null, + }, + { + fraOgMed: '2025-03-01', + tilOgMed: '2025-03-31', + harRapportert: false, + inntekt: null, + }, + { + fraOgMed: '2025-04-01', + tilOgMed: '2025-04-30', + harRapportert: false, + inntekt: null, + }, + { + fraOgMed: '2025-05-01', + tilOgMed: '2025-05-31', + harRapportert: false, + inntekt: null, + }, + { + fraOgMed: '2025-06-01', + tilOgMed: '2025-06-30', + harRapportert: false, + inntekt: null, + }, + ], + }, +]; diff --git a/apps/ungdomsytelse-deltaker/mock/msw/mocks/soker1/hentAlle.ts b/apps/ungdomsytelse-deltaker/mock/msw/mocks/soker1/hentAlle.ts new file mode 100644 index 0000000000..75d746d795 --- /dev/null +++ b/apps/ungdomsytelse-deltaker/mock/msw/mocks/soker1/hentAlle.ts @@ -0,0 +1,82 @@ +export const hentAlleResponse = [ + { + id: '123', + programperiodeFraOgMed: '2024-07-01', + programperiodeTilOgMed: '2025-06-30', + harSøkt: false, + rapporteringsPerioder: [ + { + fraOgMed: '2024-07-01', + tilOgMed: '2024-07-31', + harRapportert: false, + inntekt: null, + }, + { + fraOgMed: '2024-08-01', + tilOgMed: '2024-08-31', + harRapportert: false, + inntekt: null, + }, + { + fraOgMed: '2024-09-01', + tilOgMed: '2024-09-30', + harRapportert: false, + inntekt: null, + }, + { + fraOgMed: '2024-10-01', + tilOgMed: '2024-10-31', + harRapportert: false, + inntekt: null, + }, + { + fraOgMed: '2024-11-01', + tilOgMed: '2024-11-30', + harRapportert: false, + inntekt: null, + }, + { + fraOgMed: '2024-12-01', + tilOgMed: '2024-12-31', + harRapportert: false, + inntekt: null, + }, + { + fraOgMed: '2025-01-01', + tilOgMed: '2025-01-31', + harRapportert: false, + inntekt: null, + }, + { + fraOgMed: '2025-02-01', + tilOgMed: '2025-02-28', + harRapportert: false, + inntekt: null, + }, + { + fraOgMed: '2025-03-01', + tilOgMed: '2025-03-31', + harRapportert: false, + inntekt: null, + }, + { + fraOgMed: '2025-04-01', + tilOgMed: '2025-04-30', + harRapportert: false, + inntekt: null, + }, + { + fraOgMed: '2025-05-01', + tilOgMed: '2025-05-31', + harRapportert: false, + inntekt: null, + }, + { + fraOgMed: '2025-06-01', + tilOgMed: '2025-06-30', + harRapportert: false, + inntekt: null, + }, + ], + }, +]; diff --git a/apps/ungdomsytelse-deltaker/mock/msw/mocks/soker1/index.ts b/apps/ungdomsytelse-deltaker/mock/msw/mocks/soker1/index.ts new file mode 100644 index 0000000000..f71129fc7f --- /dev/null +++ b/apps/ungdomsytelse-deltaker/mock/msw/mocks/soker1/index.ts @@ -0,0 +1,11 @@ +import { arbeidsgiverMock } from './arbeidsgiverMock'; +import { barnMock } from './barnMock'; +import { personaliaMock } from './personaliaMock'; +import { søkerMock } from './søkerMock'; + +export const søker1Mock = { + barn: barnMock, + søker: søkerMock, + arbeidsgiver: arbeidsgiverMock, + personalia: personaliaMock, +}; diff --git a/apps/ungdomsytelse-deltaker/mock/msw/mocks/soker1/personaliaMock.ts b/apps/ungdomsytelse-deltaker/mock/msw/mocks/soker1/personaliaMock.ts new file mode 100644 index 0000000000..85ef5dc442 --- /dev/null +++ b/apps/ungdomsytelse-deltaker/mock/msw/mocks/soker1/personaliaMock.ts @@ -0,0 +1,9 @@ +import { PersonaliaApiData } from '../../../../src/api/types'; + +export const personaliaMock: PersonaliaApiData = { + personalia: { + kontoregisterStatus: 'FAILURE', + kontonr: '97105351740', + utenlandskbank: null, + }, +}; diff --git "a/apps/ungdomsytelse-deltaker/mock/msw/mocks/soker1/s\303\270kerMock.ts" "b/apps/ungdomsytelse-deltaker/mock/msw/mocks/soker1/s\303\270kerMock.ts" new file mode 100644 index 0000000000..3b50e0b393 --- /dev/null +++ "b/apps/ungdomsytelse-deltaker/mock/msw/mocks/soker1/s\303\270kerMock.ts" @@ -0,0 +1,8 @@ +export const søkerMock = { + aktørId: '2320509955297', + fødselsdato: '2005-06-02', + fødselsnummer: '02869599258', + fornavn: 'Test', + mellomnavn: null, + etternavn: 'Brukeresen', +}; diff --git a/apps/ungdomsytelse-deltaker/mockServiceWorker.js b/apps/ungdomsytelse-deltaker/mockServiceWorker.js new file mode 100644 index 0000000000..ec47a9a50a --- /dev/null +++ b/apps/ungdomsytelse-deltaker/mockServiceWorker.js @@ -0,0 +1,307 @@ +/* eslint-disable */ +/* tslint:disable */ + +/** + * Mock Service Worker. + * @see https://github.com/mswjs/msw + * - Please do NOT modify this file. + * - Please do NOT serve this file on production. + */ + +const PACKAGE_VERSION = '2.7.0' +const INTEGRITY_CHECKSUM = '00729d72e3b82faf54ca8b9621dbb96f' +const IS_MOCKED_RESPONSE = Symbol('isMockedResponse') +const activeClientIds = new Set() + +self.addEventListener('install', function () { + self.skipWaiting() +}) + +self.addEventListener('activate', function (event) { + event.waitUntil(self.clients.claim()) +}) + +self.addEventListener('message', async function (event) { + const clientId = event.source.id + + if (!clientId || !self.clients) { + return + } + + const client = await self.clients.get(clientId) + + if (!client) { + return + } + + const allClients = await self.clients.matchAll({ + type: 'window', + }) + + switch (event.data) { + case 'KEEPALIVE_REQUEST': { + sendToClient(client, { + type: 'KEEPALIVE_RESPONSE', + }) + break + } + + case 'INTEGRITY_CHECK_REQUEST': { + sendToClient(client, { + type: 'INTEGRITY_CHECK_RESPONSE', + payload: { + packageVersion: PACKAGE_VERSION, + checksum: INTEGRITY_CHECKSUM, + }, + }) + break + } + + case 'MOCK_ACTIVATE': { + activeClientIds.add(clientId) + + sendToClient(client, { + type: 'MOCKING_ENABLED', + payload: { + client: { + id: client.id, + frameType: client.frameType, + }, + }, + }) + break + } + + case 'MOCK_DEACTIVATE': { + activeClientIds.delete(clientId) + break + } + + case 'CLIENT_CLOSED': { + activeClientIds.delete(clientId) + + const remainingClients = allClients.filter((client) => { + return client.id !== clientId + }) + + // Unregister itself when there are no more clients + if (remainingClients.length === 0) { + self.registration.unregister() + } + + break + } + } +}) + +self.addEventListener('fetch', function (event) { + const { request } = event + + // Bypass navigation requests. + if (request.mode === 'navigate') { + return + } + + // Opening the DevTools triggers the "only-if-cached" request + // that cannot be handled by the worker. Bypass such requests. + if (request.cache === 'only-if-cached' && request.mode !== 'same-origin') { + return + } + + // Bypass all requests when there are no active clients. + // Prevents the self-unregistered worked from handling requests + // after it's been deleted (still remains active until the next reload). + if (activeClientIds.size === 0) { + return + } + + // Generate unique request ID. + const requestId = crypto.randomUUID() + event.respondWith(handleRequest(event, requestId)) +}) + +async function handleRequest(event, requestId) { + const client = await resolveMainClient(event) + const response = await getResponse(event, client, requestId) + + // Send back the response clone for the "response:*" life-cycle events. + // Ensure MSW is active and ready to handle the message, otherwise + // this message will pend indefinitely. + if (client && activeClientIds.has(client.id)) { + ;(async function () { + const responseClone = response.clone() + + sendToClient( + client, + { + type: 'RESPONSE', + payload: { + requestId, + isMockedResponse: IS_MOCKED_RESPONSE in response, + type: responseClone.type, + status: responseClone.status, + statusText: responseClone.statusText, + body: responseClone.body, + headers: Object.fromEntries(responseClone.headers.entries()), + }, + }, + [responseClone.body], + ) + })() + } + + return response +} + +// Resolve the main client for the given event. +// Client that issues a request doesn't necessarily equal the client +// that registered the worker. It's with the latter the worker should +// communicate with during the response resolving phase. +async function resolveMainClient(event) { + const client = await self.clients.get(event.clientId) + + if (activeClientIds.has(event.clientId)) { + return client + } + + if (client?.frameType === 'top-level') { + return client + } + + const allClients = await self.clients.matchAll({ + type: 'window', + }) + + return allClients + .filter((client) => { + // Get only those clients that are currently visible. + return client.visibilityState === 'visible' + }) + .find((client) => { + // Find the client ID that's recorded in the + // set of clients that have registered the worker. + return activeClientIds.has(client.id) + }) +} + +async function getResponse(event, client, requestId) { + const { request } = event + + // Clone the request because it might've been already used + // (i.e. its body has been read and sent to the client). + const requestClone = request.clone() + + function passthrough() { + // Cast the request headers to a new Headers instance + // so the headers can be manipulated with. + const headers = new Headers(requestClone.headers) + + // Remove the "accept" header value that marked this request as passthrough. + // This prevents request alteration and also keeps it compliant with the + // user-defined CORS policies. + const acceptHeader = headers.get('accept') + if (acceptHeader) { + const values = acceptHeader.split(',').map((value) => value.trim()) + const filteredValues = values.filter( + (value) => value !== 'msw/passthrough', + ) + + if (filteredValues.length > 0) { + headers.set('accept', filteredValues.join(', ')) + } else { + headers.delete('accept') + } + } + + return fetch(requestClone, { headers }) + } + + // Bypass mocking when the client is not active. + if (!client) { + return passthrough() + } + + // Bypass initial page load requests (i.e. static assets). + // The absence of the immediate/parent client in the map of the active clients + // means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet + // and is not ready to handle requests. + if (!activeClientIds.has(client.id)) { + return passthrough() + } + + // Notify the client that a request has been intercepted. + const requestBuffer = await request.arrayBuffer() + const clientMessage = await sendToClient( + client, + { + type: 'REQUEST', + payload: { + id: requestId, + url: request.url, + mode: request.mode, + method: request.method, + headers: Object.fromEntries(request.headers.entries()), + cache: request.cache, + credentials: request.credentials, + destination: request.destination, + integrity: request.integrity, + redirect: request.redirect, + referrer: request.referrer, + referrerPolicy: request.referrerPolicy, + body: requestBuffer, + keepalive: request.keepalive, + }, + }, + [requestBuffer], + ) + + switch (clientMessage.type) { + case 'MOCK_RESPONSE': { + return respondWithMock(clientMessage.data) + } + + case 'PASSTHROUGH': { + return passthrough() + } + } + + return passthrough() +} + +function sendToClient(client, message, transferrables = []) { + return new Promise((resolve, reject) => { + const channel = new MessageChannel() + + channel.port1.onmessage = (event) => { + if (event.data && event.data.error) { + return reject(event.data.error) + } + + resolve(event.data) + } + + client.postMessage( + message, + [channel.port2].concat(transferrables.filter(Boolean)), + ) + }) +} + +async function respondWithMock(response) { + // Setting response status code to 0 is a no-op. + // However, when responding with a "Response.error()", the produced Response + // instance will have status code set to 0. Since it's not possible to create + // a Response instance with status code 0, handle that use-case separately. + if (response.status === 0) { + return Response.error() + } + + const mockedResponse = new Response(response.body, response) + + Reflect.defineProperty(mockedResponse, IS_MOCKED_RESPONSE, { + value: true, + enumerable: true, + }) + + return mockedResponse +} diff --git a/apps/ungdomsytelse-deltaker/package.json b/apps/ungdomsytelse-deltaker/package.json index 1a2b1e174f..ea3e2a2484 100644 --- a/apps/ungdomsytelse-deltaker/package.json +++ b/apps/ungdomsytelse-deltaker/package.json @@ -34,12 +34,14 @@ "dayjs": "1.11.13", "html-react-parser": "5.2.2", "jsdom": "25.0.1", + "object-hash": "^3.0.0", "postcss": "8.4.49", "react": "19.0.0", "react-dom": "19.0.0", "react-hook-form": "7.54.2", "react-intl": "7.1.0", "react-router-dom": "7.1.1", + "swr": "2.2.5", "uuid": "11.0.5", "zod": "3.24.1" }, @@ -56,6 +58,7 @@ "@storybook/react-vite": "8.4.7", "@storybook/test": "8.4.7", "@types/node": "22.10.5", + "@types/object-hash": "3.0.6", "@types/react": "19.0.6", "@types/react-dom": "19.0.3", "@typescript-eslint/parser": "8.19.1", @@ -70,6 +73,7 @@ "eslint-plugin-unicorn": "56.0.1", "express": "4.21.2", "http-proxy-middleware": "3.0.3", + "msw": "^2.7.0", "mustache-express": "1.3.2", "postcss-import": "16.1.0", "postcss-styled-syntax": "0.7.0", @@ -82,5 +86,10 @@ "vite": "6.0.7", "vite-plugin-checker": "0.8.0", "vitest": "2.1.8" + }, + "msw": { + "workerDirectory": [ + "" + ] } } diff --git a/apps/ungdomsytelse-deltaker/src/App.tsx b/apps/ungdomsytelse-deltaker/src/App.tsx index 9f83fc2fdd..698f5312b8 100644 --- a/apps/ungdomsytelse-deltaker/src/App.tsx +++ b/apps/ungdomsytelse-deltaker/src/App.tsx @@ -1,9 +1,9 @@ +import { applicationIntlMessages } from '@i18n/index'; import { isProd } from '@navikt/sif-common-env'; import { ensureBaseNameForReactRouter, SoknadApplication } from '@navikt/sif-common-soknad-ds/src'; -import { applicationIntlMessages } from './i18n'; -import Søknad from './søknad/Søknad'; -import { appEnv } from './types/appEnv'; -import '@navikt/ds-css'; +import { appEnv } from '@utils/appEnv'; +import InitialDataLoader from './sites/InitialDataLoader'; +import './app.css'; const { PUBLIC_PATH, @@ -33,7 +33,7 @@ const App = () => { }} useAmplitude={SIF_PUBLIC_USE_AMPLITUDE ? SIF_PUBLIC_USE_AMPLITUDE === 'true' : isProd()} amplitudeApiKey={SIF_PUBLIC_AMPLITUDE_API_KEY}> - + ); }; diff --git a/apps/ungdomsytelse-deltaker/src/api/apiClient.ts b/apps/ungdomsytelse-deltaker/src/api/apiClient.ts index 2ae84de318..77292e7c5c 100644 --- a/apps/ungdomsytelse-deltaker/src/api/apiClient.ts +++ b/apps/ungdomsytelse-deltaker/src/api/apiClient.ts @@ -1,5 +1,5 @@ +import { appEnv } from '@utils/appEnv'; import axios, { AxiosRequestConfig } from 'axios'; -import { appEnv } from '../types/appEnv'; const axiosConfig: AxiosRequestConfig = { withCredentials: false, diff --git a/apps/ungdomsytelse-deltaker/src/api/schemas/deltakelseSchema.ts b/apps/ungdomsytelse-deltaker/src/api/schemas/deltakelseSchema.ts index 84e4190247..63475d4545 100644 --- a/apps/ungdomsytelse-deltaker/src/api/schemas/deltakelseSchema.ts +++ b/apps/ungdomsytelse-deltaker/src/api/schemas/deltakelseSchema.ts @@ -1,8 +1,8 @@ -import { z } from 'zod'; +import { Rapporteringsperiode } from '@api/types'; import { parseMaybeDateStringToDate } from '@navikt/sif-common-api/src/utils/jsonParseUtils'; -import { DateRange } from '@navikt/sif-common-utils'; import { OpenDateRange } from '@navikt/sif-common-formik-ds'; -import { Rapporteringsperiode } from '../types'; +import { DateRange } from '@navikt/sif-common-utils'; +import { z } from 'zod'; const rapporteringsperiodeDTOSchema = z.object({ fraOgMed: z.preprocess((val) => parseMaybeDateStringToDate(val), z.date()), diff --git a/apps/ungdomsytelse-deltaker/src/api/schemas/personaliaSchema.ts b/apps/ungdomsytelse-deltaker/src/api/schemas/personaliaSchema.ts new file mode 100644 index 0000000000..66120c78dc --- /dev/null +++ b/apps/ungdomsytelse-deltaker/src/api/schemas/personaliaSchema.ts @@ -0,0 +1,11 @@ +import { z } from 'zod'; + +export const kontonummerSchema = z.object({ + kontoregisterStatus: z.enum(['SUCCESS', 'FAILURE']), + kontonr: z.string().nullable(), + utenlandskbank: z.boolean().nullable(), +}); + +export const personaliaApiDataSchema = z.object({ + personalia: kontonummerSchema, +}); diff --git a/apps/ungdomsytelse-deltaker/src/api/services/deltakerService.ts b/apps/ungdomsytelse-deltaker/src/api/services/deltakerService.ts index 2ff8fc5074..4df71854f9 100644 --- a/apps/ungdomsytelse-deltaker/src/api/services/deltakerService.ts +++ b/apps/ungdomsytelse-deltaker/src/api/services/deltakerService.ts @@ -1,7 +1,7 @@ +import { ungDeltakelseOpplyserApiClient } from '@api/apiClient'; +import { Deltakelse, PeriodeMedInntekt } from '@api/types'; import getSentryLoggerForApp from '@navikt/sif-common-sentry'; -import { ungDeltakelseOpplyserApiClient } from '../apiClient'; import { deltakelserSchema } from '../schemas/deltakelserSchema'; -import { Deltakelse, PeriodeMedInntekt } from '../types'; const getDeltakelser = async (): Promise => { const response = await ungDeltakelseOpplyserApiClient.get(`/deltakelse/register/hent/alle`); diff --git a/apps/ungdomsytelse-deltaker/src/api/services/mellomlagringService.ts b/apps/ungdomsytelse-deltaker/src/api/services/mellomlagringService.ts new file mode 100644 index 0000000000..19591752a4 --- /dev/null +++ b/apps/ungdomsytelse-deltaker/src/api/services/mellomlagringService.ts @@ -0,0 +1,62 @@ +import { Søker } from '@navikt/sif-common-api'; +import { k9BrukerdialogApiClient } from '@navikt/sif-common-api/src/api/apiClient'; +import { jsonSort } from '@navikt/sif-common-utils'; +import hash from 'object-hash'; +import { SøknadContextState } from '../../sites/søknad/context/SøknadContextState'; +import { SOKNAD_VERSJON } from '../../sites/søknad/utils/søknadUtils'; +import { Deltakelse } from '../types'; + +export const mellomlagringEndpointUrl = `/mellomlagring/UNGDOMSYTELSE_DELTAKER_SOKNAD`; + +export type MellomlagringData = Omit & { + søknadHashString: string; +}; + +interface SøknadStateHashInfo { + søkerKey: string; + deltakelseKey: string; +} + +const createSøknadHashString = (søker: Søker, deltakelse: Deltakelse) => { + const hashData: SøknadStateHashInfo = { + søkerKey: søker.aktørId, + deltakelseKey: deltakelse.id, + }; + return hash(JSON.stringify(jsonSort(hashData))); +}; + +export const mellomlagringIsValid = ( + mellomlagring: MellomlagringData, + søker: Søker, + deltakelse: Deltakelse, +): boolean => { + return ( + mellomlagring.søknadHashString === createSøknadHashString(søker, deltakelse) && + mellomlagring.versjon === SOKNAD_VERSJON + ); +}; + +export const mellomlagringService = { + create: async (data: MellomlagringData) => { + return k9BrukerdialogApiClient.post(mellomlagringEndpointUrl, data); + }, + update: ({ søker, søknadsdata, søknadRoute, søknadSendt, registrerteBarn }: SøknadContextState) => { + const data: MellomlagringData = { + søknadHashString: createSøknadHashString(søker, søknadsdata.deltakelse), + søknadsdata, + søknadRoute, + søknadSendt, + registrerteBarn, + versjon: SOKNAD_VERSJON, + }; + return k9BrukerdialogApiClient.put(mellomlagringEndpointUrl, data); + }, + purge: () => { + return k9BrukerdialogApiClient.delete(mellomlagringEndpointUrl); + }, + fetch: async (): Promise => { + const result = await k9BrukerdialogApiClient.get(mellomlagringEndpointUrl); + return result.data; + }, + fetcher: (url: string) => k9BrukerdialogApiClient.get(url).then((res) => res.data), +}; diff --git a/apps/ungdomsytelse-deltaker/src/api/services/personaliaService.ts b/apps/ungdomsytelse-deltaker/src/api/services/personaliaService.ts new file mode 100644 index 0000000000..ff61a6dfbe --- /dev/null +++ b/apps/ungdomsytelse-deltaker/src/api/services/personaliaService.ts @@ -0,0 +1,18 @@ +import { personaliaApiDataSchema } from '../schemas/personaliaSchema'; +import { PersonaliaApiData } from '../types'; + +export const personaliaService = { + fetch: async (): Promise => { + try { + const response = await fetch(`person/personopplysninger-api/personalia`); + if (!response.ok) { + throw new Error(`Failed to fetch personalia: ${response.status}`); + } + const json = await response.json(); + return await personaliaApiDataSchema.parse(json); + } catch (e) { + console.log(e); + return undefined; + } + }, +}; diff --git "a/apps/ungdomsytelse-deltaker/src/api/services/sendS\303\270knadService.ts" "b/apps/ungdomsytelse-deltaker/src/api/services/sendS\303\270knadService.ts" index 27de3831ae..93e37e6f92 100644 --- "a/apps/ungdomsytelse-deltaker/src/api/services/sendS\303\270knadService.ts" +++ "b/apps/ungdomsytelse-deltaker/src/api/services/sendS\303\270knadService.ts" @@ -1,5 +1,5 @@ +import { SøknadApiData } from '@api/types'; import { k9BrukerdialogApiClient } from '@navikt/sif-common-api/src/api/apiClient'; -import { SøknadApiData } from '../types'; import { søknadApiDataSchema } from '../schemas/søknadApiDataSchema'; export const sendSøknadService = { diff --git a/apps/ungdomsytelse-deltaker/src/api/types/index.ts b/apps/ungdomsytelse-deltaker/src/api/types/index.ts index e7fd803d80..d0c73e635d 100644 --- a/apps/ungdomsytelse-deltaker/src/api/types/index.ts +++ b/apps/ungdomsytelse-deltaker/src/api/types/index.ts @@ -2,6 +2,7 @@ import { z } from 'zod'; import { deltakelserSchema } from '../schemas/deltakelserSchema'; import { deltakelseSchema, rapporteringsperiodeSchema } from '../schemas/deltakelseSchema'; import { periodeMedInntektSchema } from '../schemas/periodeMedInntektSchema'; +import { kontonummerSchema, personaliaApiDataSchema } from '../schemas/personaliaSchema'; import { søknadApiDataSchema } from '../schemas/søknadApiDataSchema'; export type Deltakelse = z.infer; @@ -9,3 +10,5 @@ export type Deltakelser = z.infer; export type PeriodeMedInntekt = z.infer; export type Rapporteringsperiode = z.infer; export type SøknadApiData = z.infer; +export type PersonaliaApiData = z.infer; +export type KontonummerInfo = z.infer; diff --git a/apps/ungdomsytelse-deltaker/src/app.css b/apps/ungdomsytelse-deltaker/src/app.css new file mode 100644 index 0000000000..0d54b5965b --- /dev/null +++ b/apps/ungdomsytelse-deltaker/src/app.css @@ -0,0 +1,12 @@ +@import 'tailwindcss/base'; +@import '@navikt/ds-css'; +@import 'tailwindcss/components'; +@import 'tailwindcss/utilities'; +@import '@navikt/sif-common-core-ds/src/styles/sif-ds-theme.css'; + +.navds-guide-panel__content > .navds-body-long p:first-child { + margin-top: 0; +} +.navds-guide-panel__content > .navds-body-long p:last-child { + margin-bottom: 0; +} diff --git a/apps/ungdomsytelse-deltaker/src/components/deltakelse-table/DeltakelseTable.tsx b/apps/ungdomsytelse-deltaker/src/components/deltakelse-table/DeltakelseTable.tsx index 9b6b7ca75b..ae57acdad1 100644 --- a/apps/ungdomsytelse-deltaker/src/components/deltakelse-table/DeltakelseTable.tsx +++ b/apps/ungdomsytelse-deltaker/src/components/deltakelse-table/DeltakelseTable.tsx @@ -1,6 +1,6 @@ import { Table } from '@navikt/ds-react'; import { dateFormatter } from '@navikt/sif-common-utils'; -import { Deltakelse } from '../../api/types'; +import { Deltakelse } from '@api/types'; import Inntektsrapportering from '../inntektsrapportering/Inntektsrapportering'; interface Props { diff --git a/apps/ungdomsytelse-deltaker/src/components/inntektsrapportering/InnteksrapporteringForm.tsx b/apps/ungdomsytelse-deltaker/src/components/inntektsrapportering/InnteksrapporteringForm.tsx index 5f4f2f4b71..93a71cb80a 100644 --- a/apps/ungdomsytelse-deltaker/src/components/inntektsrapportering/InnteksrapporteringForm.tsx +++ b/apps/ungdomsytelse-deltaker/src/components/inntektsrapportering/InnteksrapporteringForm.tsx @@ -2,7 +2,7 @@ import { Box, Heading, VStack } from '@navikt/ds-react'; import { getMånederForInnteksrapportering } from '../../utils/deltakelserUtils'; import InntektEnMånedForm from './InntektEnMånedForm'; import { FormLayout } from '@navikt/sif-common-ui'; -import { Deltakelse } from '../../api/types'; +import { Deltakelse } from '@api/types'; interface Props { deltakelse: Deltakelse; diff --git "a/apps/ungdomsytelse-deltaker/src/components/inntektsrapportering/InntektEnM\303\245nedForm.tsx" "b/apps/ungdomsytelse-deltaker/src/components/inntektsrapportering/InntektEnM\303\245nedForm.tsx" index 2fdc1a43f0..a615956aec 100644 --- "a/apps/ungdomsytelse-deltaker/src/components/inntektsrapportering/InntektEnM\303\245nedForm.tsx" +++ "b/apps/ungdomsytelse-deltaker/src/components/inntektsrapportering/InntektEnM\303\245nedForm.tsx" @@ -1,4 +1,4 @@ -import { Deltakelse } from '../../api/types'; +import { Deltakelse } from '@api/types'; import { DateRange, dateToISOString, getTypedFormComponents, ValidationError } from '@navikt/sif-common-formik-ds'; import { capsFirstCharacter, dateFormatter } from '@navikt/sif-common-utils'; @@ -6,7 +6,7 @@ import { Alert, Box, Button, Heading, HStack } from '@navikt/ds-react'; import getIntlFormErrorHandler from '@navikt/sif-common-formik-ds/src/validation/intlFormErrorHandler'; import { useAppIntl } from '../../i18n'; import { getNumberValidator } from '@navikt/sif-common-formik-ds/src/validation'; -import { useRapporterInntekt } from '../../hooks/useRapporterInntekt'; +import { useRapporterInntekt } from '@hooks/useRapporterInntekt'; import TimedContent from '../timed-content/TimedContent'; interface Props { diff --git a/apps/ungdomsytelse-deltaker/src/components/inntektsrapportering/Inntektsrapportering.tsx b/apps/ungdomsytelse-deltaker/src/components/inntektsrapportering/Inntektsrapportering.tsx index 5d98a92f4b..48e682ca0d 100644 --- a/apps/ungdomsytelse-deltaker/src/components/inntektsrapportering/Inntektsrapportering.tsx +++ b/apps/ungdomsytelse-deltaker/src/components/inntektsrapportering/Inntektsrapportering.tsx @@ -1,6 +1,6 @@ import { Alert, BodyShort, Box, Heading, VStack } from '@navikt/ds-react'; import { dateFormatter, dateRangeFormatter } from '@navikt/sif-common-utils'; -import { Deltakelse } from '../../api/types'; +import { Deltakelse } from '@api/types'; import { useAppIntl } from '../../i18n'; import { getMånederForInnteksrapportering } from '../../utils/deltakelserUtils'; import InntektsrapporteringForm from './InnteksrapporteringForm'; diff --git a/apps/ungdomsytelse-deltaker/src/context/DeltakerContext.tsx b/apps/ungdomsytelse-deltaker/src/context/DeltakerContext.tsx new file mode 100644 index 0000000000..6c690ffdeb --- /dev/null +++ b/apps/ungdomsytelse-deltaker/src/context/DeltakerContext.tsx @@ -0,0 +1,50 @@ +import { createContext, ReactNode, useContext, useState } from 'react'; +import { RegistrertBarn, Søker } from '@navikt/sif-common-api'; +import { Deltakelse, KontonummerInfo } from '@api/types'; +import { deltakelseErÅpenForRapportering } from '@utils/deltakelserUtils'; + +interface DeltakerContextType { + data: DeltakerContextData; + updateDeltakelse: (deltakelser: Deltakelse[]) => void; +} + +export interface DeltakerContextData { + søker: Søker; + barn: RegistrertBarn[]; + kontonummerInfo?: KontonummerInfo; + alleDeltakelser: Deltakelse[]; + deltakelserSøktFor: Deltakelse[]; + deltakelserIkkeSøktFor: Deltakelse[]; + deltakelserÅpenForRapportering: Deltakelse[]; + site: 'soknad' | 'innsyn'; +} + +export const DeltakerContext = createContext(null!); + +export const useDeltakerContext = (): DeltakerContextType => { + const context = useContext(DeltakerContext); + if (!context) { + throw new Error('useDeltakerContext must be used within a DeltakerContextProvider'); + } + return context; +}; +interface Props { + children: ReactNode; + initialData: DeltakerContextData; +} + +export const DeltakerContextProvider = ({ children, initialData }: Props) => { + const [data, setData] = useState(initialData); + + const updateDeltakelse = (deltakelser: Deltakelse[]) => { + setData({ + ...data, + alleDeltakelser: deltakelser, + deltakelserSøktFor: deltakelser.filter((d) => d.harSøkt), + deltakelserIkkeSøktFor: deltakelser.filter((d) => !d.harSøkt), + deltakelserÅpenForRapportering: deltakelser.filter(deltakelseErÅpenForRapportering), + }); + }; + + return {children}; +}; diff --git a/apps/ungdomsytelse-deltaker/src/hooks/useInitialData.ts b/apps/ungdomsytelse-deltaker/src/hooks/useInitialData.ts index 489dece94f..d60086555f 100644 --- a/apps/ungdomsytelse-deltaker/src/hooks/useInitialData.ts +++ b/apps/ungdomsytelse-deltaker/src/hooks/useInitialData.ts @@ -1,11 +1,12 @@ import { useState } from 'react'; -import { fetchSøker } from '@navikt/sif-common-api'; +import { deltakerService } from '@api/services/deltakerService'; +import { DeltakerContextData } from '@context/DeltakerContext'; +import { fetchBarn, fetchSøker } from '@navikt/sif-common-api'; import { useEffectOnce } from '@navikt/sif-common-hooks'; -import { deltakerService } from '../api/services/deltakerService'; -import { SøknadContextData } from '../søknad/context/SøknadContext'; -import { deltakelseErÅpenForRapportering } from '../utils/deltakelserUtils'; +import { deltakelseErÅpenForRapportering } from '@utils/deltakelserUtils'; +import { personaliaService } from '../api/services/personaliaService'; -export type InitialData = SøknadContextData; +export type InitialData = DeltakerContextData; export const useInitialData = () => { const [initialData, setInitialData] = useState(); @@ -17,16 +18,23 @@ export const useInitialData = () => { try { const søker = await fetchSøker(); const alleDeltakelser = await deltakerService.getDeltakelser(); + const barn = await fetchBarn(); + const personalia = await personaliaService.fetch(); + const deltakelserSøktFor = alleDeltakelser.filter((d) => d.harSøkt); const deltakelserIkkeSøktFor = alleDeltakelser.filter((d) => !d.harSøkt); const deltakelserÅpenForRapportering = deltakelserSøktFor.filter(deltakelseErÅpenForRapportering); + const site = deltakelserIkkeSøktFor.length > 0 ? 'soknad' : 'innsyn'; setInitialData({ + barn, søker, + kontonummerInfo: personalia?.personalia, alleDeltakelser, deltakelserSøktFor, deltakelserIkkeSøktFor, deltakelserÅpenForRapportering, + site, }); setIsLoading(false); } catch (e) { diff --git a/apps/ungdomsytelse-deltaker/src/hooks/useRapporterInntekt.ts b/apps/ungdomsytelse-deltaker/src/hooks/useRapporterInntekt.ts index 675f33234a..2e0a91c19f 100644 --- a/apps/ungdomsytelse-deltaker/src/hooks/useRapporterInntekt.ts +++ b/apps/ungdomsytelse-deltaker/src/hooks/useRapporterInntekt.ts @@ -1,6 +1,6 @@ import { DateRange } from '@navikt/sif-common-formik-ds'; import { useState } from 'react'; -import { deltakerService } from '../api/services/deltakerService'; +import { deltakerService } from '@api/services/deltakerService'; import { dateToISODate } from '@navikt/sif-common-utils'; import { isAxiosError } from 'axios'; diff --git a/apps/ungdomsytelse-deltaker/src/i18n/appMessages.ts b/apps/ungdomsytelse-deltaker/src/i18n/appMessages.ts index 0d7c32a329..846d27f0b4 100644 --- a/apps/ungdomsytelse-deltaker/src/i18n/appMessages.ts +++ b/apps/ungdomsytelse-deltaker/src/i18n/appMessages.ts @@ -1,6 +1,17 @@ +import { velkommenPageMessages } from '../sites/søknad/pages/velkommen/velkommenPageMessages'; +import { oppsummeringMessages } from '../sites/søknad/steps/oppsummering-step/oppsummeringMessages'; +import { validateApiDataMessages } from '../sites/søknad/utils/søknadsdataToApiData/validateApiData'; +import { søknadMessages } from '../sites/søknad/i18n'; +import { kvitteringMessages } from '../sites/søknad/pages/kvittering/kvitteringMesssages'; + const nb = { - 'application.title': 'Søknad om ungdomsytelse', - 'søknad.harBekreftetOpplysninger.noValue': 'Du må bekrefte at du vil være med i programmet for perioden.', + ...velkommenPageMessages.nb, + ...oppsummeringMessages.nb, + ...validateApiDataMessages.nb, + ...søknadMessages.nb, + ...kvitteringMessages.nb, + 'application.title': 'Søknad om deltakelse i ungdomsprogrammet', + 'søknad.harBekreftetOpplysninger.noValue': 'Du må bekrefte at du vil være med i ungdomsprogrammet for perioden.', }; export const appMessages = { diff --git a/apps/ungdomsytelse-deltaker/src/lenker.ts b/apps/ungdomsytelse-deltaker/src/lenker.ts new file mode 100644 index 0000000000..8aaac13977 --- /dev/null +++ b/apps/ungdomsytelse-deltaker/src/lenker.ts @@ -0,0 +1,29 @@ +interface Lenker { + navno: string; + personvern: string; + rettOgPlikt: string; + saksbehandlingstider: string; + skrivTilOss: string; +} + +const LenkerBokmål: Lenker = { + navno: 'https://www.nav.no/', + personvern: + 'https://www.nav.no/no/NAV+og+samfunn/Om+NAV/personvern-i-arbeids-og-velferdsetaten/personvernerkl%C3%A6ring-for-arbeids-og-velferdsetaten', + rettOgPlikt: 'https://nav.no/rettOgPlikt', + saksbehandlingstider: 'https://www.nav.no/saksbehandlingstider', + skrivTilOss: 'https://www.nav.no/person/kontakt-oss/nb/skriv-til-oss', +}; + +const getLenker = (locale?: string): Lenker => { + switch (locale) { + case 'nn': + return { + ...LenkerBokmål, + }; + default: + return LenkerBokmål; + } +}; + +export default getLenker; diff --git a/apps/ungdomsytelse-deltaker/src/main.tsx b/apps/ungdomsytelse-deltaker/src/main.tsx index 7517b40b9e..202ac60c62 100644 --- a/apps/ungdomsytelse-deltaker/src/main.tsx +++ b/apps/ungdomsytelse-deltaker/src/main.tsx @@ -1,9 +1,20 @@ import { StrictMode } from 'react'; import { createRoot } from 'react-dom/client'; +import { getMaybeEnv } from '@navikt/sif-common-env'; import App from './App'; -createRoot(document.getElementById('root')!).render( - - - , +async function enableMocking() { + const ENV = getMaybeEnv('ENV'); + if (ENV !== 'development') { + return; + } + const { worker } = await import('../mock/msw/browser'); + return worker.start(); +} +enableMocking().then(() => + createRoot(document.getElementById('root')!).render( + + + , + ), ); diff --git "a/apps/ungdomsytelse-deltaker/src/s\303\270knad/S\303\270knad.tsx" b/apps/ungdomsytelse-deltaker/src/sites/InitialDataLoader.tsx similarity index 75% rename from "apps/ungdomsytelse-deltaker/src/s\303\270knad/S\303\270knad.tsx" rename to apps/ungdomsytelse-deltaker/src/sites/InitialDataLoader.tsx index 9e4b4ce9ab..905380c8eb 100644 --- "a/apps/ungdomsytelse-deltaker/src/s\303\270knad/S\303\270knad.tsx" +++ b/apps/ungdomsytelse-deltaker/src/sites/InitialDataLoader.tsx @@ -1,13 +1,14 @@ -import { useInitialData } from '../hooks/useInitialData'; +import { Heading } from '@navikt/ds-react'; +import { DeltakerContextProvider } from '@context/DeltakerContext'; +import { useInitialData } from '@hooks/useInitialData'; import LoadingSpinner from '@navikt/sif-common-core-ds/src/atoms/loading-spinner/LoadingSpinner'; -import { SøknadContextProvider } from './context/SøknadContext'; -import SøknadRouter from './SøknadRouter'; import Page from '@navikt/sif-common-core-ds/src/components/page/Page'; -import { ErrorPage } from '@navikt/sif-common-soknad-ds/src'; import SifGuidePanel from '@navikt/sif-common-core-ds/src/components/sif-guide-panel/SifGuidePanel'; -import { Heading } from '@navikt/ds-react'; +import { ErrorPage } from '@navikt/sif-common-soknad-ds/src'; +import InnsynRouter from './innsyn/InnsynRouter'; +import Søknad from './søknad/Søknad'; -const Søknad = () => { +const InitialDataLoader = () => { const { initialData, isLoading, error } = useInitialData(); if (error) { @@ -41,10 +42,10 @@ const Søknad = () => { } return ( - - - + + {initialData.deltakelserIkkeSøktFor.length === 1 ? : } + ); }; -export default Søknad; +export default InitialDataLoader; diff --git a/apps/ungdomsytelse-deltaker/src/sites/innsyn/Innsyn.tsx b/apps/ungdomsytelse-deltaker/src/sites/innsyn/Innsyn.tsx new file mode 100644 index 0000000000..af47520b99 --- /dev/null +++ b/apps/ungdomsytelse-deltaker/src/sites/innsyn/Innsyn.tsx @@ -0,0 +1,30 @@ +import { setBreadcrumbs } from '@navikt/nav-dekoratoren-moduler'; +import { EnvKey } from '@navikt/sif-common-env'; +import { useEffectOnce } from '@navikt/sif-common-hooks'; +import { useDeltakerContext } from '../../context/DeltakerContext'; +import { appEnv } from '../../utils/appEnv'; +import { InnsynContextProvider } from './context/InnsynContext'; +import InnsynForside from './pages/InnsynForside'; + +const Innsyn = () => { + const { + data: { søker, deltakelserSøktFor, kontonummerInfo }, + } = useDeltakerContext(); + + const deltakelse = deltakelserSøktFor[0]; + + useEffectOnce(() => { + setBreadcrumbs([ + { title: 'Min side', url: '/dittnav/' }, + { title: 'Ungdomsprogrammet', url: appEnv[EnvKey.PUBLIC_PATH] }, + ]); + }); + + return ( + + + + ); +}; + +export default Innsyn; diff --git a/apps/ungdomsytelse-deltaker/src/sites/innsyn/InnsynRouter.tsx b/apps/ungdomsytelse-deltaker/src/sites/innsyn/InnsynRouter.tsx new file mode 100644 index 0000000000..40367a7189 --- /dev/null +++ b/apps/ungdomsytelse-deltaker/src/sites/innsyn/InnsynRouter.tsx @@ -0,0 +1,12 @@ +import { Route, Routes } from 'react-router-dom'; +import Innsyn from './Innsyn'; + +const InnsynRouter = () => { + return ( + + } /> + + ); +}; + +export default InnsynRouter; diff --git a/apps/ungdomsytelse-deltaker/src/sites/innsyn/components/DeltakelseInfo.tsx b/apps/ungdomsytelse-deltaker/src/sites/innsyn/components/DeltakelseInfo.tsx new file mode 100644 index 0000000000..6ca1ee76e3 --- /dev/null +++ b/apps/ungdomsytelse-deltaker/src/sites/innsyn/components/DeltakelseInfo.tsx @@ -0,0 +1,34 @@ +import { Heading, VStack } from '@navikt/ds-react'; +import { Deltakelse, KontonummerInfo } from '../../../api/types'; +import { Søker } from '@navikt/sif-common-api'; +import { List } from '@navikt/ds-react/List'; +import { dateFormatter } from '@navikt/sif-common-utils'; +import KontonummerStatusTekst from './KontonummerStatusTekst'; + +interface Props { + deltakelse: Deltakelse; + søker: Søker; + kontonummerInfo?: KontonummerInfo; +} +const DeltakelseInfo = ({ deltakelse, kontonummerInfo }: Props) => { + const { programPeriode } = deltakelse; + return ( + + + Om deg og ungdomsprogrammet + + + + Fra: {dateFormatter.dateShortMonthYear(programPeriode.from)} + {programPeriode.to ? ` til ${dateFormatter.dateShortMonthYear(programPeriode.to)}` : ''} + + [TODO] + + + + + + ); +}; + +export default DeltakelseInfo; diff --git a/apps/ungdomsytelse-deltaker/src/sites/innsyn/components/KontonummerStatusTekst.tsx b/apps/ungdomsytelse-deltaker/src/sites/innsyn/components/KontonummerStatusTekst.tsx new file mode 100644 index 0000000000..3d6ed39ac0 --- /dev/null +++ b/apps/ungdomsytelse-deltaker/src/sites/innsyn/components/KontonummerStatusTekst.tsx @@ -0,0 +1,28 @@ +import { Alert } from '@navikt/ds-react'; +import { KontonummerInfo as KontonummerStatusTekst } from '../../../api/types'; + +interface Props { + kontonummerInfo?: KontonummerStatusTekst; +} + +const KontonummerStatusTekst = ({ kontonummerInfo }: Props) => { + if (!kontonummerInfo) { + return 'Informasjon om kontonummer mangler'; + } + const { kontonr, kontoregisterStatus } = kontonummerInfo; + if (kontoregisterStatus === 'SUCCESS' && kontonr) { + return `${kontonr}`; + } else if (kontoregisterStatus === 'FAILURE' && kontonr) { + return ( + + Ugyldig kontonummer - {kontonr} + + ); + } else if (kontoregisterStatus === 'FAILURE' && !kontonr) { + return `Kontonummer mangler`; + } + + return null; +}; + +export default KontonummerStatusTekst; diff --git a/apps/ungdomsytelse-deltaker/src/sites/innsyn/components/ManglendeKontonummer.tsx b/apps/ungdomsytelse-deltaker/src/sites/innsyn/components/ManglendeKontonummer.tsx new file mode 100644 index 0000000000..6f31b3c4b6 --- /dev/null +++ b/apps/ungdomsytelse-deltaker/src/sites/innsyn/components/ManglendeKontonummer.tsx @@ -0,0 +1,45 @@ +import { Alert, BodyShort, Heading, Link, VStack } from '@navikt/ds-react'; +import { KontonummerInfo } from '../../../api/types'; + +interface Props { + kontonummerInfo: KontonummerInfo; +} + +const ManglendeKontonummer = ({ kontonummerInfo }: Props) => { + if (kontonummerInfo.kontoregisterStatus === 'FAILURE') { + return ( + + + {kontonummerInfo.kontonr ? ( + <> + + Du må oppdatere kontonummeret ditt + + Kontonummeret vi har registrert på deg er ikke gyldig. + + Du kan oppdatere ditt kontonummer på{' '} + denne siden. + + + ) : ( + <> + + Vi mangler kontonummeret ditt + + + For at du skal være med i ungdomsprogrammet trenger vi kontonummer ditt. + + + Du kan registrere ditt kontonummer på{' '} + denne siden. + + + )} + + + ); + } + return null; +}; + +export default ManglendeKontonummer; diff --git a/apps/ungdomsytelse-deltaker/src/sites/innsyn/context/InnsynContext.tsx b/apps/ungdomsytelse-deltaker/src/sites/innsyn/context/InnsynContext.tsx new file mode 100644 index 0000000000..302c2307b4 --- /dev/null +++ b/apps/ungdomsytelse-deltaker/src/sites/innsyn/context/InnsynContext.tsx @@ -0,0 +1,21 @@ +import { createContext, FunctionComponent, ReactNode } from 'react'; +import { Deltakelse, KontonummerInfo } from '../../../api/types'; +import { Søker } from '@navikt/sif-common-api'; + +interface InnsynContextData { + søker: Søker; + deltakelse: Deltakelse; + kontonummerInfo?: KontonummerInfo; +} + +// eslint-disable-next-line @typescript-eslint/no-non-null-assertion +export const InnsynContext = createContext(null!); + +interface Props { + initialData: InnsynContextData; + children: ReactNode; +} + +export const InnsynContextProvider: FunctionComponent = ({ children, initialData }) => { + return {children}; +}; diff --git a/apps/ungdomsytelse-deltaker/src/sites/innsyn/hooks/useInnsynContext.ts b/apps/ungdomsytelse-deltaker/src/sites/innsyn/hooks/useInnsynContext.ts new file mode 100644 index 0000000000..d26a58d672 --- /dev/null +++ b/apps/ungdomsytelse-deltaker/src/sites/innsyn/hooks/useInnsynContext.ts @@ -0,0 +1,4 @@ +import { useContext } from 'react'; +import { InnsynContext } from '../context/InnsynContext'; + +export const useInnsynContext = () => useContext(InnsynContext); diff --git a/apps/ungdomsytelse-deltaker/src/sites/innsyn/pages/InnsynForside.tsx b/apps/ungdomsytelse-deltaker/src/sites/innsyn/pages/InnsynForside.tsx new file mode 100644 index 0000000000..44f1d2419a --- /dev/null +++ b/apps/ungdomsytelse-deltaker/src/sites/innsyn/pages/InnsynForside.tsx @@ -0,0 +1,52 @@ +import { VStack } from '@navikt/ds-react'; +import { setBreadcrumbs } from '@navikt/nav-dekoratoren-moduler'; +import Page from '@navikt/sif-common-core-ds/src/components/page/Page'; +import { EnvKey } from '@navikt/sif-common-env'; +import { useEffectOnce } from '@navikt/sif-common-hooks'; +import InntektsrapporteringForm from '../../../components/inntektsrapportering/InnteksrapporteringForm'; +import VelkommenPageHeader from '../../../components/velkommen-page-header/VelkommenPageHeader'; +import { useDeltakerContext } from '../../../context/DeltakerContext'; +import { appEnv } from '../../../utils/appEnv'; +import ManglendeKontonummer from '../components/ManglendeKontonummer'; +import DeltakelseInfo from '../components/DeltakelseInfo'; + +const InnsynForside = () => { + const { + data: { søker, deltakelserSøktFor, kontonummerInfo }, + } = useDeltakerContext(); + + const deltakelse = deltakelserSøktFor[0]; + // const { programPeriode } = deltakelse; + + useEffectOnce(() => { + setBreadcrumbs([ + { title: 'Min side', url: '/dittnav/' }, + { title: 'Ungdomsprogrammet', url: appEnv[EnvKey.PUBLIC_PATH] }, + ]); + }); + + return ( + + + + {/* + + Hei {søker.fornavn} + + + Du er meldt på av din veileder til å være med i ungdomsprogrammet fra og med{' '} + {dateFormatter.dateShortMonthYear(programPeriode.from)}. + + */} + {kontonummerInfo && kontonummerInfo.kontoregisterStatus === 'FAILURE' ? ( + + ) : null} + + + + + + ); +}; + +export default InnsynForside; diff --git "a/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/S\303\270knad.tsx" "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/S\303\270knad.tsx" new file mode 100644 index 0000000000..5520e8cb21 --- /dev/null +++ "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/S\303\270knad.tsx" @@ -0,0 +1,79 @@ +import { Alert } from '@navikt/ds-react'; +import { RegistrertBarn, Søker } from '@navikt/sif-common-api'; +import { guid } from '@navikt/sif-common-utils'; +import useSWR from 'swr'; +import { + MellomlagringData, + mellomlagringEndpointUrl, + mellomlagringIsValid, + mellomlagringService, +} from '../../api/services/mellomlagringService'; +import { Deltakelse } from '../../api/types'; +import { useDeltakerContext } from '../../context/DeltakerContext'; +import { StepFormValuesContextProvider } from './context/StepFormValuesContext'; +import { SøknadContextProvider } from './context/SøknadContext'; +import { SøknadContextState } from './context/SøknadContextState'; +import SøknadRouter from './SøknadRouter'; +import LoadingSpinner from '@navikt/sif-common-core-ds/src/atoms/loading-spinner/LoadingSpinner'; +import Page from '@navikt/sif-common-core-ds/src/components/page/Page'; +import { SOKNAD_VERSJON } from './utils/søknadUtils'; +import { Søknadsdata } from './types/søknadsdata/Søknadsdata'; + +const getInitialSøknadsdata = ( + søker: Søker, + registrerteBarn: RegistrertBarn[], + deltakelse: Deltakelse, + mellomlagring?: MellomlagringData, +): SøknadContextState => { + const søknadsdata: Søknadsdata = + mellomlagring && mellomlagringIsValid(mellomlagring, søker, deltakelse) + ? mellomlagring.søknadsdata + : { id: guid(), deltakelse }; + return { + søker, + registrerteBarn, + versjon: SOKNAD_VERSJON, + søknadsdata, + }; +}; + +const Søknad = () => { + const { + data: { barn, deltakelserIkkeSøktFor, søker }, + } = useDeltakerContext(); + + const { + data: mellomlagring, + error, + isLoading, + } = useSWR(mellomlagringEndpointUrl, mellomlagringService.fetcher); + + if (isLoading) { + return ( + +
+ +
+
+ ); + } + + if (error) { + return ( + + Feil: {JSON.stringify(error)} + + ); + } + + return ( + + + + + + ); +}; + +export default Søknad; diff --git "a/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/S\303\270knadRouter.tsx" "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/S\303\270knadRouter.tsx" new file mode 100644 index 0000000000..edcf819a0d --- /dev/null +++ "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/S\303\270knadRouter.tsx" @@ -0,0 +1,70 @@ +import { Navigate, Route, Routes, useLocation, useNavigate } from 'react-router-dom'; +import IntroPage from './pages/intro/IntroPage'; +import VelkommenPage from './pages/velkommen/VelkommenPage'; +import BarnStep from './steps/barn-step/BarnStep'; +import ArbeidstidStep from './steps/arbeidstid-step/ArbeidstidStep'; +import OppsummeringStep from './steps/oppsummering-step/OppsummeringStep'; +import { SøknadRoutes } from './types/SøknadRoutes'; +import KvitteringPage from './pages/kvittering/KvitteringPage'; +import { useSøknadContext } from './context/hooks/useSøknadContext'; +import { useEffect, useState } from 'react'; +import { usePersistSøknadState } from './hooks/usePersistSøknadState'; +import { relocateToRootPage, relocateToWelcomePage } from './utils/navigationUtils'; + +const SøknadRouter = () => { + const { + state: { søknadRoute, søknadsdata, søknadSendt }, + } = useSøknadContext(); + + const [isFirstTimeLoadingApp, setIsFirstTimeLoadingApp] = useState(true); + const { pathname } = useLocation(); + const navigateTo = useNavigate(); + usePersistSøknadState(); + + useEffect(() => { + if (søknadRoute && isFirstTimeLoadingApp) { + setIsFirstTimeLoadingApp(false); + navigateTo(søknadRoute); + } + if (pathname === SøknadRoutes.VELKOMMEN && søknadRoute === SøknadRoutes.SØKNAD_SENDT && søknadSendt) { + relocateToWelcomePage(); + } + if ( + pathname === SøknadRoutes.VELKOMMEN && + søknadRoute && + søknadRoute !== SøknadRoutes.SØKNAD_SENDT && + !søknadSendt + ) { + navigateTo(søknadRoute); // Send til sider dersom bruker kommer til velkommen via annen navigasjon + } + }, [navigateTo, pathname, søknadRoute, isFirstTimeLoadingApp]); + + if (søknadSendt && søknadRoute && søknadRoute !== SøknadRoutes.SØKNAD_SENDT) { + relocateToRootPage(); + } + + if (søknadsdata.velkommen?.harForståttRettigheterOgPlikter !== true) { + return ( + + } /> + } /> + } /> + } /> + + ); + } + + return ( + + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + ); +}; + +export default SøknadRouter; diff --git "a/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/S\303\270knadStep.tsx" "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/S\303\270knadStep.tsx" new file mode 100644 index 0000000000..8f16b81eec --- /dev/null +++ "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/S\303\270knadStep.tsx" @@ -0,0 +1,46 @@ +import React from 'react'; +import { useIntl } from 'react-intl'; +import { soknadStepUtils, Step } from '@navikt/sif-common-soknad-ds'; +import { useAppIntl } from '../../i18n'; +import InvalidStepSøknadsdataInfo from './components/invalid-step-søknadsdata-info/InvalidStepSøknadsdataInfo'; +import useAvbrytEllerFortsettSenere from './hooks/useAvbrytSøknad'; +import { getSøknadStepConfig } from './søknadStepConfig'; +import { StepId } from './types/StepId'; +import { useSøknadContext } from './context/hooks/useSøknadContext'; + +interface Props { + stepId: StepId; + children: React.ReactNode; +} + +const SøknadStep: React.FunctionComponent = ({ stepId, children }) => { + const intl = useIntl(); + const { text } = useAppIntl(); + const { + state: { + søknadsdata: { deltakelse }, + }, + } = useSøknadContext(); + + const stepConfig = getSøknadStepConfig(deltakelse); + + const { avbrytSøknad, fortsettSøknadSenere } = useAvbrytEllerFortsettSenere(); + + const { index } = stepConfig[stepId]; + + const steps = soknadStepUtils.getProgressStepsFromConfig(stepConfig, index, intl); + + return ( + + + {children} + + ); +}; + +export default SøknadStep; diff --git "a/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/components/BehandlingAvPersonopplysningerContent.tsx" "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/components/BehandlingAvPersonopplysningerContent.tsx" new file mode 100644 index 0000000000..404a6c7df8 --- /dev/null +++ "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/components/BehandlingAvPersonopplysningerContent.tsx" @@ -0,0 +1,56 @@ +import { Heading, Link, List, VStack } from '@navikt/ds-react'; +import { AppText } from '@i18n/index'; +import getLenker from '../../../lenker'; + +const BehandlingAvPersonopplysningerContent = () => { + return ( + +
+ + + +

+ +

+
+
+ + + +

+ +

+ + + + + + + + + + + + + + +
+
+

+ ( + + {children} + + ), + }} + /> +

+
+
+ ); +}; + +export default BehandlingAvPersonopplysningerContent; diff --git "a/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/components/KortOmUngdomsprogrammet.tsx" "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/components/KortOmUngdomsprogrammet.tsx" new file mode 100644 index 0000000000..8dbdda0d3c --- /dev/null +++ "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/components/KortOmUngdomsprogrammet.tsx" @@ -0,0 +1,17 @@ +import { Heading, ReadMore, VStack } from '@navikt/ds-react'; + +const KortOmUngdomsprogrammet = () => { + return ( + + + Kort om Ungdomsprogrammet + + Lorem + Lorem + Lorem + Lorem + + ); +}; + +export default KortOmUngdomsprogrammet; diff --git "a/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/components/OmS\303\270knaden.tsx" "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/components/OmS\303\270knaden.tsx" new file mode 100644 index 0000000000..9c905bf4d4 --- /dev/null +++ "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/components/OmS\303\270knaden.tsx" @@ -0,0 +1,39 @@ +import { AppText, useAppIntl } from '@i18n/index'; +import { Accordion, Box, Heading, List, VStack } from '@navikt/ds-react'; +import BehandlingAvPersonopplysningerContent from './BehandlingAvPersonopplysningerContent'; + +const OmSøknaden = () => { + const { text } = useAppIntl(); + return ( + <> + + + + + + + + + + + + + + + + + + + + {text('omSøknaden.4')} + + + + + + + + ); +}; + +export default OmSøknaden; diff --git "a/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/components/VelkommenGuideContent.tsx" "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/components/VelkommenGuideContent.tsx" new file mode 100644 index 0000000000..457c7ee5da --- /dev/null +++ "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/components/VelkommenGuideContent.tsx" @@ -0,0 +1,11 @@ +import { BodyLong } from '@navikt/ds-react'; + +const VelkommenGuideContent = () => ( + <> + + Introtekst? + + +); + +export default VelkommenGuideContent; diff --git "a/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/components/invalid-step-s\303\270knadsdata-info/InvalidStepS\303\270knadsdataInfo.tsx" "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/components/invalid-step-s\303\270knadsdata-info/InvalidStepS\303\270knadsdataInfo.tsx" new file mode 100644 index 0000000000..91e9d8dacd --- /dev/null +++ "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/components/invalid-step-s\303\270knadsdata-info/InvalidStepS\303\270knadsdataInfo.tsx" @@ -0,0 +1,51 @@ +import React from 'react'; +import { Alert, Heading, Link } from '@navikt/ds-react'; +import { useNavigate } from 'react-router-dom'; +import FormBlock from '@navikt/sif-common-core-ds/src/atoms/form-block/FormBlock'; +import { SoknadStepsConfig } from '@navikt/sif-common-soknad-ds'; +import { StepId } from '../../types/StepId'; +import { getSøknadStepRoute } from '../../utils/søknadRoutesUtils'; +import { useAppIntl } from '../../../../i18n'; +import { useSøknadsdataStatus } from '../../hooks/useSøknadsdataStatus'; + +interface Props { + stepId: StepId; + stepConfig: SoknadStepsConfig; +} + +const InvalidStepSøknadsdataInfo: React.FunctionComponent = ({ stepId, stepConfig }) => { + const { text } = useAppIntl(); + const navigate = useNavigate(); + const { invalidSteps } = useSøknadsdataStatus(stepId, stepConfig); + + if (invalidSteps.length > 0) { + const step = invalidSteps[0]; + const stepTitle = text(stepConfig[step].stepTitleIntlKey as any); + const stepRoute = getSøknadStepRoute(step); + const getStepLink = () => ( + { + evt.stopPropagation(); + evt.preventDefault(); + navigate(stepRoute); + }}> + {stepTitle} + + ); + return ( + + + + Oops, dette stemmer ikke helt + + Vennligst gå tilbake til steget "{getStepLink()}", og bruk knappene nederst i skjemaet for + å gå videre. Ikke bruk frem og tilbake-funksjonaliteten i nettleseren. + + + ); + } + return null; +}; + +export default InvalidStepSøknadsdataInfo; diff --git "a/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/context/StepFormValuesContext.tsx" "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/context/StepFormValuesContext.tsx" new file mode 100644 index 0000000000..78b17b3210 --- /dev/null +++ "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/context/StepFormValuesContext.tsx" @@ -0,0 +1,41 @@ +import React, { createContext, FunctionComponent, useContext, useState } from 'react'; +import { StepFormValues } from '../types/StepFormValues'; +import { StepId } from '../types/StepId'; + +interface StepFormValuesContextInterface { + stepFormValues: StepFormValues; + clearAllSteps: () => void; + clearStepFormValues: (stepId: StepId) => void; + setStepFormValues: (stepId: StepId, formValues: StepFormValues) => void; +} + +// eslint-disable-next-line @typescript-eslint/no-non-null-assertion +export const StepFormValuesContext = createContext(null!); + +export const useStepFormValuesContext = () => useContext(StepFormValuesContext); + +interface Props { + children: React.ReactNode; + initialValues?: StepFormValues; +} + +export const StepFormValuesContextProvider: FunctionComponent = ({ children, initialValues }) => { + const [values, setValues] = useState(initialValues || {}); + return ( + { + setValues({ ...values, [stepId]: undefined }); + }, + clearAllSteps: () => { + setValues({}); + }, + setStepFormValues: (stepId: StepId, formValues: StepFormValues) => { + setValues({ ...values, [stepId]: formValues[stepId] }); + }, + }}> + {children} + + ); +}; diff --git "a/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/context/S\303\270knadContext.tsx" "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/context/S\303\270knadContext.tsx" new file mode 100644 index 0000000000..312f7aef0e --- /dev/null +++ "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/context/S\303\270knadContext.tsx" @@ -0,0 +1,27 @@ +import { createContext, Dispatch, FunctionComponent, ReactNode, useMemo, useReducer } from 'react'; +import { SøknadContextAction } from './action/actionCreator'; +import { søknadReducer } from './reducer/søknadReducer'; +import { SøknadContextState } from './SøknadContextState'; + +interface SøknadContextData { + state: SøknadContextState; + dispatch: Dispatch; +} + +// eslint-disable-next-line @typescript-eslint/no-non-null-assertion +export const SøknadContext = createContext(null!); + +interface Props { + initialData: SøknadContextState; + children: ReactNode; +} + +export const SøknadContextProvider: FunctionComponent = ({ children, initialData }) => { + const [state, dispatch] = useReducer(søknadReducer, initialData); + + const contextValue = useMemo(() => { + return { state, dispatch }; + }, [state, dispatch]); + + return {children}; +}; diff --git "a/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/context/S\303\270knadContextState.ts" "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/context/S\303\270knadContextState.ts" new file mode 100644 index 0000000000..798954e90a --- /dev/null +++ "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/context/S\303\270knadContextState.ts" @@ -0,0 +1,14 @@ +import { RegistrertBarn, Søker } from '@navikt/sif-common-api'; +import { Søknadsdata } from '../types/søknadsdata/Søknadsdata'; +import { SøknadRoutes } from '../types/SøknadRoutes'; + +export interface SøknadContextState { + versjon: string; + søker: Søker; + registrerteBarn: RegistrertBarn[]; + søknadsdata: Søknadsdata; + søknadRoute?: SøknadRoutes; + søknadSendt?: boolean; + børMellomlagres?: boolean; + isReloadingApp?: boolean; +} diff --git "a/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/context/action/actionCreator.ts" "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/context/action/actionCreator.ts" new file mode 100644 index 0000000000..5aa6a5bd1a --- /dev/null +++ "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/context/action/actionCreator.ts" @@ -0,0 +1,145 @@ +import { OppsummeringFormValues } from '../../steps/oppsummering-step/OppsummeringStep'; +import { SøknadRoutes } from '../../types/SøknadRoutes'; +import { ArbeidstidSøknadsdata } from '../../types/søknadsdata/ArbeidstidSøknadsdata'; +import { BarnSøknadsdata } from '../../types/søknadsdata/BarnSøknadsdata'; + +export enum SøknadContextActionKeys { + RESET_SØKNAD = 'resetSøknad', + SET_IS_RELOADING_APP = 'setIsReloadingApp', + START_SØKNAD = 'startSøknad', + AVBRYT_SØKNAD = 'avbrytSøknad', + FORTSETT_SØKNAD_SENERE = 'fortsettSøknadSenere', + SET_SØKNAD_ROUTE = 'setSøknadRoute', + SET_SØKNAD_BARN = 'setSøknadBarn', + SET_SØKNAD_ARBEIDSTID = 'setSøknadArbeidstid', + SET_SØKNAD_HAR_BEKREFTET_OPPLYSNINGER = 'setSøknadHarBekreftetOpplysninger', + REQUEST_LAGRE_SØKNAD = 'requestLargeSøknad', + SET_SØKNAD_LAGRET = 'setSøknadLagret', + SET_SØKNAD_SENDT = 'setSøknadSendt', + SET_UNSUBMITTED_STEP_FORM_VALUES = 'setUnsubmittedStepFormValues', +} + +interface ResetSøknad { + type: SøknadContextActionKeys.RESET_SØKNAD; +} +interface SetIsReloadingApp { + type: SøknadContextActionKeys.SET_IS_RELOADING_APP; +} +interface StartSøknad { + type: SøknadContextActionKeys.START_SØKNAD; +} +interface AvbrytSøknad { + type: SøknadContextActionKeys.AVBRYT_SØKNAD; +} +interface FortsettSøknadSenere { + type: SøknadContextActionKeys.FORTSETT_SØKNAD_SENERE; +} +interface RequestLagreSøknad { + type: SøknadContextActionKeys.REQUEST_LAGRE_SØKNAD; +} +interface SetSøknadLagret { + type: SøknadContextActionKeys.SET_SØKNAD_LAGRET; +} +interface SetSøknadSendt { + type: SøknadContextActionKeys.SET_SØKNAD_SENDT; +} +interface SetSøknadRoute { + type: SøknadContextActionKeys.SET_SØKNAD_ROUTE; + payload: SøknadRoutes; +} + +interface SetSøknadBarn { + type: SøknadContextActionKeys.SET_SØKNAD_BARN; + payload: BarnSøknadsdata; +} + +interface SetSøknadArbeidstid { + type: SøknadContextActionKeys.SET_SØKNAD_ARBEIDSTID; + payload: ArbeidstidSøknadsdata; +} + +interface SetSøknadHarBekreftetOpplysninger { + type: SøknadContextActionKeys.SET_SØKNAD_HAR_BEKREFTET_OPPLYSNINGER; + payload: OppsummeringFormValues; +} + +const resetSøknad = (): ResetSøknad => ({ + type: SøknadContextActionKeys.RESET_SØKNAD, +}); + +const setIsReloadingApp = (): SetIsReloadingApp => ({ + type: SøknadContextActionKeys.SET_IS_RELOADING_APP, +}); + +const startSøknad = (): StartSøknad => ({ + type: SøknadContextActionKeys.START_SØKNAD, +}); + +const avbrytSøknad = (): AvbrytSøknad => ({ + type: SøknadContextActionKeys.AVBRYT_SØKNAD, +}); + +const fortsettSøknadSenere = (): FortsettSøknadSenere => ({ + type: SøknadContextActionKeys.FORTSETT_SØKNAD_SENERE, +}); + +const requestLagreSøknad = (): RequestLagreSøknad => ({ + type: SøknadContextActionKeys.REQUEST_LAGRE_SØKNAD, +}); + +const setSøknadLagret = (): SetSøknadLagret => ({ + type: SøknadContextActionKeys.SET_SØKNAD_LAGRET, +}); +const setSøknadSendt = (): SetSøknadSendt => ({ + type: SøknadContextActionKeys.SET_SØKNAD_SENDT, +}); + +const setSøknadBarn = (payload: BarnSøknadsdata): SetSøknadBarn => ({ + type: SøknadContextActionKeys.SET_SØKNAD_BARN, + payload, +}); + +const setSøknadArbeidstid = (payload: ArbeidstidSøknadsdata): SetSøknadArbeidstid => ({ + type: SøknadContextActionKeys.SET_SØKNAD_ARBEIDSTID, + payload, +}); + +const setSøknadHarBekreftetOpplysninger = (payload: OppsummeringFormValues): SetSøknadHarBekreftetOpplysninger => ({ + type: SøknadContextActionKeys.SET_SØKNAD_HAR_BEKREFTET_OPPLYSNINGER, + payload, +}); +const setSøknadRoute = (payload: SøknadRoutes): SetSøknadRoute => ({ + type: SøknadContextActionKeys.SET_SØKNAD_ROUTE, + payload, +}); + +export type SøknadContextAction = + | StartSøknad + | AvbrytSøknad + | ResetSøknad + | SetIsReloadingApp + | FortsettSøknadSenere + | RequestLagreSøknad + | SetSøknadLagret + | SetSøknadSendt + | SetSøknadBarn + | SetSøknadArbeidstid + | SetSøknadHarBekreftetOpplysninger + | SetSøknadRoute; + +const actionsCreator = { + resetSøknad, + startSøknad, + avbrytSøknad, + fortsettSøknadSenere, + requestLagreSøknad, + setIsReloadingApp, + setSøknadRoute, + setSøknadBarn, + setSøknadArbeidstid, + setSøknadHarBekreftetOpplysninger, + setSøknadLagret, + setSøknadSendt, +}; + +export default actionsCreator; diff --git "a/apps/ungdomsytelse-deltaker/src/s\303\270knad/hooks/useS\303\270knadContext.ts" "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/context/hooks/useS\303\270knadContext.ts" similarity index 63% rename from "apps/ungdomsytelse-deltaker/src/s\303\270knad/hooks/useS\303\270knadContext.ts" rename to "apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/context/hooks/useS\303\270knadContext.ts" index e6777b2372..94b89a6cb4 100644 --- "a/apps/ungdomsytelse-deltaker/src/s\303\270knad/hooks/useS\303\270knadContext.ts" +++ "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/context/hooks/useS\303\270knadContext.ts" @@ -1,4 +1,4 @@ import { useContext } from 'react'; -import { SøknadContext } from '../context/SøknadContext'; +import { SøknadContext } from '../SøknadContext'; export const useSøknadContext = () => useContext(SøknadContext); diff --git "a/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/context/reducer/s\303\270knadReducer.ts" "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/context/reducer/s\303\270knadReducer.ts" new file mode 100644 index 0000000000..8a949ee487 --- /dev/null +++ "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/context/reducer/s\303\270knadReducer.ts" @@ -0,0 +1,115 @@ +import { guid } from '@navikt/sif-common-utils'; +import { SøknadRoutes } from '../../types/SøknadRoutes'; +import { Søknadsdata } from '../../types/søknadsdata/Søknadsdata'; +import { SøknadContextAction, SøknadContextActionKeys } from '../action/actionCreator'; +import { SøknadContextState } from '../SøknadContextState'; + +export const initialSøknadsdata: Søknadsdata = { + id: undefined, +} as any; + +export const søknadReducer = (state: SøknadContextState, action: SøknadContextAction): SøknadContextState => { + switch (action.type) { + case SøknadContextActionKeys.START_SØKNAD: + return { + ...state, + søknadsdata: { + id: guid(), + deltakelse: state.søknadsdata.deltakelse, + velkommen: { + harForståttRettigheterOgPlikter: true, + }, + }, + søknadRoute: SøknadRoutes.BARN, + børMellomlagres: true, + }; + case SøknadContextActionKeys.AVBRYT_SØKNAD: + return { + ...state, + søknadsdata: initialSøknadsdata, + søknadRoute: SøknadRoutes.VELKOMMEN, + }; + } + + if (state.søknadsdata) { + switch (action.type) { + case SøknadContextActionKeys.SET_SØKNAD_ROUTE: + return { + ...state, + søknadRoute: action.payload, + }; + case SøknadContextActionKeys.REQUEST_LAGRE_SØKNAD: + return { + ...state, + børMellomlagres: true, + }; + case SøknadContextActionKeys.SET_SØKNAD_LAGRET: + return { + ...state, + børMellomlagres: false, + }; + case SøknadContextActionKeys.SET_SØKNAD_BARN: { + const søknadsdata: Søknadsdata = { + ...state.søknadsdata, + barn: { + ...action.payload, + }, + }; + + return { + ...state, + søknadsdata, + }; + } + + case SøknadContextActionKeys.SET_SØKNAD_ARBEIDSTID: + return { + ...state, + søknadsdata: { + ...state.søknadsdata, + arbeidstid: { + ...action.payload, + }, + }, + }; + + case SøknadContextActionKeys.SET_SØKNAD_HAR_BEKREFTET_OPPLYSNINGER: + return { + ...state, + søknadsdata: { + ...state.søknadsdata, + oppsummering: { + harBekreftetOpplysninger: action.payload.harBekreftetOpplysninger, + }, + }, + }; + case SøknadContextActionKeys.SET_SØKNAD_SENDT: + return { + ...state, + børMellomlagres: false, + søknadsdata: initialSøknadsdata, + søknadRoute: SøknadRoutes.SØKNAD_SENDT, + søknadSendt: true, + }; + case SøknadContextActionKeys.RESET_SØKNAD: + return { + ...state, + børMellomlagres: false, + søknadsdata: initialSøknadsdata, + søknadSendt: false, + søknadRoute: SøknadRoutes.VELKOMMEN, + }; + case SøknadContextActionKeys.FORTSETT_SØKNAD_SENERE: + return { + ...state, + børMellomlagres: true, + }; + case SøknadContextActionKeys.SET_IS_RELOADING_APP: + return { + ...state, + isReloadingApp: true, + }; + } + } + return state; +}; diff --git "a/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/hooks/useAvbrytS\303\270knad.ts" "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/hooks/useAvbrytS\303\270knad.ts" new file mode 100644 index 0000000000..497ac06825 --- /dev/null +++ "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/hooks/useAvbrytS\303\270knad.ts" @@ -0,0 +1,31 @@ +import { useCallback } from 'react'; +import { useNavigate } from 'react-router-dom'; +import actionsCreator from '../context/action/actionCreator'; +import { useSøknadContext } from '../context/hooks/useSøknadContext'; +import { useStepFormValuesContext } from '../context/StepFormValuesContext'; +import { SøknadRoutes } from '../types/SøknadRoutes'; +import { relocateToRootPage } from '../utils/navigationUtils'; +import { mellomlagringService } from '../../../api/services/mellomlagringService'; + +const useAvbrytEllerFortsettSenere = () => { + const navigate = useNavigate(); + const { dispatch } = useSøknadContext(); + const { clearAllSteps } = useStepFormValuesContext(); + + const avbrytSøknad = useCallback(async () => { + await mellomlagringService.purge(); + clearAllSteps(); + dispatch(actionsCreator.avbrytSøknad()); + navigate(SøknadRoutes.VELKOMMEN); + }, [navigate, clearAllSteps, dispatch]); + + const fortsettSøknadSenere = useCallback(() => { + clearAllSteps(); + dispatch(actionsCreator.fortsettSøknadSenere()); + relocateToRootPage(); + }, [clearAllSteps, dispatch]); + + return { avbrytSøknad, fortsettSøknadSenere }; +}; + +export default useAvbrytEllerFortsettSenere; diff --git "a/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/hooks/useOnValidSubmit.ts" "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/hooks/useOnValidSubmit.ts" new file mode 100644 index 0000000000..e1aac37a9b --- /dev/null +++ "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/hooks/useOnValidSubmit.ts" @@ -0,0 +1,66 @@ +import { useEffect, useState } from 'react'; +import { useNavigate } from 'react-router-dom'; +import actionsCreator, { SøknadContextAction } from '../context/action/actionCreator'; +import { SøknadContextState } from '../context/SøknadContextState'; +import { useSøknadContext } from '../context/hooks/useSøknadContext'; +import { getSøknadStepConfig } from '../søknadStepConfig'; +import { getSøknadStepRoute } from '../utils/søknadRoutesUtils'; +import { relocateToLoginPage } from '../utils/navigationUtils'; +import { StepId } from '../types/StepId'; + +export const useOnValidSubmit = ( + submitHandler: (values: T) => SøknadContextAction[], + stepId: StepId, + postSubmit?: (state: SøknadContextState) => Promise, +) => { + const { dispatch, state } = useSøknadContext(); + const navigate = useNavigate(); + const [hasSubmitted, setSubmitted] = useState(false); + const [isSubmitting, setIsSubmitting] = useState(false); + const [submitError, setSubmitError] = useState(undefined); + + const { nextStep } = getSøknadStepConfig(state.søknadsdata.deltakelse)[stepId]; + + useEffect(() => { + if (hasSubmitted && postSubmit) { + postSubmit(state) + .then(() => { + if (nextStep) { + navigate(getSøknadStepRoute(nextStep)); + } + }) + .catch((error) => { + if (error.response && (error.response.status === 401 || error.response.status === 403)) { + relocateToLoginPage(); + } else { + setSubmitError(error); + } + }); + } + }, [hasSubmitted, navigate, nextStep, state, postSubmit]); + + useEffect(() => { + if (submitError) { + throw new Error(submitError); + } + }, [submitError]); + + const dispatchAction = (action: SøknadContextAction | undefined): void => { + if (action) { + dispatch(action); + } + }; + + const handleSubmit = (values: T) => { + setIsSubmitting(true); + const actions = [ + nextStep === undefined || nextStep === StepId.KVITTERING + ? undefined + : dispatch(actionsCreator.setSøknadRoute(getSøknadStepRoute(nextStep))), + ...submitHandler(values), + ]; + Promise.all([...actions.map(dispatchAction)]).then(() => setSubmitted(true)); + }; + + return { handleSubmit, isSubmitting }; +}; diff --git "a/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/hooks/usePersistS\303\270knadState.ts" "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/hooks/usePersistS\303\270knadState.ts" new file mode 100644 index 0000000000..64676b08ac --- /dev/null +++ "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/hooks/usePersistS\303\270knadState.ts" @@ -0,0 +1,21 @@ +import { useEffect, useState } from 'react'; +import { useSøknadContext } from '../context/hooks/useSøknadContext'; +import { lagreSøknadState } from '../utils/lagreSøknadState'; +import actionsCreator from '../context/action/actionCreator'; + +export const usePersistSøknadState = () => { + const { dispatch, state } = useSøknadContext(); + const [pending, setPending] = useState(false); + + useEffect(() => { + if (state.børMellomlagres) { + setPending(true); + lagreSøknadState(state).then(() => { + dispatch(actionsCreator.setSøknadLagret()); + setPending(false); + }); + } + }, [state, dispatch]); + + return { pending }; +}; diff --git "a/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/hooks/useResetS\303\270knad.ts" "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/hooks/useResetS\303\270knad.ts" new file mode 100644 index 0000000000..284ad315c7 --- /dev/null +++ "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/hooks/useResetS\303\270knad.ts" @@ -0,0 +1,26 @@ +import { useEffect, useState } from 'react'; +import actionsCreator from '../context/action/actionCreator'; +import { useSøknadContext } from '../context/hooks/useSøknadContext'; +import { relocateToWelcomePage } from '../utils/navigationUtils'; + +/** + * Nullstiller søknad etter at søker har sendt inn melding + * og navigerer bort fra kvitteringssiden. + */ + +export const useResetSøknad = () => { + const [shouldResetSøknad, setShouldResetSøknad] = useState(false); + const { dispatch } = useSøknadContext(); + + useEffect(() => { + if (shouldResetSøknad) { + dispatch(actionsCreator.resetSøknad()); + dispatch(actionsCreator.setIsReloadingApp()); + setTimeout(() => { + relocateToWelcomePage(); + }); + } + }, [shouldResetSøknad, dispatch]); + + return { setShouldResetSøknad, shouldResetSøknad }; +}; diff --git "a/apps/ungdomsytelse-deltaker/src/s\303\270knad/hooks/useSendS\303\270knad.ts" "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/hooks/useSendS\303\270knad.ts" similarity index 51% rename from "apps/ungdomsytelse-deltaker/src/s\303\270knad/hooks/useSendS\303\270knad.ts" rename to "apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/hooks/useSendS\303\270knad.ts" index 5c758a7288..89e5b35fcb 100644 --- "a/apps/ungdomsytelse-deltaker/src/s\303\270knad/hooks/useSendS\303\270knad.ts" +++ "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/hooks/useSendS\303\270knad.ts" @@ -1,34 +1,39 @@ import { useState } from 'react'; +import { OmsorgsdagerAleneomsorgApp } from '@navikt/sif-app-register'; +import { useAmplitudeInstance } from '@navikt/sif-common-amplitude'; import { AxiosError } from 'axios'; -import { sendSøknadService } from '../../api/services/sendSøknadService'; -import { SøknadApiData } from '../../api/types'; -import { deltakerService } from '../../api/services/deltakerService'; +import { sendSøknadService } from '../../../api/services/sendSøknadService'; +import { SøknadApiData } from '../../../api/types'; +import { useAppIntl } from '../../../i18n'; +import { mellomlagringService } from '../../../api/services/mellomlagringService'; +import { relocateToKvittering } from '../utils/navigationUtils'; export const useSendSøknad = () => { const [isSubmitting, setIsSubmitting] = useState(false); const [sendSøknadError, setSendSøknadError] = useState(); - const [søknadSendt, setSøknadSendt] = useState(false); + const { locale } = useAppIntl(); - const sendSøknad = async (apiData: SøknadApiData) => { + const { logSoknadSent } = useAmplitudeInstance(); + + const sendSøknad = (apiData: SøknadApiData) => { setIsSubmitting(true); - await sendSøknadService + sendSøknadService .sendSøknad(apiData) .then(onSøknadSendSuccess) .catch((error) => { - setSøknadSendt(false); setSendSøknadError(error); setIsSubmitting(false); }); - await deltakerService.putMarkerHarSøkt(apiData.id); }; const onSøknadSendSuccess = async () => { - setSøknadSendt(true); + await logSoknadSent(OmsorgsdagerAleneomsorgApp.key, locale); + await mellomlagringService.purge(); setIsSubmitting(false); + relocateToKvittering(); }; const resetSendSøknad = () => { - setSøknadSendt(false); setIsSubmitting(false); setSendSøknadError(undefined); }; @@ -37,7 +42,6 @@ export const useSendSøknad = () => { resetSendSøknad, sendSøknad, isSubmitting, - søknadSendt, sendSøknadError, }; }; diff --git "a/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/hooks/useStepNavigation.ts" "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/hooks/useStepNavigation.ts" new file mode 100644 index 0000000000..e5a1d03c3d --- /dev/null +++ "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/hooks/useStepNavigation.ts" @@ -0,0 +1,41 @@ +import { useNavigate } from 'react-router-dom'; +import { StepConfig } from '@navikt/sif-common-soknad-ds'; +import { StepId } from '../types/StepId'; +import { getSøknadStepRoute } from '../utils/søknadRoutesUtils'; +import { useSøknadContext } from '../context/hooks/useSøknadContext'; +import actionsCreator from '../context/action/actionCreator'; + +/** Bør nok brukes med måte, i og med en denne ikke vil submitte skjema og oppdatere søknadsdata */ + +export const useStepNavigation = (step?: StepConfig) => { + const { dispatch } = useSøknadContext(); + const navigate = useNavigate(); + + const gotoStep = (stepId: StepId) => { + const route = getSøknadStepRoute(stepId); + dispatch(actionsCreator.setSøknadRoute(route)); + dispatch(actionsCreator.requestLagreSøknad()); + setTimeout(() => { + navigate(route); + }); + }; + + const goToPreviousStep = () => { + if (step?.previousStep) { + gotoStep(step?.previousStep); + } + }; + + const goToNextStep = () => { + if (step?.nextStep) { + if (step?.previousStep) { + gotoStep(step?.nextStep); + } + } + }; + + return { + goBack: step?.previousStep ? goToPreviousStep : undefined, + goNext: step?.nextStep ? goToNextStep : undefined, + }; +}; diff --git "a/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/hooks/useS\303\270knadsdataStatus.ts" "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/hooks/useS\303\270knadsdataStatus.ts" new file mode 100644 index 0000000000..0e7f1f76ad --- /dev/null +++ "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/hooks/useS\303\270knadsdataStatus.ts" @@ -0,0 +1,77 @@ +import { useState } from 'react'; +import isEqual from 'react-fast-compare'; +import { RegistrertBarn } from '@navikt/sif-common-api'; +import { useEffectOnce } from '@navikt/sif-common-hooks'; +import { SoknadStepsConfig } from '@navikt/sif-common-soknad-ds'; +import { useSøknadContext } from '../context/hooks/useSøknadContext'; +import { useStepFormValuesContext } from '../context/StepFormValuesContext'; +import { ArbeidstidFormValues } from '../steps/arbeidstid-step/ArbeidstidStep'; +import { getArbeidstidSøknadsdataFromFormValues } from '../steps/arbeidstid-step/arbeidstidStepUtils'; +import { getBarnSøknadsdataFromFormValues } from '../steps/barn-step/barnStepUtils'; +import { StepFormValues } from '../types/StepFormValues'; +import { StepId } from '../types/StepId'; +import { Søknadsdata } from '../types/søknadsdata/Søknadsdata'; + +const getPrecedingSteps = (currentStepIndex: number, stepConfig: SoknadStepsConfig): StepId[] => { + return Object.keys(stepConfig).filter((_key, idx) => idx < currentStepIndex) as StepId[]; +}; + +const getStepSøknadsdataFromStepFormValues = ( + step: StepId, + stepFormValues: StepFormValues, + registrertBarn: RegistrertBarn[], +) => { + const formValues = stepFormValues[step]; + if (!formValues) { + return undefined; + } + switch (step) { + case StepId.BARN: + return getBarnSøknadsdataFromFormValues(registrertBarn); + case StepId.ARBEIDSTID: + return getArbeidstidSøknadsdataFromFormValues(formValues as ArbeidstidFormValues); + } + return undefined; +}; + +export const isStepFormValuesAndStepSøknadsdataValid = ( + step: StepId, + stepFormValues: StepFormValues, + søknadsdata: Søknadsdata, + registrertBarn: RegistrertBarn[], +): boolean => { + if (stepFormValues[step]) { + const stepSøknadsdata = søknadsdata[step]; + const tempSøknadsdata = getStepSøknadsdataFromStepFormValues(step, stepFormValues, registrertBarn); + if (!stepSøknadsdata || !isEqual(tempSøknadsdata, stepSøknadsdata)) { + return false; + } + } + return true; +}; + +export const useSøknadsdataStatus = (stepId: StepId, stepConfig: SoknadStepsConfig) => { + const [invalidSteps, setInvalidSteps] = useState([]); + + const { + state: { søknadsdata, registrerteBarn: registrertBarn }, + } = useSøknadContext(); + + const { stepFormValues } = useStepFormValuesContext(); + + useEffectOnce(() => { + const currentStep = stepConfig[stepId]; + const ip = []; + const precedingSteps = getPrecedingSteps(currentStep.index, stepConfig); + + precedingSteps.forEach((step) => { + if (isStepFormValuesAndStepSøknadsdataValid(step, stepFormValues, søknadsdata, registrertBarn) === false) { + ip.push(step); + } + }); + + setInvalidSteps(ip); + }); + + return { invalidSteps, hasInvalidSteps: invalidSteps.length > 0 }; +}; diff --git "a/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/i18n/index.ts" "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/i18n/index.ts" new file mode 100644 index 0000000000..1c52f21921 --- /dev/null +++ "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/i18n/index.ts" @@ -0,0 +1,8 @@ +const nb = { + 'step.barn.stepTitle': 'Barn', + 'step.arbeidstid.stepTitle': 'Arbeidstid', + 'step.oppsummering.stepTitle': 'Oppsummering', +}; +export const søknadMessages = { + nb, +}; diff --git "a/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/pages/intro/IntroPage.tsx" "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/pages/intro/IntroPage.tsx" new file mode 100644 index 0000000000..1a8009f89f --- /dev/null +++ "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/pages/intro/IntroPage.tsx" @@ -0,0 +1,65 @@ +import { Alert, BodyLong, Box, Button, Heading, VStack } from '@navikt/ds-react'; +import { useNavigate } from 'react-router-dom'; +import VelkommenPageHeader from '@components/velkommen-page-header/VelkommenPageHeader'; +import { useDeltakerContext } from '@context/DeltakerContext'; +import Page from '@navikt/sif-common-core-ds/src/components/page/Page'; +import { dateFormatter } from '@navikt/sif-common-utils'; +import KortOmUngdomsprogrammet from '../../components/KortOmUngdomsprogrammet'; +import { SøknadRoutes } from '../../types/SøknadRoutes'; + +const IntroPage = () => { + const { + data: { søker, deltakelserIkkeSøktFor }, + } = useDeltakerContext(); + const navigate = useNavigate(); + + if (deltakelserIkkeSøktFor.length !== 1) { + return ( + + + + + + Scenario som ikke støttes + + {deltakelserIkkeSøktFor.length === 0 + ? 'Ingen deltakelse som det ikke er søkt for' + : 'Det finnes flere deltakelser som ikke er søkt for'} + + + + ); + } + + const { programPeriode } = deltakelserIkkeSøktFor[0]; + + const handleStartSøknad = () => { + navigate(SøknadRoutes.VELKOMMEN); + }; + + return ( + + + + + + Hei {søker.fornavn} + + + Du er meldt på av din veileder til å være med i Ungdomsprogrammet fra og med{' '} + {dateFormatter.dateShortMonthYear(programPeriode.from)}. For å starte + programmet, må du sende inn en kort søknad. + + + + + + + + + ); +}; + +export default IntroPage; diff --git "a/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/pages/kvittering/KvitteringPage.tsx" "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/pages/kvittering/KvitteringPage.tsx" new file mode 100644 index 0000000000..102b4edea5 --- /dev/null +++ "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/pages/kvittering/KvitteringPage.tsx" @@ -0,0 +1,62 @@ +import { Alert, BodyLong, Heading, Link, VStack } from '@navikt/ds-react'; +import React from 'react'; +import { Infolist } from '@navikt/sif-common-core-ds'; +import Page from '@navikt/sif-common-core-ds/src/components/page/Page'; +import { useEffectOnce } from '@navikt/sif-common-hooks'; +import actionsCreator from '../../context/action/actionCreator'; +import { AppText, useAppIntl } from '../../../../i18n'; +import { useSøknadContext } from '../../context/hooks/useSøknadContext'; +import { appEnv } from '../../../../utils/appEnv'; +import { EnvKey } from '@navikt/sif-common-env'; + +const KvitteringPage = () => { + const { text } = useAppIntl(); + const { dispatch } = useSøknadContext(); + + useEffectOnce(() => { + dispatch(actionsCreator.setSøknadSendt()); + }); + return ( + + + + + Kvittering + + + + Vi har mottatt din søknad + + + Vivero eos et accusamus et iusto odio dignis simos ducimus qui blanditiis praesentium + voluptatum. + + + + + +
  • + +
  • +
  • + +
  • +
  • + ( + + {children} + + ), + }} + /> +
  • +
    +
    +
    + ); +}; + +export default KvitteringPage; diff --git "a/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/pages/kvittering/kvitteringMesssages.ts" "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/pages/kvittering/kvitteringMesssages.ts" new file mode 100644 index 0000000000..56a33780a9 --- /dev/null +++ "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/pages/kvittering/kvitteringMesssages.ts" @@ -0,0 +1,14 @@ +const nb = { + 'kvittering.tittel': 'Vi har mottatt søknad om å være med i ungdomsprogrammet', + 'kvittering.info.tittel': '[TODO] Hva skjer videre nå?', + 'kvittering.info.1': 'Hva skal vi ha med her? Hva skjer egentlig', + 'kvittering.info.2': 'Vi kontakter deg hvis vi trenger flere opplysninger.', + 'kvittering.info.3': 'Du kan følge saken din på Ungdomsprogrammet.', +}; + +const nn: Record = { ...nb }; + +export const kvitteringMessages = { + nb, + nn, +}; diff --git "a/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/pages/velkommen/VelkommenPage.tsx" "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/pages/velkommen/VelkommenPage.tsx" new file mode 100644 index 0000000000..8a815f08a7 --- /dev/null +++ "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/pages/velkommen/VelkommenPage.tsx" @@ -0,0 +1,39 @@ +import { useDeltakerContext } from '@context/DeltakerContext'; +import { UngdomsytelseDeltakerApp } from '@navikt/sif-app-register'; +import { useAmplitudeInstance } from '@navikt/sif-common-amplitude'; +import { SoknadVelkommenPage } from '@navikt/sif-common-soknad-ds/src'; +import OmSøknaden from '../../components/OmSøknaden'; +import VelkommenGuideContent from '../../components/VelkommenGuideContent'; +import actionsCreator from '../../context/action/actionCreator'; +import { useSøknadContext } from '../../context/hooks/useSøknadContext'; +import { SøknadRoutes } from '../../types/SøknadRoutes'; + +const VelkommenPage = () => { + const { + data: { søker }, + } = useDeltakerContext(); + + const { dispatch } = useSøknadContext(); + + const { logSoknadStartet } = useAmplitudeInstance(); + + const startSøknad = async () => { + await logSoknadStartet(UngdomsytelseDeltakerApp.key); + dispatch(actionsCreator.startSøknad()); + dispatch(actionsCreator.setSøknadRoute(SøknadRoutes.BARN)); + }; + + return ( + , + }} + title="Søknad om deltakelse i ungdoms­programmet"> + + + ); +}; + +export default VelkommenPage; diff --git "a/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/pages/velkommen/velkommenPageMessages.ts" "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/pages/velkommen/velkommenPageMessages.ts" new file mode 100644 index 0000000000..df435ee93d --- /dev/null +++ "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/pages/velkommen/velkommenPageMessages.ts" @@ -0,0 +1,27 @@ +const nb = { + 'omSøknaden.tittel': 'Om søknaden', + 'omSøknaden.1': 'Du får veiledning underveis i søknaden om hva du skal fylle ut, og hvordan.', + 'omSøknaden.2': + 'Vi tar vare på svarene dine i 72 timer. Hvis du innenfor den tiden for eksempel vil ta en pause eller blir automatisk logget ut, fortsetter du der du var når du kommer tilbake.', + 'omSøknaden.3': + 'Du må svare på alle spørsmålene for å kunne gå videre. Hvis du mangler etterspurt dokumentasjon, kan du ettersende det så snart du kan.', + 'omSøknaden.4': 'Om hvordan vi innhenter opplysninger om deg', + 'harForståttRettigheterOgPlikter.notChecked': 'Du må velge at du har forstått ditt ansvar som søker', + + 'personopplysninger.dialogtittel': 'Om behandling av personopplysninger', + 'personopplysninger.1': 'Slik behandler Nav personopplysningene dine', + 'personopplysninger.2': + 'Vi innhenter og mottar opplysninger om deg når vi skal behandle saken din. Det er nødvendig for at du skal få riktig tjeneste. Saken din kan behandles automatisk.', + 'personopplysninger.3': 'Hvilke opplysninger innhenter vi?', + 'personopplysninger.4': 'Opplysningene vi innhenter kommer enten fra deg eller fra offentlige registre:', + 'personopplysninger.4.1': 'hvilke barn du er registrert som forelder til.', + 'personopplysninger.4.2': 'hvem den andre forelderen er, og om dere er bosatt på samme folkeregistrerte adresse.', + 'personopplysninger.4.3': 'tilknytningen din til Norge.', + 'personopplysninger.4.4': + 'trygdeordninger du kan ha rett til i andre land. Vi kan også sende opplysninger om deg til trygdemyndigheter i andre land.', + + 'personopplysninger.5': + 'Du har rett til innsyn i saken din. Vil du vite mer om hvordan Nav behandler personopplysninger? Se nav.no/personvern.', +}; + +export const velkommenPageMessages = { nb }; diff --git "a/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/steps/arbeidstid-step/ArbeidstidStep.tsx" "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/steps/arbeidstid-step/ArbeidstidStep.tsx" new file mode 100644 index 0000000000..cee131c46b --- /dev/null +++ "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/steps/arbeidstid-step/ArbeidstidStep.tsx" @@ -0,0 +1,83 @@ +import { useIntl } from 'react-intl'; +import Block from '@navikt/sif-common-core-ds/src/atoms/block/Block'; +import { getTypedFormComponents, ValidationError, YesOrNo } from '@navikt/sif-common-formik-ds'; +import getIntlFormErrorHandler from '@navikt/sif-common-formik-ds/src/validation/intlFormErrorHandler'; +import actionsCreator from '../../context/action/actionCreator'; +import { useSøknadContext } from '../../context/hooks/useSøknadContext'; +import { useStepFormValuesContext } from '../../context/StepFormValuesContext'; +import { SøknadContextState } from '../../context/SøknadContextState'; +import { useOnValidSubmit } from '../../hooks/useOnValidSubmit'; +import { useStepNavigation } from '../../hooks/useStepNavigation'; +import SøknadStep from '../../SøknadStep'; +import { getSøknadStepConfigForStep } from '../../søknadStepConfig'; +import { StepId } from '../../types/StepId'; +import { lagreSøknadState } from '../../utils/lagreSøknadState'; +import { getArbeidstidSøknadsdataFromFormValues } from './arbeidstidStepUtils'; + +export enum ArbeidstidFormFields { + 'harArbeidetIPerioden' = 'harArbeidetIPerioden', +} + +export interface ArbeidstidFormValues { + [ArbeidstidFormFields.harArbeidetIPerioden]?: YesOrNo; +} + +const { FormikWrapper, Form } = getTypedFormComponents(); + +const ArbeidstidStep = () => { + const intl = useIntl(); + + const { + state: { søknadsdata }, + } = useSøknadContext(); + + const stepId = StepId.ARBEIDSTID; + const step = getSøknadStepConfigForStep(søknadsdata, stepId); + + const { goBack } = useStepNavigation(step); + + const { clearStepFormValues } = useStepFormValuesContext(); + + const onValidSubmitHandler = (values) => { + const ArbeidstidSøknadsdata = getArbeidstidSøknadsdataFromFormValues(values); + if (ArbeidstidSøknadsdata) { + clearStepFormValues(stepId); + return [actionsCreator.setSøknadArbeidstid(ArbeidstidSøknadsdata)]; + } + return []; + }; + + const { handleSubmit, isSubmitting } = useOnValidSubmit( + onValidSubmitHandler, + stepId, + (state: SøknadContextState) => { + return lagreSøknadState(state); + }, + ); + + return ( + + { + return ( + <> +
    + Arbeidstid +
    + + ); + }} + /> +
    + ); +}; + +export default ArbeidstidStep; diff --git "a/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/steps/arbeidstid-step/arbeidstidStepUtils.ts" "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/steps/arbeidstid-step/arbeidstidStepUtils.ts" new file mode 100644 index 0000000000..1eeef1f5a4 --- /dev/null +++ "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/steps/arbeidstid-step/arbeidstidStepUtils.ts" @@ -0,0 +1,9 @@ +import { YesOrNo } from '@navikt/sif-common-formik-ds'; +import { ArbeidstidSøknadsdata } from '../../types/søknadsdata/ArbeidstidSøknadsdata'; +import { ArbeidstidFormValues } from './ArbeidstidStep'; + +export const getArbeidstidSøknadsdataFromFormValues = (formValues: ArbeidstidFormValues): ArbeidstidSøknadsdata => { + return { + harArbeidetIPerioden: formValues.harArbeidetIPerioden === YesOrNo.YES, + }; +}; diff --git "a/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/steps/barn-step/BarnStep.tsx" "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/steps/barn-step/BarnStep.tsx" new file mode 100644 index 0000000000..097a6afb16 --- /dev/null +++ "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/steps/barn-step/BarnStep.tsx" @@ -0,0 +1,97 @@ +import { useIntl } from 'react-intl'; +import { RegistrertBarn } from '@navikt/sif-common-api'; +import Block from '@navikt/sif-common-core-ds/src/atoms/block/Block'; +import ItemList from '@navikt/sif-common-core-ds/src/components/lists/item-list/ItemList'; +import { getTypedFormComponents, ValidationError } from '@navikt/sif-common-formik-ds'; +import getIntlFormErrorHandler from '@navikt/sif-common-formik-ds/src/validation/intlFormErrorHandler'; +import RegistrerteBarnListeHeading from '@navikt/sif-common-ui/src/components/registrerte-barn-liste/RegistrerteBarnListeHeading'; +import actionsCreator from '../../context/action/actionCreator'; +import { useSøknadContext } from '../../context/hooks/useSøknadContext'; +import { useStepFormValuesContext } from '../../context/StepFormValuesContext'; +import { SøknadContextState } from '../../context/SøknadContextState'; +import { useOnValidSubmit } from '../../hooks/useOnValidSubmit'; +import { useStepNavigation } from '../../hooks/useStepNavigation'; +import SøknadStep from '../../SøknadStep'; +import { getSøknadStepConfigForStep } from '../../søknadStepConfig'; +import { StepId } from '../../types/StepId'; +import { lagreSøknadState } from '../../utils/lagreSøknadState'; +import { barnItemLabelRenderer, getBarnSøknadsdataFromFormValues } from './barnStepUtils'; + +export enum BarnFormFields {} + +export interface BarnFormValues {} + +const { FormikWrapper, Form } = getTypedFormComponents(); + +const BarnStep = () => { + const intl = useIntl(); + + const { + state: { søknadsdata, registrerteBarn: registrertBarn }, + } = useSøknadContext(); + + const stepId = StepId.BARN; + const step = getSøknadStepConfigForStep(søknadsdata, stepId); + + const { goBack } = useStepNavigation(step); + + const { clearStepFormValues } = useStepFormValuesContext(); + + const onValidSubmitHandler = () => { + const BarnSøknadsdata = getBarnSøknadsdataFromFormValues(registrertBarn); + if (BarnSøknadsdata) { + clearStepFormValues(stepId); + return [actionsCreator.setSøknadBarn(BarnSøknadsdata)]; + } + return []; + }; + + const { handleSubmit, isSubmitting } = useOnValidSubmit( + onValidSubmitHandler, + stepId, + (state: SøknadContextState) => { + return lagreSøknadState(state); + }, + ); + + return ( + + { + return ( + <> +
    + + + Barn registrert på deg + + + {registrertBarn.length > 0 && ( + + + getItemId={(barn): string => barn.aktørId} + getItemTitle={(barn): string => barn.etternavn} + labelRenderer={(barn): React.ReactNode => barnItemLabelRenderer(barn)} + items={registrertBarn} + /> + + )} + +
    + + ); + }} + /> +
    + ); +}; + +export default BarnStep; diff --git "a/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/steps/barn-step/barnStepUtils.tsx" "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/steps/barn-step/barnStepUtils.tsx" new file mode 100644 index 0000000000..b425b35900 --- /dev/null +++ "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/steps/barn-step/barnStepUtils.tsx" @@ -0,0 +1,17 @@ +import { RegistrertBarn } from '@navikt/sif-common-api'; +import { dateFormatter } from '@navikt/sif-common-utils'; +import { formatName } from '@navikt/sif-common-core-ds/src/utils/personUtils'; +import { Box, HGrid } from '@navikt/ds-react'; + +export const getBarnSøknadsdataFromFormValues = (registrertBarn: RegistrertBarn[]) => { + return registrertBarn; +}; + +export const barnItemLabelRenderer = (registrertBarn: RegistrertBarn): React.ReactNode => { + return ( + + Født: {dateFormatter.compact(registrertBarn.fødselsdato)} + {formatName(registrertBarn.fornavn, registrertBarn.etternavn, registrertBarn.mellomnavn)} + + ); +}; diff --git "a/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/steps/oppsummering-step/OppsummeringStep.tsx" "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/steps/oppsummering-step/OppsummeringStep.tsx" new file mode 100644 index 0000000000..e5bb46ab0f --- /dev/null +++ "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/steps/oppsummering-step/OppsummeringStep.tsx" @@ -0,0 +1,140 @@ +import { ErrorSummary, FormSummary, VStack } from '@navikt/ds-react'; +import { ErrorSummaryItem } from '@navikt/ds-react/ErrorSummary'; +import { useEffect, useRef } from 'react'; +import { useIntl } from 'react-intl'; +import FormBlock from '@navikt/sif-common-core-ds/src/atoms/form-block/FormBlock'; +import { getTypedFormComponents } from '@navikt/sif-common-formik-ds'; +import { getCheckedValidator } from '@navikt/sif-common-formik-ds/src/validation'; +import getIntlFormErrorHandler from '@navikt/sif-common-formik-ds/src/validation/intlFormErrorHandler'; +import { usePrevious } from '@navikt/sif-common-hooks'; +import { ErrorPage } from '@navikt/sif-common-soknad-ds'; +import { AppText } from '../../../../i18n'; +import { useSøknadContext } from '../../../søknad/context/hooks/useSøknadContext'; +import SøknadStep from '../../../søknad/SøknadStep'; +import { getSøknadStepConfig, getSøknadStepConfigForStep } from '../../../søknad/søknadStepConfig'; +import { useSendSøknad } from '../../hooks/useSendSøknad'; +import { useStepNavigation } from '../../hooks/useStepNavigation'; +import { useSøknadsdataStatus } from '../../hooks/useSøknadsdataStatus'; +import { StepId } from '../../types/StepId'; +import { getApiDataFromSøknadsdata } from '../../utils/søknadsdataToApiData/getApiDataFromSøknadsdata'; +import BarnSummaryList from './components/BarnSummaryList'; +import OmSøkerOppsummering from './components/OmSøkerOppsummering'; +import { getOppsummeringStepInitialValues } from './oppsummeringStepUtils'; +import DeltakelseOppsummering from './components/DeltakelseOppsummering'; +import { relocateToRootPage } from '../../utils/navigationUtils'; + +enum OppsummeringFormFields { + harBekreftetOpplysninger = 'harBekreftetOpplysninger', +} + +export interface OppsummeringFormValues { + [OppsummeringFormFields.harBekreftetOpplysninger]: boolean; +} + +const { FormikWrapper, Form, ConfirmationCheckbox } = getTypedFormComponents< + OppsummeringFormFields, + OppsummeringFormValues +>(); + +const OppsummeringStep = () => { + const intl = useIntl(); + + const { + state: { søknadsdata, søker, registrerteBarn: registrertBarn }, + } = useSøknadContext(); + + if (!søknadsdata.deltakelse) { + relocateToRootPage(); + } + const stepId = StepId.OPPSUMMERING; + const step = getSøknadStepConfigForStep(søknadsdata, stepId); + + const { invalidSteps } = useSøknadsdataStatus(stepId, getSøknadStepConfig(søknadsdata.deltakelse)); + const hasInvalidSteps = invalidSteps.length > 0; + + const { goBack } = useStepNavigation(step); + + const { sendSøknad, isSubmitting, sendSøknadError } = useSendSøknad(); + const previousSøknadError = usePrevious(sendSøknadError); + const sendSøknadErrorSummary = useRef(null); + + useEffect(() => { + if (previousSøknadError === undefined && sendSøknadError !== undefined) { + sendSøknadErrorSummary.current?.focus(); + } + }, [previousSøknadError, sendSøknadError]); + + const apiData = getApiDataFromSøknadsdata(søker.fødselsnummer, søknadsdata); + + if (!apiData) { + return ( + { + return <>Manglende API data; + }} + /> + ); + } + + return ( + + { + if (apiData) { + sendSøknad({ + ...apiData, + harBekreftetOpplysninger: values[OppsummeringFormFields.harBekreftetOpplysninger] === true, + }); + } + }} + renderForm={() => { + return ( + <> +
    + + + + + + Dine barn + + + + + + + + + + } + validate={getCheckedValidator()} + name={OppsummeringFormFields.harBekreftetOpplysninger} + /> + +
    + {sendSøknadError && ( + + + {sendSøknadError.message} + + + )} + + ); + }}>
    +
    + ); +}; + +export default OppsummeringStep; diff --git "a/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/steps/oppsummering-step/components/BarnSummaryList.tsx" "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/steps/oppsummering-step/components/BarnSummaryList.tsx" new file mode 100644 index 0000000000..2365a93386 --- /dev/null +++ "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/steps/oppsummering-step/components/BarnSummaryList.tsx" @@ -0,0 +1,21 @@ +import { RegistrertBarn } from '@navikt/sif-common-api'; +import { formatName } from '@navikt/sif-common-core-ds/src/utils/personUtils'; +import { SummaryList } from '@navikt/sif-common-ui'; + +interface Props { + barn: RegistrertBarn[]; +} + +const BarnSummaryList = ({ barn }: Props) => { + return ( + { + return formatName(fornavn, etternavn, mellomnavn); + }} + /> + ); +}; + +export default BarnSummaryList; diff --git "a/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/steps/oppsummering-step/components/DeltakelseOppsummering.tsx" "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/steps/oppsummering-step/components/DeltakelseOppsummering.tsx" new file mode 100644 index 0000000000..ea45479fed --- /dev/null +++ "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/steps/oppsummering-step/components/DeltakelseOppsummering.tsx" @@ -0,0 +1,32 @@ +import { FormSummary } from '@navikt/ds-react'; +import { dateFormatter } from '@navikt/sif-common-utils'; +import { Deltakelse } from '../../../../../api/types'; +import { AppText } from '../../../../../i18n'; + +interface Props { + deltakelse: Deltakelse; +} + +const DeltakelseOppsummering = ({ deltakelse }: Props) => { + return ( + + + + + + + + + + + + + {dateFormatter.dateShortMonthYear(deltakelse.programPeriode.from)} + + + + + ); +}; + +export default DeltakelseOppsummering; diff --git "a/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/steps/oppsummering-step/components/OmS\303\270kerOppsummering.tsx" "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/steps/oppsummering-step/components/OmS\303\270kerOppsummering.tsx" new file mode 100644 index 0000000000..192c9ea7ae --- /dev/null +++ "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/steps/oppsummering-step/components/OmS\303\270kerOppsummering.tsx" @@ -0,0 +1,38 @@ +import { FormSummary } from '@navikt/ds-react'; +import React from 'react'; +import { formatName } from '@navikt/sif-common-core-ds/src/utils/personUtils'; +import { AppText } from '../../../../../i18n'; +import { Søker } from '@navikt/sif-common-api'; + +interface Props { + søker: Søker; +} +const OmSøkerOppsummering: React.FC = ({ søker }) => { + return ( + + + + + + + + + + + + + {formatName(søker.fornavn, søker.etternavn, søker.mellomnavn)} + + + + + + + {søker.fødselsnummer} + + + + ); +}; + +export default OmSøkerOppsummering; diff --git "a/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/steps/oppsummering-step/oppsummeringMessages.ts" "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/steps/oppsummering-step/oppsummeringMessages.ts" new file mode 100644 index 0000000000..66671ac05f --- /dev/null +++ "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/steps/oppsummering-step/oppsummeringMessages.ts" @@ -0,0 +1,34 @@ +const nb = { + 'step.oppsummering.søker.header': 'Om deg', + 'step.oppsummering.søker.navn': 'Navn', + 'step.oppsummering.søker.fnr': 'Fødselsnummer', + 'step.oppsummering.dineBarn.listItem.FOSTERBARN': '(Barnet er mitt fosterbarn).', + + 'step.oppsummering.deltakelse.header': 'Om deltakelsen', + 'step.oppsummering.deltakelse.startdato': 'Startdato', + + 'step.oppsummering.omOmsorgenForBarn.barnList.title': 'Barn du er alene om omsorgen for', + 'step.oppsummering.omOmsorgenForBarn.harOmsorgFor.tidspunktForAleneomsorg': 'Tidspunkt for aleneomsorg: {dato}', + 'step.oppsummering.omOmsorgenForBarn.harOmsorgFor.tidspunktForAleneomsorg.tidligere': + 'Du ble alene om omsorgen for over 2 år siden.', + + 'step.oppsummering.bekrefterOpplysninger': + 'Jeg bekrefter at opplysningene jeg har gitt er riktige, og at jeg ikke har holdt tilbake opplysninger som har betydning for min rett til omsorgsdager.', + + 'step.oppsummering.sendMelding.feilmelding.førsteGang': + 'Det oppstod en feil under innsending. Vennligst prøv på nytt.', + 'step.oppsummering.sendMelding.feilmelding.andreGang': + 'Det oppstod fortsatt en feil under innsending. Vennligst vent litt og prøv på nytt.', + 'step.oppsummering.sendSøknad': 'Send søknad', + + 'validation.harBekreftetOpplysninger.notChecked': 'Du må bekrefte at opplysningene du har gitt er riktige.', +}; + +const nn: Record = { + ...nb, +}; + +export const oppsummeringMessages = { + nb, + nn, +}; diff --git "a/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/steps/oppsummering-step/oppsummeringStepUtils.ts" "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/steps/oppsummering-step/oppsummeringStepUtils.ts" new file mode 100644 index 0000000000..ea8141f2bf --- /dev/null +++ "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/steps/oppsummering-step/oppsummeringStepUtils.ts" @@ -0,0 +1,8 @@ +import { Søknadsdata } from '../../types/søknadsdata/Søknadsdata'; +import { OppsummeringFormValues } from './OppsummeringStep'; + +export const getOppsummeringStepInitialValues = (søknadsdata: Søknadsdata): OppsummeringFormValues => { + return { + harBekreftetOpplysninger: søknadsdata.oppsummering?.harBekreftetOpplysninger || false, + }; +}; diff --git "a/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/s\303\270knadStepConfig.ts" "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/s\303\270knadStepConfig.ts" new file mode 100644 index 0000000000..9e9edb20f7 --- /dev/null +++ "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/s\303\270knadStepConfig.ts" @@ -0,0 +1,26 @@ +import { SoknadApplicationType, SoknadStepsConfig, soknadStepUtils, StepConfig } from '@navikt/sif-common-soknad-ds'; +import { Deltakelse } from '../../api/types'; +import { StepId } from './types/StepId'; +import { Søknadsdata } from './types/søknadsdata/Søknadsdata'; +import { getSøknadStepRoute } from './utils/søknadRoutesUtils'; +import { søkerMåRapportereArbeidstidISøknaden } from './utils/søknadUtils'; + +const getSøknadSteps = (deltakelse: Deltakelse): StepId[] => { + if (søkerMåRapportereArbeidstidISøknaden(deltakelse)) { + return [StepId.BARN, StepId.ARBEIDSTID, StepId.OPPSUMMERING]; + } + return [StepId.BARN, StepId.OPPSUMMERING]; +}; + +export const getSøknadStepConfig = (deltakelse: Deltakelse): SoknadStepsConfig => + soknadStepUtils.getStepsConfig(getSøknadSteps(deltakelse), SoknadApplicationType.SOKNAD, (step) => { + return getSøknadStepRoute(step); + }); + +export const getSøknadStepConfigForStep = (_søknadsdata: Søknadsdata, stepId: StepId): StepConfig => { + const config = getSøknadStepConfig(_søknadsdata.deltakelse)[stepId]; + if (!config) { + throw `Missing step config ${stepId}`; + } + return config; +}; diff --git "a/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/types/RequestStatus.ts" "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/types/RequestStatus.ts" new file mode 100644 index 0000000000..8161c02db0 --- /dev/null +++ "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/types/RequestStatus.ts" @@ -0,0 +1,6 @@ +export enum RequestStatus { + 'loading' = 'loading', + 'success' = 'success', + 'error' = 'error', + 'redirectingToLogin' = 'redirectingToLogin', +} diff --git "a/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/types/StepFormValues.ts" "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/types/StepFormValues.ts" new file mode 100644 index 0000000000..ec30bd7b38 --- /dev/null +++ "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/types/StepFormValues.ts" @@ -0,0 +1,13 @@ +import { SamtykkeFormValues } from '@navikt/sif-common-soknad-ds'; +import { ArbeidstidFormValues } from '../steps/arbeidstid-step/ArbeidstidStep'; +import { BarnFormValues } from '../steps/barn-step/BarnStep'; +import { OppsummeringFormValues } from '../steps/oppsummering-step/OppsummeringStep'; +import { StepId } from './StepId'; + +export interface StepFormValues { + [StepId.VELKOMMEN]?: SamtykkeFormValues; + [StepId.BARN]?: BarnFormValues; + [StepId.ARBEIDSTID]?: ArbeidstidFormValues; + [StepId.OPPSUMMERING]?: OppsummeringFormValues; + [StepId.KVITTERING]?: undefined; +} diff --git a/apps/ungdomsytelse-deltaker/src/types/StepId.ts "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/types/StepId.ts" similarity index 70% rename from apps/ungdomsytelse-deltaker/src/types/StepId.ts rename to "apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/types/StepId.ts" index 989b14fcf0..4a36991662 100644 --- a/apps/ungdomsytelse-deltaker/src/types/StepId.ts +++ "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/types/StepId.ts" @@ -1,6 +1,7 @@ export enum StepId { 'VELKOMMEN' = 'velkommen', - 'INFO' = 'info', + 'BARN' = 'barn', + 'ARBEIDSTID' = 'arbeidstid', 'OPPSUMMERING' = 'oppsummering', 'KVITTERING' = 'soknad_sendt', } diff --git "a/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/types/S\303\270knadContextState.ts" "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/types/S\303\270knadContextState.ts" new file mode 100644 index 0000000000..6c9bd5a431 --- /dev/null +++ "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/types/S\303\270knadContextState.ts" @@ -0,0 +1,5 @@ +import { InitialData } from '@hooks/useInitialData'; + +export interface DeltakerContextState extends InitialData { + versjon: string; +} diff --git "a/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/types/S\303\270knadRoutes.ts" "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/types/S\303\270knadRoutes.ts" new file mode 100644 index 0000000000..251c928e8e --- /dev/null +++ "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/types/S\303\270knadRoutes.ts" @@ -0,0 +1,19 @@ +import { StepId } from './StepId'; + +export const SøknadStepRoutePath = { + [StepId.VELKOMMEN]: 'velkommen', + [StepId.BARN]: 'barn', + [StepId.ARBEIDSTID]: 'arbeidstid', + [StepId.OPPSUMMERING]: 'oppsummering', + [StepId.KVITTERING]: 'soknad_sendt', +}; + +export enum SøknadRoutes { + VELKOMMEN = '/soknad/velkommen', + BARN = '/soknad/barn', + ARBEIDSTID = '/soknad/arbeidstid', + OPPSUMMERING = '/soknad/oppsummering', + SØKNAD_SENDT = '/soknad/soknad_sendt', + UKJENT_STEG = '/soknad/ukjent-steg', + IKKE_TILGANG = '/ikke-tilgang', +} diff --git "a/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/types/s\303\270knadsdata/ArbeidstidS\303\270knadsdata.ts" "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/types/s\303\270knadsdata/ArbeidstidS\303\270knadsdata.ts" new file mode 100644 index 0000000000..1a23700258 --- /dev/null +++ "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/types/s\303\270knadsdata/ArbeidstidS\303\270knadsdata.ts" @@ -0,0 +1,4 @@ +export type ArbeidstidSøknadsdata = { + harArbeidetIPerioden: boolean; + arbeidstid?: any; +}; diff --git "a/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/types/s\303\270knadsdata/BarnS\303\270knadsdata.ts" "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/types/s\303\270knadsdata/BarnS\303\270knadsdata.ts" new file mode 100644 index 0000000000..a4b8f53846 --- /dev/null +++ "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/types/s\303\270knadsdata/BarnS\303\270knadsdata.ts" @@ -0,0 +1 @@ +export type BarnSøknadsdata = {}; diff --git "a/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/types/s\303\270knadsdata/S\303\270knadsdata.ts" "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/types/s\303\270knadsdata/S\303\270knadsdata.ts" new file mode 100644 index 0000000000..7ebe70a567 --- /dev/null +++ "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/types/s\303\270knadsdata/S\303\270knadsdata.ts" @@ -0,0 +1,15 @@ +import { Deltakelse } from '../../../../api/types'; +import { StepId } from '../StepId'; + +export type Søknadsdata = { + id: string; + deltakelse: Deltakelse; + [StepId.VELKOMMEN]?: { + harForståttRettigheterOgPlikter?: boolean; + }; + [StepId.BARN]?: {}; + [StepId.ARBEIDSTID]?: {}; + [StepId.OPPSUMMERING]?: { + harBekreftetOpplysninger?: boolean; + }; +}; diff --git "a/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/utils/lagreS\303\270knadState.ts" "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/utils/lagreS\303\270knadState.ts" new file mode 100644 index 0000000000..e5db18579f --- /dev/null +++ "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/utils/lagreS\303\270knadState.ts" @@ -0,0 +1,6 @@ +import { mellomlagringService } from '../../../api/services/mellomlagringService'; +import { SøknadContextState } from '../context/SøknadContextState'; + +export const lagreSøknadState = (state: SøknadContextState) => { + return mellomlagringService.update(state); +}; diff --git "a/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/utils/navigationUtils.ts" "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/utils/navigationUtils.ts" new file mode 100644 index 0000000000..73b544e3e1 --- /dev/null +++ "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/utils/navigationUtils.ts" @@ -0,0 +1,24 @@ +import { appEnv } from '../../../utils/appEnv'; +import { SøknadRoutes } from '../types/SøknadRoutes'; + +const { PUBLIC_PATH, SIF_PUBLIC_LOGIN_URL, SIF_PUBLIC_MINSIDE_URL } = appEnv; + +const relocateTo = (url: string): void => { + /** Hard redirect enforcing page reload */ + window.location.assign(url); +}; + +const getSøknadRouteURL = (route: SøknadRoutes) => { + return `${PUBLIC_PATH}${route}`; +}; + +export const relocateToWelcomePage = () => { + relocateTo(getSøknadRouteURL(SøknadRoutes.VELKOMMEN)); +}; +export const relocateToKvittering = () => { + relocateTo(getSøknadRouteURL(SøknadRoutes.SØKNAD_SENDT)); +}; +export const relocateToNoAccessPage = (): void => relocateTo(getSøknadRouteURL(SøknadRoutes.IKKE_TILGANG)); +export const relocateToLoginPage = () => relocateTo(SIF_PUBLIC_LOGIN_URL); +export const relocateToMinSide = () => relocateTo(SIF_PUBLIC_MINSIDE_URL); +export const relocateToRootPage = () => relocateTo(PUBLIC_PATH); diff --git "a/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/utils/s\303\270knadRoutesUtils.ts" "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/utils/s\303\270knadRoutesUtils.ts" new file mode 100644 index 0000000000..47af7cb8ee --- /dev/null +++ "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/utils/s\303\270knadRoutesUtils.ts" @@ -0,0 +1,24 @@ +import { StepId } from '../types/StepId'; +import { SøknadRoutes } from '../types/SøknadRoutes'; + +export const getSøknadStepRoute = (stepId: StepId): SøknadRoutes => { + switch (stepId) { + case StepId.VELKOMMEN: + return SøknadRoutes.VELKOMMEN; + case StepId.BARN: + return SøknadRoutes.BARN; + case StepId.ARBEIDSTID: + return SøknadRoutes.ARBEIDSTID; + case StepId.OPPSUMMERING: + return SøknadRoutes.OPPSUMMERING; + case StepId.KVITTERING: + return SøknadRoutes.SØKNAD_SENDT; + } +}; + +export const isValidSøknadRoute = (route?: SøknadRoutes): boolean => { + if (!route) { + return false; + } + return Object.values(SøknadRoutes).includes(route); +}; diff --git "a/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/utils/s\303\270knadUtils.ts" "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/utils/s\303\270knadUtils.ts" new file mode 100644 index 0000000000..7f997b177a --- /dev/null +++ "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/utils/s\303\270knadUtils.ts" @@ -0,0 +1,14 @@ +import dayjs from 'dayjs'; +import { Deltakelse } from '../../../api/types'; +import { getDateToday } from '@navikt/sif-common-utils'; + +export const SOKNAD_VERSJON = '0.0.1'; + +/** Dato bruker vil måtte svare på om en har jobbet i perioden før en søker */ +export const getDatoForArbeidstidRegistrering = (): Date => { + return dayjs().startOf('month').subtract(1, 'month').toDate(); +}; + +export const søkerMåRapportereArbeidstidISøknaden = (deltakelse?: Deltakelse): boolean => { + return dayjs(getDateToday()).isBefore(dayjs(deltakelse?.programPeriode.from)); +}; diff --git "a/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/utils/s\303\270knadsdataToApiData/getApiDataFromS\303\270knadsdata.ts" "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/utils/s\303\270knadsdataToApiData/getApiDataFromS\303\270knadsdata.ts" new file mode 100644 index 0000000000..7e16f563ad --- /dev/null +++ "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/utils/s\303\270knadsdataToApiData/getApiDataFromS\303\270knadsdata.ts" @@ -0,0 +1,19 @@ +import { dateToISODate } from '@navikt/sif-common-utils'; +import { SøknadApiData } from '../../../../api/types'; +import { Søknadsdata } from '../../types/søknadsdata/Søknadsdata'; + +export const getApiDataFromSøknadsdata = ( + søkerNorskIdent: string, + søknadsdata: Søknadsdata, +): SøknadApiData | undefined => { + const { id } = søknadsdata; + + return { + søkerNorskIdent, + id, + språk: 'nb', + fraOgMed: dateToISODate(søknadsdata.deltakelse.programPeriode.from), + harForståttRettigheterOgPlikter: søknadsdata.velkommen?.harForståttRettigheterOgPlikter === true, + harBekreftetOpplysninger: søknadsdata.oppsummering?.harBekreftetOpplysninger === true, + }; +}; diff --git "a/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/utils/s\303\270knadsdataToApiData/validateApiData.ts" "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/utils/s\303\270knadsdataToApiData/validateApiData.ts" new file mode 100644 index 0000000000..713f880af5 --- /dev/null +++ "b/apps/ungdomsytelse-deltaker/src/sites/s\303\270knad/utils/s\303\270knadsdataToApiData/validateApiData.ts" @@ -0,0 +1,39 @@ +import { SøknadApiData } from '../../../../api/types'; +import { AppIntlShape } from '../../../../i18n'; +import { StepId } from '../../types/StepId'; +import { Søknadsdata } from '../../types/søknadsdata/Søknadsdata'; + +export enum API_DATA_VALIDATION_ERROR { + 'undefined' = 'undefined', +} + +export const validateApiDataMessages = { + nb: { + 'apiDataValidation.undefined': 'Det oppstod en feil ved visningen av siden.', + }, + nn: { + 'apiDataValidation.undefined': 'Det oppstod ein feil ved visning av sida.', + }, +}; + +interface ApiDataValidationError { + error: API_DATA_VALIDATION_ERROR; + step?: StepId; + message: React.ReactNode; +} + +/** Ikke tatt i bruk enda */ +export const validateApiData = ( + apiData: SøknadApiData | undefined, + _søknadsdata: Søknadsdata, + { text }: AppIntlShape, +): undefined | ApiDataValidationError => { + if (!apiData) { + return { + error: API_DATA_VALIDATION_ERROR.undefined, + message: text('apiDataValidation.undefined'), + }; + } + + return undefined; +}; diff --git "a/apps/ungdomsytelse-deltaker/src/s\303\270knad/S\303\270knadRouter.tsx" "b/apps/ungdomsytelse-deltaker/src/s\303\270knad/S\303\270knadRouter.tsx" deleted file mode 100644 index c1ea832a12..0000000000 --- "a/apps/ungdomsytelse-deltaker/src/s\303\270knad/S\303\270knadRouter.tsx" +++ /dev/null @@ -1,12 +0,0 @@ -import { Route, Routes } from 'react-router-dom'; -import Forside from './form/Forside'; - -const SøknadRouter = () => { - return ( - - } /> - - ); -}; - -export default SøknadRouter; diff --git "a/apps/ungdomsytelse-deltaker/src/s\303\270knad/context/S\303\270knadContext.tsx" "b/apps/ungdomsytelse-deltaker/src/s\303\270knad/context/S\303\270knadContext.tsx" deleted file mode 100644 index 5b311c22d4..0000000000 --- "a/apps/ungdomsytelse-deltaker/src/s\303\270knad/context/S\303\270knadContext.tsx" +++ /dev/null @@ -1,47 +0,0 @@ -import { createContext, ReactNode, useContext, useState } from 'react'; -import { Søker } from '@navikt/sif-common-api'; -import { Deltakelse } from '../../api/types'; -import { deltakelseErÅpenForRapportering } from '../../utils/deltakelserUtils'; - -interface SøknadContextType { - data: SøknadContextData; - updateDeltakelse: (deltakelser: Deltakelse[]) => void; -} - -export interface SøknadContextData { - søker: Søker; - alleDeltakelser: Deltakelse[]; - deltakelserSøktFor: Deltakelse[]; - deltakelserIkkeSøktFor: Deltakelse[]; - deltakelserÅpenForRapportering: Deltakelse[]; -} - -export const SøknadContext = createContext(null!); - -export const useSøknadContext = (): SøknadContextType => { - const context = useContext(SøknadContext); - if (!context) { - throw new Error('useSøknadContext must be used within a SøknadContextProvider'); - } - return context; -}; -interface Props { - children: ReactNode; - initialData: SøknadContextData; -} - -export const SøknadContextProvider = ({ children, initialData }: Props) => { - const [data, setData] = useState(initialData); - - const updateDeltakelse = (deltakelser: Deltakelse[]) => { - setData({ - ...data, - alleDeltakelser: deltakelser, - deltakelserSøktFor: deltakelser.filter((d) => d.harSøkt), - deltakelserIkkeSøktFor: deltakelser.filter((d) => !d.harSøkt), - deltakelserÅpenForRapportering: deltakelser.filter(deltakelseErÅpenForRapportering), - }); - }; - - return {children}; -}; diff --git "a/apps/ungdomsytelse-deltaker/src/s\303\270knad/form/DeltakelseForm.tsx" "b/apps/ungdomsytelse-deltaker/src/s\303\270knad/form/DeltakelseForm.tsx" deleted file mode 100644 index f12b988106..0000000000 --- "a/apps/ungdomsytelse-deltaker/src/s\303\270knad/form/DeltakelseForm.tsx" +++ /dev/null @@ -1,122 +0,0 @@ -import { Alert, BodyShort, Box, Button, HStack, VStack } from '@navikt/ds-react'; -import { FormikConfirmationCheckbox, TypedFormikForm, TypedFormikWrapper } from '@navikt/sif-common-formik-ds'; -import { getCheckedValidator } from '@navikt/sif-common-formik-ds/src/validation'; -import getIntlFormErrorHandler from '@navikt/sif-common-formik-ds/src/validation/intlFormErrorHandler'; -import { FormLayout } from '@navikt/sif-common-ui'; -import { dateFormatter, dateRangeFormatter, dateToISODate } from '@navikt/sif-common-utils'; -import { Deltakelse, SøknadApiData } from '../../api/types'; -import { useAppIntl } from '../../i18n'; -import { useSendSøknad } from '../hooks/useSendSøknad'; -import { Søker } from '@navikt/sif-common-api'; -import { Kvittering } from '@navikt/sif-common-soknad-ds/src'; -import ShadowBox from '../../components/shadow-box/ShadowBox'; -import dayjs from 'dayjs'; - -export interface FormValues { - fom: string; - tom: string; - harBekreftetOpplysninger: boolean; -} - -interface Props { - søker: Søker; - deltakelse: Deltakelse; - onSøknadSendt: (deltakelse: Deltakelse) => void; - onClose: (deltakelse: Deltakelse) => void; -} - -const initialValues: Partial = {}; - -const DeltakelseForm = ({ deltakelse, søker, onClose, onSøknadSendt }: Props) => { - const { intl } = useAppIntl(); - const { programPeriode } = deltakelse; - const { isSubmitting, sendSøknad, sendSøknadError, søknadSendt } = useSendSøknad(); - - return søknadSendt ? ( - <> - - - - - Lorem ipsum dolor sit amet consectetur adipisicing elit. Placeat eum assumenda tempore - pariatur quaerat aut nihil maiores recusandae similique. Et quisquam similique doloremque - optio odit impedit temporibus ullam hic officiis. - - - - - - - - - ) : ( - { - const data: SøknadApiData = { - id: deltakelse.id, - fraOgMed: dateToISODate(deltakelse.programPeriode.from), - tilOgMed: dateToISODate( - deltakelse.programPeriode.to || dayjs(deltakelse.programPeriode.from).add(1, 'month').toDate(), - ), - harBekreftetOpplysninger: values.harBekreftetOpplysninger, - harForståttRettigheterOgPlikter: true, - språk: 'nb', - søkerNorskIdent: søker.fødselsnummer, - }; - await sendSøknad(data); - onSøknadSendt(deltakelse); - }} - renderForm={() => { - return ( - <> - - - - - {programPeriode.to - ? `Delta i perioden ${dateRangeFormatter.getDateRangeText({ from: programPeriode.from, to: programPeriode.to }, 'nb')}` - : `Delta fra ${dateFormatter.compact(programPeriode.from)}`} - - - - Lorem ipsum dolor sit amet consectetur adipisicing elit. Eveniet asperiores - corrupti optio, aliquam praesentium harum reprehenderit. Cumque maxime quia - tenetur esse placeat reprehenderit, soluta amet adipisci qui officia, nesciunt - culpa. - - - - Du må bekrefte at du ønsker å være med i programmet i denne perioden - - - - - - - - - {sendSøknadError && ( - - Det skjedde en feil ved sending av søknad: -
    - {JSON.stringify(sendSøknadError.message)} -
    -
    - )} - - ); - }} - /> - ); -}; - -export default DeltakelseForm; diff --git "a/apps/ungdomsytelse-deltaker/src/s\303\270knad/form/Forside.tsx" "b/apps/ungdomsytelse-deltaker/src/s\303\270knad/form/Forside.tsx" deleted file mode 100644 index 5c7ffba045..0000000000 --- "a/apps/ungdomsytelse-deltaker/src/s\303\270knad/form/Forside.tsx" +++ /dev/null @@ -1,89 +0,0 @@ -import { Alert, Box, Heading, VStack } from '@navikt/ds-react'; -import { useState } from 'react'; -import Page from '@navikt/sif-common-core-ds/src/components/page/Page'; -import { SoknadVelkommenGuide } from '@navikt/sif-common-soknad-ds/src'; -import { deltakerService } from '../../api/services/deltakerService'; -import { Deltakelse } from '../../api/types'; -import DeltakelseTable from '../../components/deltakelse-table/DeltakelseTable'; -import VelkommenPageHeader from '../../components/velkommen-page-header/VelkommenPageHeader'; -import { useSøknadContext } from '../hooks/useSøknadContext'; -import DeltakelseForm from './DeltakelseForm'; -import Inntektsrapportering from '../../components/inntektsrapportering/Inntektsrapportering'; - -const Forside = () => { - const { - data: { søker, deltakelserIkkeSøktFor, deltakelserSøktFor, alleDeltakelser, deltakelserÅpenForRapportering }, - updateDeltakelse, - } = useSøknadContext(); - - const [søktFor, setSøktFor] = useState(deltakelserSøktFor.map((d) => d.id)); - - const handleSøknadSendt = async (deltakelse: Deltakelse) => { - setSøktFor([...søktFor, deltakelse.id]); - }; - - const erSøktFor = (deltakelse: Deltakelse): boolean => { - return søktFor.includes(deltakelse.id); - }; - - const handleOnClose = async () => { - const data = await deltakerService.getDeltakelser(); - const deltakelser = data.map((d) => ({ ...d, søktFor: erSøktFor(d) })); - updateDeltakelse(deltakelser); - }; - - return ( - - - <> - - - - - {alleDeltakelser.length === 0 ? ( - - Vi kan ikke se at du er registrert for å kunne delta i dette programmet. Hvis du - mener dette ikke stemmer, ta kontakt med din Nav veileder. - - ) : ( - <>Dette er en MVP-app for innsending av data til søknad om ny ungdomsytelse. - )} - - - - {deltakelserIkkeSøktFor.length > 0 && ( - - {deltakelserIkkeSøktFor.map((deltakelse) => { - return ( - - - - ); - })} - - )} - {deltakelserÅpenForRapportering.length === 1 && ( - <> - - - )} - {alleDeltakelser.length > 1 && ( - <> - - Alle deltakelser - - - - )} - - - - ); -}; - -export default Forside; diff --git "a/apps/ungdomsytelse-deltaker/src/types/S\303\270knadContextState.ts" "b/apps/ungdomsytelse-deltaker/src/types/S\303\270knadContextState.ts" deleted file mode 100644 index 451c5230e4..0000000000 --- "a/apps/ungdomsytelse-deltaker/src/types/S\303\270knadContextState.ts" +++ /dev/null @@ -1,5 +0,0 @@ -import { InitialData } from '../hooks/useInitialData'; - -export interface SøknadContextState extends InitialData { - versjon: string; -} diff --git "a/apps/ungdomsytelse-deltaker/src/types/S\303\270knadRoutes.ts" "b/apps/ungdomsytelse-deltaker/src/types/S\303\270knadRoutes.ts" deleted file mode 100644 index d93491d7e6..0000000000 --- "a/apps/ungdomsytelse-deltaker/src/types/S\303\270knadRoutes.ts" +++ /dev/null @@ -1,3 +0,0 @@ -export enum SøknadRoutes { - SOKNAD_ROOT = '', -} diff --git a/apps/ungdomsytelse-deltaker/src/types/appEnv.ts b/apps/ungdomsytelse-deltaker/src/types/appEnv.ts deleted file mode 100644 index 0070d6e0da..0000000000 --- a/apps/ungdomsytelse-deltaker/src/types/appEnv.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { getCommonEnv, getRequiredEnv } from '@navikt/sif-common-env'; - -const getAppEnv = () => ({ - ...getCommonEnv(), - UNG_DELTAKELSE_OPPLYSER_FRONTEND_PATH: getRequiredEnv('UNG_DELTAKELSE_OPPLYSER_FRONTEND_PATH'), -}); - -export const appEnv = getAppEnv(); diff --git "a/apps/ungdomsytelse-deltaker/src/types/s\303\270knadsdata/S\303\270knadsdata.ts" "b/apps/ungdomsytelse-deltaker/src/types/s\303\270knadsdata/S\303\270knadsdata.ts" deleted file mode 100644 index 8041b254c1..0000000000 --- "a/apps/ungdomsytelse-deltaker/src/types/s\303\270knadsdata/S\303\270knadsdata.ts" +++ /dev/null @@ -1,3 +0,0 @@ -export type Søknadsdata = { - todo?: string; -}; diff --git a/apps/ungdomsytelse-deltaker/src/utils/deltakelserUtils.ts b/apps/ungdomsytelse-deltaker/src/utils/deltakelserUtils.ts index bd8c63ab0c..d75dcec34d 100644 --- a/apps/ungdomsytelse-deltaker/src/utils/deltakelserUtils.ts +++ b/apps/ungdomsytelse-deltaker/src/utils/deltakelserUtils.ts @@ -1,7 +1,7 @@ +import { Deltakelse } from '@api/types'; import { DateRange } from '@navikt/sif-common-formik-ds'; import { getDateToday } from '@navikt/sif-common-utils'; import dayjs from 'dayjs'; -import { Deltakelse } from '../api/types'; export const periodeKanRapporteresFor = (periode: DateRange): boolean => { return dayjs(periode.from).isBefore(getDateToday()); diff --git a/apps/ungdomsytelse-deltaker/storybook/components/AlertStoryWrapper.tsx b/apps/ungdomsytelse-deltaker/storybook/components/AlertStoryWrapper.tsx index dc131cda8c..b56cf389cb 100644 --- a/apps/ungdomsytelse-deltaker/storybook/components/AlertStoryWrapper.tsx +++ b/apps/ungdomsytelse-deltaker/storybook/components/AlertStoryWrapper.tsx @@ -1,7 +1,7 @@ import { Heading, VStack } from '@navikt/ds-react'; import { ReactNode } from 'react'; import MessagesList from '@navikt/sif-common-core-ds/src/dev-utils/intl/messages-preview/MessagesList'; -import { storybookIntlUtils } from '../utils/intlUtils'; +import { storybookIntlUtils } from '@utils/intlUtils'; import ShadowBox from './ShadowBox'; const AlertStoryWrapper = ({ diff --git "a/apps/ungdomsytelse-deltaker/storybook/components/Sp\303\270rsm\303\245lWrapper.tsx" "b/apps/ungdomsytelse-deltaker/storybook/components/Sp\303\270rsm\303\245lWrapper.tsx" index 8e27205129..edaeec5117 100644 --- "a/apps/ungdomsytelse-deltaker/storybook/components/Sp\303\270rsm\303\245lWrapper.tsx" +++ "b/apps/ungdomsytelse-deltaker/storybook/components/Sp\303\270rsm\303\245lWrapper.tsx" @@ -1,8 +1,8 @@ import { Box, Button, Tabs, VStack } from '@navikt/ds-react'; import * as React from 'react'; import { MessagesTable } from '@navikt/sif-common-core-ds/src/dev-utils/intl/messages-preview/MessagesList'; -import ShadowBox from '../components/ShadowBox'; -import { storybookIntlUtils } from '../utils/intlUtils'; +import ShadowBox from '@components/ShadowBox'; +import { storybookIntlUtils } from '@utils/intlUtils'; import { StoryFormikWrapper } from './StoryFormikWrapper'; import '@navikt/ds-css'; diff --git a/apps/ungdomsytelse-deltaker/storybook/decorators/withFormikWrapper.tsx b/apps/ungdomsytelse-deltaker/storybook/decorators/withFormikWrapper.tsx index 4dbdb6f16c..0a36371433 100644 --- a/apps/ungdomsytelse-deltaker/storybook/decorators/withFormikWrapper.tsx +++ b/apps/ungdomsytelse-deltaker/storybook/decorators/withFormikWrapper.tsx @@ -1,4 +1,4 @@ -import { StoryFormikWrapper } from '../components/StoryFormikWrapper'; +import { StoryFormikWrapper } from '@components/StoryFormikWrapper'; export const withFormikWrapper = (Story, args) => ( diff --git "a/apps/ungdomsytelse-deltaker/storybook/decorators/withS\303\270knadContext.tsx" "b/apps/ungdomsytelse-deltaker/storybook/decorators/withS\303\270knadContext.tsx" index 920f9cabcb..3ef8d634c0 100644 --- "a/apps/ungdomsytelse-deltaker/storybook/decorators/withS\303\270knadContext.tsx" +++ "b/apps/ungdomsytelse-deltaker/storybook/decorators/withS\303\270knadContext.tsx" @@ -1,8 +1,8 @@ -import { SøknadContextProvider } from '../../app/søknad/context/SøknadContext'; -import { SøknadContextState } from '../../app/types/SøknadContextState'; +import { DeltakerContextProvider } from '../../app/søknad/context/DeltakerContext'; +import { DeltakerContextState } from '../../app/types/DeltakerContextState'; import { RegistrerteBarnMock, SøkerMock } from '../mock-data'; -export const mockInitialSøknadContextState: SøknadContextState = { +export const mockInitialDeltakerContextState: DeltakerContextState = { versjon: '1.0.0', søker: SøkerMock, registrerteBarn: RegistrerteBarnMock, @@ -12,8 +12,8 @@ export const mockInitialSøknadContextState: SøknadContextState = { børMellomlagres: false, }; -export const withSøknadContextProvider = (Story: any, state: Partial = {}) => ( - +export const withDeltakerContextProvider = (Story: any, state: Partial = {}) => ( + - + ); diff --git a/apps/ungdomsytelse-deltaker/tsconfig.json b/apps/ungdomsytelse-deltaker/tsconfig.json index 23378630cd..20ead20675 100644 --- a/apps/ungdomsytelse-deltaker/tsconfig.json +++ b/apps/ungdomsytelse-deltaker/tsconfig.json @@ -2,8 +2,18 @@ "extends": "../../packages/config/tsconfig-apps.json", "compilerOptions": { "outDir": "build/dist", - "typeRoots": ["../../node_modules/@types", "./node_modules/@types"] + "typeRoots": ["../../node_modules/@types", "./node_modules/@types"], + "baseUrl": "./", + "paths": { + "@api/*": ["./src/api/*"], + "@components/*": ["./src/components/*"], + "@context/*": ["./src/context/*"], + "@hooks/*": ["./src/hooks/*"], + "@i18n/*": ["./src/i18n/*"], + "@types/*": ["./src/types/*"], + "@utils/*": ["./src/utils/*"] + } }, "exclude": ["node_modules", "src/build", "**.js", "dist"], - "include": ["./src/**/*"] + "include": ["./src/**/*", "./mock/msw/**/*"] } diff --git a/apps/ungdomsytelse-deltaker/vite.config.ts b/apps/ungdomsytelse-deltaker/vite.config.ts index 50b56c058e..8585b54f2d 100644 --- a/apps/ungdomsytelse-deltaker/vite.config.ts +++ b/apps/ungdomsytelse-deltaker/vite.config.ts @@ -14,7 +14,17 @@ export default defineConfig({ server: { port: 8080, }, - resolve: {}, + resolve: { + alias: { + '@api': '/src/api', + '@components': '/src/components', + '@context': '/src/context', + '@hooks': '/src/hooks', + '@i18n': '/src/i18n', + '@types': '/src/types', + '@utils': '/src/utils', + }, + }, build: { sourcemap: true, }, diff --git a/apps/ungdomsytelse-veileder/api-mock/mock-server.cjs b/apps/ungdomsytelse-veileder/api-mock/mock-server.cjs index b69cc1c933..32c975772a 100644 --- a/apps/ungdomsytelse-veileder/api-mock/mock-server.cjs +++ b/apps/ungdomsytelse-veileder/api-mock/mock-server.cjs @@ -50,14 +50,14 @@ const registrertDeltaker = { }; const deltakelse1 = { - id: '3ebb8cb3-a2eb-45a5-aeee-22a2766aaab0', + id: '3ebb8cb3-a2eb-45a5-aeee-22a2766aaab0-1', deltakerIdent: '03867198392', deltaker: { id: 'd-r', deltakerIdent: '03867198392', }, - fraOgMed: '2024-09-01', - tilOgMed: '2025-01-01', + fraOgMed: '2025-01-01', + tilOgMed: '2025-05-01', harSøkt: true, }; const deltakelse2 = { diff --git a/packages/sif-app-register/src/index.ts b/packages/sif-app-register/src/index.ts index 8ce94b9898..d2070a2c26 100644 --- a/packages/sif-app-register/src/index.ts +++ b/packages/sif-app-register/src/index.ts @@ -191,3 +191,15 @@ export const OpplæringspengerApp: AppInfo = { prod: 'https://www.nav.no/familie/sykdom-i-familien/soknad/opplaringspenger', }, }; + +export const UngdomsytelseDeltakerApp: AppInfo = { + key: 'ungdomsytelse-deltaker', + navn: 'Søknad om deltakelse i ungdomprogrammer', + tittel: { + nb: 'Søknad om deltakelse i ungdomprogrammer', + }, + lenker: { + q: 'https://ungdomsytelse-deltaker.intern.dev.nav.no', + prod: 'https://www.nav.no/familie/sykdom-i-familien/ungdomsytelse-deltaker', + }, +}; diff --git a/yarn.lock b/yarn.lock index 459fd76b28..1ad863bac7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5630,6 +5630,7 @@ __metadata: "@storybook/react-vite": "npm:8.4.7" "@storybook/test": "npm:8.4.7" "@types/node": "npm:22.10.5" + "@types/object-hash": "npm:3.0.6" "@types/react": "npm:19.0.6" "@types/react-dom": "npm:19.0.3" "@typescript-eslint/parser": "npm:8.19.1" @@ -5650,7 +5651,9 @@ __metadata: html-react-parser: "npm:5.2.2" http-proxy-middleware: "npm:3.0.3" jsdom: "npm:25.0.1" + msw: "npm:^2.7.0" mustache-express: "npm:1.3.2" + object-hash: "npm:^3.0.0" postcss: "npm:8.4.49" postcss-import: "npm:16.1.0" postcss-styled-syntax: "npm:0.7.0" @@ -5662,6 +5665,7 @@ __metadata: react-router-dom: "npm:7.1.1" storybook: "npm:8.4.7" stylelint: "npm:16.13.0" + swr: "npm:2.2.5" tailwindcss: "npm:3.4.17" typescript: "npm:5.7.3" typescript-eslint: "npm:8.19.1" @@ -13273,7 +13277,7 @@ __metadata: languageName: node linkType: hard -"client-only@npm:0.0.1": +"client-only@npm:0.0.1, client-only@npm:^0.0.1": version: 0.0.1 resolution: "client-only@npm:0.0.1" checksum: 10/0c16bf660dadb90610553c1d8946a7fdfb81d624adea073b8440b7d795d5b5b08beb3c950c6a2cf16279365a3265158a236876d92bce16423c485c322d7dfaf8 @@ -22002,7 +22006,7 @@ __metadata: languageName: node linkType: hard -"msw@npm:2.7.0": +"msw@npm:2.7.0, msw@npm:^2.7.0": version: 2.7.0 resolution: "msw@npm:2.7.0" dependencies: @@ -27329,6 +27333,18 @@ __metadata: languageName: node linkType: hard +"swr@npm:2.2.5": + version: 2.2.5 + resolution: "swr@npm:2.2.5" + dependencies: + client-only: "npm:^0.0.1" + use-sync-external-store: "npm:^1.2.0" + peerDependencies: + react: ^16.11.0 || ^17.0.0 || ^18.0.0 + checksum: 10/f02b3bd5a198a0f62f9a53d7c0528c4a58aa61a43310bea169614b6e873dadb52599e856ef0775405b6aa7409835343da0cf328948aa892aa309bf4b7e7d6902 + languageName: node + linkType: hard + "swr@npm:2.3.0": version: 2.3.0 resolution: "swr@npm:2.3.0"