diff --git a/.nais/vars/vars-dev1-failover.yml b/.nais/vars/vars-dev1-failover.yml index 38b52a0d7..11b978949 100644 --- a/.nais/vars/vars-dev1-failover.yml +++ b/.nais/vars/vars-dev1-failover.yml @@ -9,7 +9,7 @@ ingresses: - https://www-failover.intern.dev.nav.no resources: requests: - cpu: 100m + cpu: 50m memory: 250Mi limits: memory: 250Mi diff --git a/.nais/vars/vars-dev1.yml b/.nais/vars/vars-dev1.yml index 63b88599b..0a89a470c 100644 --- a/.nais/vars/vars-dev1.yml +++ b/.nais/vars/vars-dev1.yml @@ -11,10 +11,10 @@ ingresses: - https://www.intern.dev.nav.no resources: requests: - cpu: 500m - memory: 1000Mi + cpu: 100m + memory: 384Mi limits: - memory: 2000Mi + memory: 1024Mi valkey: plan: hobbyist project: nav-dev diff --git a/.nais/vars/vars-dev2-failover.yml b/.nais/vars/vars-dev2-failover.yml index 71180fa90..1ad650cf9 100644 --- a/.nais/vars/vars-dev2-failover.yml +++ b/.nais/vars/vars-dev2-failover.yml @@ -9,7 +9,7 @@ ingresses: - https://www-2-failover.intern.dev.nav.no resources: requests: - cpu: 100m + cpu: 50m memory: 250Mi limits: memory: 250Mi diff --git a/.nais/vars/vars-dev2.yml b/.nais/vars/vars-dev2.yml index faf605ebe..5fc39c37e 100644 --- a/.nais/vars/vars-dev2.yml +++ b/.nais/vars/vars-dev2.yml @@ -12,10 +12,10 @@ ingresses: - https://www-2.intern.dev.nav.no resources: requests: - cpu: 250m - memory: 500Mi + cpu: 100m + memory: 384Mi limits: - memory: 1000Mi + memory: 1024Mi valkey: plan: hobbyist project: nav-dev diff --git a/.nais/vars/vars-prod-failover.yml b/.nais/vars/vars-prod-failover.yml index 6fb26e920..45cf54d05 100644 --- a/.nais/vars/vars-prod-failover.yml +++ b/.nais/vars/vars-prod-failover.yml @@ -9,7 +9,7 @@ ingresses: - https://www-failover.nav.no resources: requests: - cpu: 500m - memory: 750Mi + cpu: 250m + memory: 512Mi limits: - memory: 750Mi + memory: 1024Mi diff --git a/.nais/vars/vars-prod.yml b/.nais/vars/vars-prod.yml index 6b5d850f6..b21f5300a 100644 --- a/.nais/vars/vars-prod.yml +++ b/.nais/vars/vars-prod.yml @@ -9,7 +9,7 @@ ingresses: - https://www.nav.no resources: requests: - cpu: 750m + cpu: 600m memory: 2048Mi limits: memory: 4096Mi diff --git a/packages/nextjs/playwright/screenshot.spec.ts-snapshots/components-common-formintermediatesteplink--default-desktop-linux-desktop-linux.png b/packages/nextjs/playwright/screenshot.spec.ts-snapshots/components-common-formintermediatesteplink--default-desktop-linux-desktop-linux.png new file mode 100644 index 000000000..29b1da649 Binary files /dev/null and b/packages/nextjs/playwright/screenshot.spec.ts-snapshots/components-common-formintermediatesteplink--default-desktop-linux-desktop-linux.png differ diff --git a/packages/nextjs/playwright/screenshot.spec.ts-snapshots/components-common-formintermediatesteplink--language-disclaimer-desktop-linux-desktop-linux.png b/packages/nextjs/playwright/screenshot.spec.ts-snapshots/components-common-formintermediatesteplink--language-disclaimer-desktop-linux-desktop-linux.png new file mode 100644 index 000000000..74101128d Binary files /dev/null and b/packages/nextjs/playwright/screenshot.spec.ts-snapshots/components-common-formintermediatesteplink--language-disclaimer-desktop-linux-desktop-linux.png differ diff --git a/packages/nextjs/playwright/screenshot.spec.ts-snapshots/components-common-languagedisclaimer--default-desktop-linux-desktop-linux.png b/packages/nextjs/playwright/screenshot.spec.ts-snapshots/components-common-languagedisclaimer--default-desktop-linux-desktop-linux.png new file mode 100644 index 000000000..e723d4377 Binary files /dev/null and b/packages/nextjs/playwright/screenshot.spec.ts-snapshots/components-common-languagedisclaimer--default-desktop-linux-desktop-linux.png differ diff --git a/packages/nextjs/src/components/_common/formIntermediateStepLink/FormIntermediateStepLink.stories.tsx b/packages/nextjs/src/components/_common/formIntermediateStepLink/FormIntermediateStepLink.stories.tsx new file mode 100644 index 000000000..c08ec30f9 --- /dev/null +++ b/packages/nextjs/src/components/_common/formIntermediateStepLink/FormIntermediateStepLink.stories.tsx @@ -0,0 +1,56 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import { FormIntermediateStepLink } from './FormIntermediateStepLink'; + +const meta = { + component: FormIntermediateStepLink, +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = { + args: { + label: 'Steg 1', + explanation: 'Dette er et eksempel på hva som kan stå her', + href: '/example-path', + isStepNavigation: true, + analyticsComponent: 'mellomsteg', + analyticsLinkGroup: 'Example Group', + analyticsLabel: 'Example Step', + formNumberStepData: 'Nav 04-01.02', + nextStep: { + _selected: 'next', + next: { + editorial: { html: '' }, + stepsHeadline: '', + steps: [], + formNumberStepData: 'Nav 04-01.02', + }, + }, + }, +}; + +export const LanguageDisclaimer: Story = { + args: { + label: 'Steg med språk', + explanation: 'Dette er et eksempel på hva som kan stå her', + languageDisclaimer: 'Kun på bokmål', + href: '/example-path', + isStepNavigation: true, + analyticsComponent: 'mellomsteg', + analyticsLinkGroup: 'Example Group', + analyticsLabel: 'Example Step', + formNumberStepData: 'Nav 04-01.02', + nextStep: { + _selected: 'next', + next: { + editorial: { html: '' }, + stepsHeadline: '', + steps: [], + formNumberStepData: 'Nav 04-01.02', + }, + }, + }, +}; diff --git a/packages/nextjs/src/components/_common/formIntermediateStepLink/FormIntermediateStepLink.tsx b/packages/nextjs/src/components/_common/formIntermediateStepLink/FormIntermediateStepLink.tsx new file mode 100644 index 000000000..887f53e60 --- /dev/null +++ b/packages/nextjs/src/components/_common/formIntermediateStepLink/FormIntermediateStepLink.tsx @@ -0,0 +1,37 @@ +import React from 'react'; +import { LinkPanel } from '@navikt/ds-react'; +import { LenkeBase } from 'components/_common/lenke/lenkeBase/LenkeBase'; +import { FormIntermediateStep_StepLinkData } from 'components/pages/form-intermediate-step-page/useFormIntermediateStepPage'; +import { EditorHelp } from 'components/_editor-only/editor-help/EditorHelp'; +import { LanguageDisclaimer } from 'components/_common/languageDisclaimer/LanguageDisclaimer'; +import FormNumberTag from 'components/_common/formNumberTag/FormNumberTag'; + +type Props = FormIntermediateStep_StepLinkData & + Omit, 'children' | 'href'> & { + formNumberStepData?: string; + }; + +export const FormIntermediateStepLink = ({ + label, + explanation, + languageDisclaimer, + href, + isStepNavigation, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + nextStep, + formNumberStepData, + ...rest +}: Props) => { + if (!href) { + return ; + } + + return ( + + {label} + {formNumberStepData && } + {explanation} + {languageDisclaimer && {languageDisclaimer}} + + ); +}; diff --git a/packages/nextjs/src/components/_common/formNumberTag/FormNumberTag.module.scss b/packages/nextjs/src/components/_common/formNumberTag/FormNumberTag.module.scss new file mode 100644 index 000000000..9897e2ead --- /dev/null +++ b/packages/nextjs/src/components/_common/formNumberTag/FormNumberTag.module.scss @@ -0,0 +1,7 @@ +.formNumberTag { + background-color: var(--a-surface-neutral-subtle); + border-radius: 2px; + padding: 3px 6px; + color: var(--a-gray-700); + width: fit-content; +} diff --git a/packages/nextjs/src/components/_common/formNumberTag/FormNumberTag.tsx b/packages/nextjs/src/components/_common/formNumberTag/FormNumberTag.tsx new file mode 100644 index 000000000..562fb6852 --- /dev/null +++ b/packages/nextjs/src/components/_common/formNumberTag/FormNumberTag.tsx @@ -0,0 +1,11 @@ +import style from './FormNumberTag.module.scss'; + +interface Props { + formNumber: string; +} + +export const FormNumberTag = ({ formNumber }: Props) => { + return
{formNumber}
; +}; + +export default FormNumberTag; diff --git a/packages/nextjs/src/components/_common/headers/headerWithParent/HeaderWithParent.module.scss b/packages/nextjs/src/components/_common/headers/headerWithParent/HeaderWithParent.module.scss index 418905bb6..eec4e4d40 100644 --- a/packages/nextjs/src/components/_common/headers/headerWithParent/HeaderWithParent.module.scss +++ b/packages/nextjs/src/components/_common/headers/headerWithParent/HeaderWithParent.module.scss @@ -15,4 +15,14 @@ font-size: 40px; line-height: 52px; } + + .formNumberListe { + display: flex; + list-style: none; + padding: 0; + align-items: center; + gap: 5px; + margin: 0; + flex-wrap: wrap; + } } diff --git a/packages/nextjs/src/components/_common/headers/headerWithParent/HeaderWithParent.tsx b/packages/nextjs/src/components/_common/headers/headerWithParent/HeaderWithParent.tsx index e6718c371..91ea37478 100644 --- a/packages/nextjs/src/components/_common/headers/headerWithParent/HeaderWithParent.tsx +++ b/packages/nextjs/src/components/_common/headers/headerWithParent/HeaderWithParent.tsx @@ -2,19 +2,25 @@ import { BodyShort, Heading } from '@navikt/ds-react'; import { ProductDataMixin } from 'types/component-props/_mixins'; import { ContentProps } from 'types/content-props/_content-common'; import { classNames } from 'utils/classnames'; +import FormNumberTag from 'components/_common/formNumberTag/FormNumberTag'; import style from './HeaderWithParent.module.scss'; type Props = { contentProps: Pick & { - data: Pick; + data: Pick; }; textAboveTitle?: string; className?: string; + formNumbers?: string[]; }; -export const HeaderWithParent = ({ contentProps, textAboveTitle, className }: Props) => { +export const HeaderWithParent = ({ + contentProps, + textAboveTitle, + className, + formNumbers, +}: Props) => { const { data } = contentProps; - return (
@@ -23,6 +29,13 @@ export const HeaderWithParent = ({ contentProps, textAboveTitle, className }: Pr {data.title} + {formNumbers && ( +
    + {formNumbers.map((nummer, index) => ( + + ))} +
+ )}
); }; diff --git a/packages/nextjs/src/components/_common/languageDisclaimer/LanguageDisclaimer.module.scss b/packages/nextjs/src/components/_common/languageDisclaimer/LanguageDisclaimer.module.scss new file mode 100644 index 000000000..1f63f4c14 --- /dev/null +++ b/packages/nextjs/src/components/_common/languageDisclaimer/LanguageDisclaimer.module.scss @@ -0,0 +1,10 @@ +.languageDisclaimer { + display: flex; + align-items: center; + gap: 0.5rem; + margin-top: 1rem; + + svg { + color: var(--a-icon-subtle); + } +} diff --git a/packages/nextjs/src/components/_common/languageDisclaimer/LanguageDisclaimer.stories.tsx b/packages/nextjs/src/components/_common/languageDisclaimer/LanguageDisclaimer.stories.tsx new file mode 100644 index 000000000..b3dd1e8fa --- /dev/null +++ b/packages/nextjs/src/components/_common/languageDisclaimer/LanguageDisclaimer.stories.tsx @@ -0,0 +1,17 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import { LanguageDisclaimer } from './LanguageDisclaimer'; + +const meta = { + component: LanguageDisclaimer, +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = { + args: { + children: 'Kun på bokmål', + }, +}; diff --git a/packages/nextjs/src/components/_common/languageDisclaimer/LanguageDisclaimer.tsx b/packages/nextjs/src/components/_common/languageDisclaimer/LanguageDisclaimer.tsx new file mode 100644 index 000000000..6c12ebfe5 --- /dev/null +++ b/packages/nextjs/src/components/_common/languageDisclaimer/LanguageDisclaimer.tsx @@ -0,0 +1,12 @@ +import { PropsWithChildren } from 'react'; +import { InformationSquareIcon } from '@navikt/aksel-icons'; +import style from './LanguageDisclaimer.module.scss'; + +export const LanguageDisclaimer = (props: PropsWithChildren) => { + return ( +
+ + {props.children} +
+ ); +}; diff --git a/packages/nextjs/src/components/pages/contact-step-page/ContactStepPage.module.scss b/packages/nextjs/src/components/pages/contact-step-page/ContactStepPage.module.scss index 61c44c1e0..05fdc3b47 100644 --- a/packages/nextjs/src/components/pages/contact-step-page/ContactStepPage.module.scss +++ b/packages/nextjs/src/components/pages/contact-step-page/ContactStepPage.module.scss @@ -37,6 +37,7 @@ .content { grid-area: content; + margin-top: 2rem; .linkPanels { display: flex; diff --git a/packages/nextjs/src/components/pages/contact-step-page/ContactStepPage.tsx b/packages/nextjs/src/components/pages/contact-step-page/ContactStepPage.tsx index 16cf81c8e..8bfbf6b97 100644 --- a/packages/nextjs/src/components/pages/contact-step-page/ContactStepPage.tsx +++ b/packages/nextjs/src/components/pages/contact-step-page/ContactStepPage.tsx @@ -20,6 +20,7 @@ export type ContactStepPageProps = ContentCommonProps & { linkPanelsSubHeading: string; linkPanels: (InternalLinkMixin & { ingress?: string })[]; backLink: InternalLinkMixin; + formNumbers?: string[]; }; }; @@ -33,15 +34,17 @@ export const ContactStepPage = ({ data }: ContactStepPageProps) => { linkPanelsSubHeading, linkPanels, backLink, + formNumbers, } = data; return (
diff --git a/packages/nextjs/src/components/pages/form-intermediate-step-page/FormIntermediateStepLink.tsx b/packages/nextjs/src/components/pages/form-intermediate-step-page/FormIntermediateStepLink.tsx deleted file mode 100644 index 95e2471ba..000000000 --- a/packages/nextjs/src/components/pages/form-intermediate-step-page/FormIntermediateStepLink.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import React from 'react'; -import { LinkPanel } from '@navikt/ds-react'; -import { InfoBox } from 'components/_common/infoBox/InfoBox'; -import { LenkeBase } from 'components/_common/lenke/lenkeBase/LenkeBase'; -import { FormIntermediateStep_StepLinkData } from 'components/pages/form-intermediate-step-page/useFormIntermediateStepPageState'; -import { EditorHelp } from 'components/_editor-only/editor-help/EditorHelp'; - -type Props = FormIntermediateStep_StepLinkData & - Omit, 'children' | 'href'>; - -export const FormIntermediateStepLink = ({ - label, - explanation, - languageDisclaimer, - href, - isStepNavigation, - ...rest -}: Props) => { - if (!href) { - return ; - } - - return ( - <> - {languageDisclaimer && {languageDisclaimer}} - - {label} - {explanation} - - - ); -}; diff --git a/packages/nextjs/src/components/pages/form-intermediate-step-page/FormIntermediateStepPage.module.scss b/packages/nextjs/src/components/pages/form-intermediate-step-page/FormIntermediateStepPage.module.scss index 5b73ed01f..e795db16c 100644 --- a/packages/nextjs/src/components/pages/form-intermediate-step-page/FormIntermediateStepPage.module.scss +++ b/packages/nextjs/src/components/pages/form-intermediate-step-page/FormIntermediateStepPage.module.scss @@ -1,72 +1,78 @@ -@use 'common' as common; - -$padding: 2rem; - .formIntermediateStepPage { - background-color: white; - display: flex; - flex-direction: column; - justify-content: flex-start; - width: 100%; -} + padding-bottom: 1rem; + column-gap: 1.75rem; + margin: 1.25rem auto 2.5rem; + display: grid; + grid-template-columns: 1fr 40rem 1fr; + grid-template-areas: + 'pictogram header .' + '. content .' + '. backButton .'; -.content { - margin-top: 2rem; - display: flex; - flex-direction: column; - justify-content: flex-start; - align-self: center; - width: 100%; - max-width: common.$contentMaxWidth; - - & > * { - width: 100%; + @media only screen and (max-width: 1024px) { + grid-template-columns: none; + grid-template-areas: + '. pictogram .' + '. header .' + '. content .' + '. backButton .'; } -} + .pictogram { + grid-area: pictogram; + display: block; + justify-self: end; + width: 4.5rem; + height: auto; + position: relative; + align-self: flex-start; -.stepOptionsWrapper { - display: flex; - flex-direction: column; - align-items: flex-start; - max-width: common.$sectionMaxWidth; -} + @media only screen and (max-width: 1024px) { + width: 4rem; + justify-self: start; + } + } -.stepList { - list-style-type: none; - margin: 0; - padding: 0; -} + .header { + grid-area: header; + } -.stepItem { - margin-bottom: var(--a-spacing-7); -} + .content { + grid-area: content; + margin-top: 2rem; -.stepAction { - &:global(.navds-link-panel) { - border-color: var(--default-action-color); - } - & :global { - .navds-link-panel__title, - .navds-link-panel__chevron { - color: var(--default-action-color); + & > * { + width: 100%; } - } - @media #{common.$mq-screen-mobile} { - padding: 0.75rem; - } -} -.buttonGroup { - display: flex; -} + .stepList { + list-style-type: none; + margin: 0; + padding: 0; -.backButton { - text-decoration-line: underline; - text-underline-offset: 4px; - padding: 0; - margin-top: 1rem; + .stepItem { + margin-bottom: var(--a-spacing-7); - &:hover { - background-color: transparent; + .stepAction { + &:global(.navds-link-panel) { + border-color: var(--default-action-color); + } + } + } + } + .buttonGroup { + display: flex; + } + + .backButton { + grid-area: backButton; + text-decoration-line: underline; + text-underline-offset: 4px; + padding: 0; + margin-top: 1rem; + + &:hover { + background-color: transparent; + } + } } } diff --git a/packages/nextjs/src/components/pages/form-intermediate-step-page/FormIntermediateStepPage.tsx b/packages/nextjs/src/components/pages/form-intermediate-step-page/FormIntermediateStepPage.tsx index 47338cb49..ef5606e41 100644 --- a/packages/nextjs/src/components/pages/form-intermediate-step-page/FormIntermediateStepPage.tsx +++ b/packages/nextjs/src/components/pages/form-intermediate-step-page/FormIntermediateStepPage.tsx @@ -1,78 +1,86 @@ import React from 'react'; import { Button, Heading } from '@navikt/ds-react'; import { translator } from 'translations'; -import { ThemedPageHeader } from 'components/_common/headers/themedPageHeader/ThemedPageHeader'; -import { FormIntermediateStepPageProps } from 'types/content-props/form-intermediate-step'; +import { StepBase } from 'types/content-props/form-intermediate-step'; import { ParsedHtml } from 'components/_common/parsedHtml/ParsedHtml'; import { LenkeBase } from 'components/_common/lenke/lenkeBase/LenkeBase'; -import { useFormIntermediateStepPageState } from 'components/pages/form-intermediate-step-page/useFormIntermediateStepPageState'; -import { FormIntermediateStepLink } from 'components/pages/form-intermediate-step-page/FormIntermediateStepLink'; +import { useFormIntermediateStepPage } from 'components/pages/form-intermediate-step-page/useFormIntermediateStepPage'; +import { FormIntermediateStepLink } from 'components/_common/formIntermediateStepLink/FormIntermediateStepLink'; +import { HeaderWithParent } from 'components/_common/headers/headerWithParent/HeaderWithParent'; +import { IllustrationStatic } from 'components/_common/illustration/static/IllustrationStatic'; +import { ContentCommonProps, ContentType } from 'types/content-props/_content-common'; +import { PictogramsProps } from 'types/content-props/pictograms'; +import { Taxonomy } from 'types/taxonomies'; import style from './FormIntermediateStepPage.module.scss'; -export const FormIntermediateStepPage = (props: FormIntermediateStepPageProps) => { - const { language, type, displayName, modifiedTime, data } = props; - const { title, illustration } = data; +export type FormIntermediateStepPageProps = ContentCommonProps & { + type: ContentType.FormIntermediateStepPage; + data: { + title: string; + illustration: PictogramsProps; + taxonomy?: Taxonomy[]; + customCategory: string; + textAboveTitle?: string; + formNumbers?: string[]; + } & StepBase; +}; - const { currentStepData, backUrl } = useFormIntermediateStepPageState(props); +export const FormIntermediateStepPage = (props: FormIntermediateStepPageProps) => { + const { language, data } = props; + const { title, illustration, textAboveTitle, formNumbers } = data; + const { currentStepData, backUrl, previousStepTitle } = useFormIntermediateStepPage(props); const getTranslations = translator('form', language); return (
- +
-
- - {currentStepData.stepsHeadline && ( - - {currentStepData.stepsHeadline} - - )} -
    - {currentStepData.steps.map((step) => ( -
  • - -
  • - ))} -
-
- {backUrl && ( -
- -
+ + {previousStepTitle &&
Forrige steg: {previousStepTitle}
} + {currentStepData.stepsHeadline && ( + + {currentStepData.stepsHeadline} + )} +
    + {currentStepData.steps.map((step) => ( +
  • + +
  • + ))} +
+ {backUrl && ( +
+ +
+ )}
); }; diff --git a/packages/nextjs/src/components/pages/form-intermediate-step-page/useFormIntermediateStepPage.tsx b/packages/nextjs/src/components/pages/form-intermediate-step-page/useFormIntermediateStepPage.tsx new file mode 100644 index 000000000..6e70c7092 --- /dev/null +++ b/packages/nextjs/src/components/pages/form-intermediate-step-page/useFormIntermediateStepPage.tsx @@ -0,0 +1,176 @@ +import { useEffect, useState } from 'react'; +import { useRouter } from 'next/compat/router'; +import { SelectableStep, StepBase } from 'types/content-props/form-intermediate-step'; +import { stripXpPathPrefix } from 'utils/urls'; +import { FormIntermediateStepPageProps } from './FormIntermediateStepPage'; + +const STEP_PARAM = 'stegvalg'; + +export type FormIntermediateStep_StepLinkData = SelectableStep & { + href?: string; + isStepNavigation?: boolean; +}; + +type StepPath = number[]; +const buildStepUrl = (basePath: string, stepPath: StepPath) => + `${basePath}?${STEP_PARAM}=${stepPath.join(',')}`; + +const resolveStepUrl = ({ + step, + nextStepPath, + basePath, +}: { + step: SelectableStep; + nextStepPath: StepPath; + basePath: string; +}): FormIntermediateStep_StepLinkData => { + switch (step.nextStep?._selected) { + case 'external': { + return { + ...step, + href: step.nextStep.external?.externalUrl, + }; + } + case 'internal': { + return { + ...step, + href: stripXpPathPrefix(step.nextStep.internal?.internalContent._path), + }; + } + default: { + return { + ...step, + href: buildStepUrl(basePath, nextStepPath), + isStepNavigation: true, + }; + } + } +}; + +const getStepData = (data: FormIntermediateStepPageProps['data'], stepPath: StepPath): StepBase => { + // No steps selected (meaning the user is on first step) + if (stepPath.length === 0) { + return { + editorial: data.editorial, + stepsHeadline: data.stepsHeadline, + steps: data.steps, + }; + } + + let tmp: any = data; + + stepPath.forEach((index) => { + const foundStep = tmp.steps[index]; + if (foundStep) { + tmp = foundStep.nextStep?.next; + } + }); + + const stepDetails = tmp.nextStep; + if (stepDetails?._selected === 'next') { + return stepDetails.next; + } + + return { + editorial: tmp.editorial, + stepsHeadline: tmp.stepsHeadline, + steps: tmp.steps, + }; +}; + +const buildCurrentStepData = ( + allData: FormIntermediateStepPageProps['data'], + basePath: string, + stepPath: StepPath +): StepBase => { + const stepData = getStepData(allData, stepPath); + + return { + ...stepData, + steps: stepData.steps.map((step, index) => + resolveStepUrl({ step, nextStepPath: [...stepPath, index], basePath }) + ), + }; +}; + +const buildBackUrl = (basePath: string, stepPath: StepPath): string | null => { + if (stepPath.length === 0) { + return null; // No back URL if on the first step + } + + if (stepPath.length === 1) { + return basePath; // Back to the first step + } + return buildStepUrl(basePath, stepPath.slice(0, -1)); +}; + +const getStepPathFromParam = (url: string): StepPath => { + const stepQuery = new URL(url, window.location.origin).searchParams.get(STEP_PARAM); + const stepPath = stepQuery ? stepQuery.split(',').map(Number) : []; + if (stepPath.some(isNaN)) { + return []; + } + return stepPath; +}; + +const getPreviousStepTitle = ( + stepPath: StepPath, + allData: FormIntermediateStepPageProps['data'] +) => { + if (stepPath.length === 0) { + return null; // No previous step title if on the first step + } + + const previousStepPath = stepPath.slice(0, -1); + + // Previous step was the first page, so just get the + // headline from the data root. + if (previousStepPath.length === 0) { + return allData.stepsHeadline; + } + + // Traverse the tree to find the previous step. + let step: StepBase = allData; + previousStepPath.forEach((index) => { + const foundStep = step.steps[index]; + if (foundStep) { + step = foundStep.nextStep?.next; + } + }); + + return step.stepsHeadline; +}; + +export const useFormIntermediateStepPage = (props: FormIntermediateStepPageProps) => { + const [stepPath, setStepPath] = useState([]); + const router = useRouter(); + + const pagePath = stripXpPathPrefix(props._path); + const currentStepData = buildCurrentStepData(props.data, pagePath, stepPath); + + const backUrl = buildBackUrl(pagePath, stepPath); + const previousStepTitle = getPreviousStepTitle(stepPath, props.data); + + useEffect(() => { + if (!router) { + return; + } + + const handleRouteChange = (url: string) => { + setStepPath(getStepPathFromParam(url)); + }; + + handleRouteChange(router.asPath); + + router.events.on('routeChangeComplete', handleRouteChange); + return () => { + router.events.off('routeChangeComplete', handleRouteChange); + }; + }, [router]); + + return { + currentStepData, + backUrl, + previousStepTitle, + }; +}; diff --git a/packages/nextjs/src/components/pages/form-intermediate-step-page/useFormIntermediateStepPageState.tsx b/packages/nextjs/src/components/pages/form-intermediate-step-page/useFormIntermediateStepPageState.tsx deleted file mode 100644 index 320396888..000000000 --- a/packages/nextjs/src/components/pages/form-intermediate-step-page/useFormIntermediateStepPageState.tsx +++ /dev/null @@ -1,119 +0,0 @@ -import { useEffect, useState } from 'react'; -import { useRouter } from 'next/compat/router'; -import { - FormIntermediateStepPageProps, - FormIntermediateStep_CompoundedStepData, - FormIntermediateStep_StepBase, - FormIntermediateStep_StepData, - FormIntermediateStep_StepLevel, -} from 'types/content-props/form-intermediate-step'; -import { stripXpPathPrefix } from 'utils/urls'; - -const STEP_PARAM = 'stegvalg'; - -export type FormIntermediateStep_StepLinkData = FormIntermediateStep_StepBase & { - href?: string; - isStepNavigation?: boolean; -}; - -type CurrentStepData = FormIntermediateStep_StepData; - -const buildStepUrl = (basePath: string, stepIndex: number) => - `${basePath}?${STEP_PARAM}=${stepIndex}`; - -const resolveStepUrl = ( - step: FormIntermediateStep_StepLevel, - stepIndex: number, - basePath: string -): FormIntermediateStep_StepLinkData => { - switch (step.nextStep?._selected) { - case 'external': { - return { - ...step, - href: step.nextStep.external?.externalUrl, - }; - } - case 'internal': { - return { - ...step, - href: stripXpPathPrefix(step.nextStep.internal?.internalContent._path), - }; - } - default: { - return { - ...step, - href: buildStepUrl(basePath, stepIndex), - isStepNavigation: true, - }; - } - } -}; - -const getStepData = ( - data: FormIntermediateStepPageProps['data'], - selectedStepIndex: number | null -): FormIntermediateStep_CompoundedStepData => { - if (selectedStepIndex !== null) { - const stepDetails = data.steps[selectedStepIndex].nextStep; - if (stepDetails?._selected === 'next') { - return stepDetails.next; - } - } - - return { - editorial: data.editorial, - stepsHeadline: data.stepsHeadline, - steps: data.steps, - }; -}; - -const buildResolvedStepData = ( - data: FormIntermediateStepPageProps['data'], - basePath: string, - prevSelectedStep: number | null -): CurrentStepData => { - const stepData = getStepData(data, prevSelectedStep); - - return { - ...stepData, - steps: stepData.steps.map((step, index) => resolveStepUrl(step, index, basePath)), - }; -}; - -const getSelectedStepFromParam = (url: string) => { - const stepQuery = new URL(url, window.location.origin).searchParams.get(STEP_PARAM); - return stepQuery ? Number(stepQuery) : null; -}; - -export const useFormIntermediateStepPageState = (props: FormIntermediateStepPageProps) => { - const [selectedStepIndex, setSelectedStepIndex] = useState(null); - const router = useRouter(); - - const pagePath = stripXpPathPrefix(props._path); - - const currentStepData = buildResolvedStepData(props.data, pagePath, selectedStepIndex); - - const backUrl = selectedStepIndex !== null ? pagePath : null; - - useEffect(() => { - if (!router) { - return; - } - - const handleRouteChange = (url: string) => { - setSelectedStepIndex(getSelectedStepFromParam(url)); - }; - - handleRouteChange(router.asPath); - - router.events.on('routeChangeComplete', handleRouteChange); - return () => { - router.events.off('routeChangeComplete', handleRouteChange); - }; - }, [router]); - - return { - currentStepData, - backUrl, - }; -}; diff --git a/packages/nextjs/src/types/content-props/_content-common.ts b/packages/nextjs/src/types/content-props/_content-common.ts index 98bffe8ef..b1c5781f0 100644 --- a/packages/nextjs/src/types/content-props/_content-common.ts +++ b/packages/nextjs/src/types/content-props/_content-common.ts @@ -10,6 +10,9 @@ import { SiteProps } from 'types/content-props/site-props'; import { FormsOverviewProps } from 'types/content-props/forms-overview'; import { OverviewPageProps } from 'types/content-props/overview-props'; import { ContactStepPageProps } from 'components/pages/contact-step-page/ContactStepPage'; +import { + FormIntermediateStepPageProps +} from 'components/pages/form-intermediate-step-page/FormIntermediateStepPage'; import { ExternalLinkProps } from './external-link-props'; import { InternalLinkProps } from './internal-link-props'; import { ContentListProps } from './content-list-props'; @@ -43,7 +46,6 @@ import { PayoutDatesProps } from './payout-dates'; import { FragmentPageProps } from './fragment-page-props'; import { AreaPageProps, FrontPageNestedProps, FrontPageProps } from './index-pages-props'; import { FormDetailsPageProps } from './form-details'; -import { FormIntermediateStepPageProps } from './form-intermediate-step'; import { FallbackPageProps } from './fallback-page-props'; export enum ContentType { diff --git a/packages/nextjs/src/types/content-props/form-intermediate-step.ts b/packages/nextjs/src/types/content-props/form-intermediate-step.ts index 9131e7563..445701852 100644 --- a/packages/nextjs/src/types/content-props/form-intermediate-step.ts +++ b/packages/nextjs/src/types/content-props/form-intermediate-step.ts @@ -1,55 +1,26 @@ import { ProcessedHtmlProps } from 'types/processed-html-props'; -import { Taxonomy } from 'types/taxonomies'; import { OptionSetSingle } from 'types/util-types'; -import { ContentCommonProps, ContentType } from './_content-common'; -import { PictogramsProps } from './pictograms'; -export type FormIntermediateStep_StepData< - NextStep extends FormIntermediateStep_StepBase = FormIntermediateStep_StepBase, -> = { - editorial: ProcessedHtmlProps; - stepsHeadline: string; - steps: NextStep[]; +type ExternalOption = any; +type InternalOption = any; +type NextOption = any; + +export type StepOptions = { + external?: ExternalOption; + internal?: InternalOption; + next?: NextOption; }; -export type FormIntermediateStep_StepBase = { +export type SelectableStep = { + formNumberStepData: string | undefined; label: string; explanation: string; languageDisclaimer?: string; + nextStep: OptionSetSingle; }; -type StepBaseOptions = { - external: { - externalUrl: string; - }; - internal: { - internalContent: ContentCommonProps; - }; -}; - -type StepLevel1 = FormIntermediateStep_StepBase & { - nextStep: OptionSetSingle< - StepBaseOptions & { - next: FormIntermediateStep_StepData; - } - >; -}; - -type StepLevel2 = FormIntermediateStep_StepBase & { - nextStep: OptionSetSingle; -}; - -export type FormIntermediateStep_StepLevel = StepLevel1 | StepLevel2; - -export type FormIntermediateStep_CompoundedStepData = - FormIntermediateStep_StepData; - -export type FormIntermediateStepPageProps = ContentCommonProps & { - type: ContentType.FormIntermediateStepPage; - data: { - title: string; - illustration: PictogramsProps; - taxonomy?: Taxonomy[]; - customCategory: string; - } & FormIntermediateStep_StepData; +export type StepBase = { + editorial: ProcessedHtmlProps; + stepsHeadline: string; + steps: SelectableStep[]; };