{
- opprettDialogEtterRender();
return harIngenDialoger() ? [] : [...dialoger];
};
diff --git a/src/mock/handlers.ts b/src/mock/handlers.ts
index 3e0b2698..54c07dd4 100644
--- a/src/mock/handlers.ts
+++ b/src/mock/handlers.ts
@@ -58,6 +58,10 @@ const internalServerError = (ctx: RestContext) => {
};
export const handlers = [
+ rest.get(
+ '/veilarbaktivitet/api/feature',
+ jsonResponse({ [FeatureToggle.VIS_SKJUL_AKTIVITET_KNAPP]: harCompactModeSkruddPa() })
+ ),
rest.get('/auth/info', jsonResponse({ remainingSeconds: 60 * 60 })),
// veilarbdialog
@@ -88,10 +92,6 @@ export const handlers = [
'/veilarbaktivitet/api/arena/tiltak',
failOrGetResponse(harArenaaktivitetFeilerSkruddPa, () => arenaAktiviteter)
),
- rest.get(
- '/veilarbaktivitet/api/feature',
- jsonResponse({ [FeatureToggle.VIS_SKJUL_AKTIVITET_KNAPP]: harCompactModeSkruddPa() })
- ),
// veilarbveileder
rest.get(`/veilarbveileder/api/veileder/me`, jsonResponse(veilederMe)),
diff --git a/src/mock/index.tsx b/src/mock/index.tsx
index ac51ef4c..623a6f40 100644
--- a/src/mock/index.tsx
+++ b/src/mock/index.tsx
@@ -4,8 +4,10 @@ import { createRoot } from 'react-dom/client';
import DemoBanner from './demo/DemoBanner';
import { handlers } from './handlers';
+import { opprettDialogEtterRender } from './Dialog';
const worker = setupWorker(...handlers);
+opprettDialogEtterRender();
export default () =>
worker
diff --git a/src/routes.ts b/src/routes.ts
deleted file mode 100644
index 117f1d6e..00000000
--- a/src/routes.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-const aktivitetQuery = (aktivitetId?: string) => (aktivitetId ? `?aktivitetId=${aktivitetId}` : '');
-
-const baseRoute = () => '/';
-const dialogRoute = (id: string) => `/${id}`;
-const nyRoute = (aktivitetId: string | undefined = undefined) => `/ny${aktivitetQuery(aktivitetId)}`;
-const informasjonRoute = () => '/informasjon';
-
-export const useRoutes = () => {
- return {
- baseRoute,
- dialogRoute,
- nyRoute,
- informasjonRoute
- };
-};
diff --git a/src/routes.tsx b/src/routes.tsx
new file mode 100644
index 00000000..58a6466f
--- /dev/null
+++ b/src/routes.tsx
@@ -0,0 +1,93 @@
+import AppBody from './view/AppBody';
+import NyDialog from './view/dialog/NyDialog';
+import { Aktivitetskort } from './view/aktivitet/Aktivitetskort';
+import Dialog from './view/dialog/Dialog';
+import DialogInfoMelding from './view/dialog/DialogInfoMelding';
+import { Navigate, RouteObject, RouterProvider, useParams } from 'react-router';
+import React from 'react';
+import { erInternFlate, USE_HASH_ROUTER } from './constants';
+import { createBrowserRouter, createHashRouter, useSearchParams } from 'react-router-dom';
+import { stripTrailingSlash } from './api/UseApiBasePath';
+
+const aktivitetQuery = (aktivitetId?: string) => (aktivitetId ? `?aktivitetId=${aktivitetId}` : '');
+
+const baseRoute = () => '/';
+const dialogRoute = (id: string) => `/${id}`;
+const nyRoute = (aktivitetId: string | undefined = undefined) => `/ny${aktivitetQuery(aktivitetId)}`;
+const informasjonRoute = () => '/informasjon';
+
+export const useRoutes = () => {
+ return {
+ baseRoute,
+ dialogRoute,
+ nyRoute,
+ informasjonRoute
+ };
+};
+
+const RedirectToDialogWithoutFnr = () => {
+ // /:fnr/:dialogId -> /:dialogId
+ const params = useParams();
+ return
;
+};
+const RedirectToNyDialogWithoutFnr = () => {
+ // Handle route /:fnr/ny?aktivitetId=
-> /ny?aktivitetId=
+ const [queryParams, _] = useSearchParams();
+ const aktivitetId = queryParams.get('aktivitetId');
+ const queryPart = aktivitetId ? '?aktivitetId=' + aktivitetId : '';
+ return ;
+};
+
+export const dialogRoutes: RouteObject[] = [
+ {
+ path: '/',
+ element: ,
+ children: [
+ {
+ path: 'ny',
+ element: (
+ <>
+
+
+ >
+ )
+ },
+ {
+ path: ':dialogId',
+ element: (
+ <>
+
+
+ >
+ )
+ },
+ {
+ path: '',
+ element:
+ },
+ {
+ path: ':fnr/ny',
+ element:
+ },
+ {
+ path: ':fnr/:dialogId',
+ element:
+ },
+ {
+ path: '*',
+ element:
+ }
+ ]
+ }
+];
+
+export const Routes = () => {
+ if (USE_HASH_ROUTER) {
+ const hashRouter = createHashRouter(dialogRoutes);
+ return ;
+ }
+ let basename = stripTrailingSlash(import.meta.env.BASE_URL);
+ if (erInternFlate) basename = `/`;
+ const browserRouter = createBrowserRouter(dialogRoutes, { basename });
+ return ;
+};
diff --git a/src/stories/Button.stories.ts b/src/stories/Button.stories.ts
deleted file mode 100644
index 46ff7b61..00000000
--- a/src/stories/Button.stories.ts
+++ /dev/null
@@ -1,58 +0,0 @@
-import type { Meta, StoryObj } from '@storybook/react';
-
-import { Button } from './Button';
-
-// More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction#default-export
-const meta = {
- title: 'Example/Button',
- component: Button,
- parameters: {
- // Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/react/configure/story-layout
- layout: 'centered'
- },
- // This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/react/writing-docs/autodocs
- tags: ['autodocs'],
- // More on argTypes: https://storybook.js.org/docs/react/api/argtypes
- argTypes: {
- backgroundColor: { control: 'color' }
- }
-} satisfies Meta;
-
-export default meta;
-type Story = StoryObj;
-
-// More on writing stories with args: https://storybook.js.org/docs/react/writing-stories/args
-export const Primary: Story = {
- args: {
- primary: true,
- label: 'Button'
- }
-};
-
-export const Secondary: Story = {
- args: {
- label: 'Button'
- }
-};
-
-export const Large: Story = {
- args: {
- size: 'large',
- label: 'Button'
- }
-};
-
-export const Small: Story = {
- args: {
- size: 'small',
- label: 'Button'
- }
-};
-
-export const Warning: Story = {
- args: {
- primary: true,
- label: 'Delete now',
- backgroundColor: 'red'
- }
-};
diff --git a/src/stories/Button.tsx b/src/stories/Button.tsx
deleted file mode 100644
index 0d843501..00000000
--- a/src/stories/Button.tsx
+++ /dev/null
@@ -1,42 +0,0 @@
-import React from 'react';
-import './button.css';
-
-interface ButtonProps {
- /**
- * Is this the principal call to action on the page?
- */
- primary?: boolean;
- /**
- * What background color to use
- */
- backgroundColor?: string;
- /**
- * How large should the button be?
- */
- size?: 'small' | 'medium' | 'large';
- /**
- * Button contents
- */
- label: string;
- /**
- * Optional click handler
- */
- onClick?: () => void;
-}
-
-/**
- * Primary UI component for user interaction
- */
-export const Button = ({ primary = false, size = 'medium', backgroundColor, label, ...props }: ButtonProps) => {
- const mode = primary ? 'storybook-button--primary' : 'storybook-button--secondary';
- return (
-
- );
-};
diff --git a/src/stories/Header.stories.ts b/src/stories/Header.stories.ts
deleted file mode 100644
index 44eed897..00000000
--- a/src/stories/Header.stories.ts
+++ /dev/null
@@ -1,27 +0,0 @@
-import type { Meta, StoryObj } from '@storybook/react';
-
-import { Header } from './Header';
-
-const meta = {
- title: 'Example/Header',
- component: Header,
- // This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/react/writing-docs/autodocs
- tags: ['autodocs'],
- parameters: {
- // More on how to position stories at: https://storybook.js.org/docs/react/configure/story-layout
- layout: 'fullscreen'
- }
-} satisfies Meta;
-
-export default meta;
-type Story = StoryObj;
-
-export const LoggedIn: Story = {
- args: {
- user: {
- name: 'Jane Doe'
- }
- }
-};
-
-export const LoggedOut: Story = {};
diff --git a/src/stories/Header.tsx b/src/stories/Header.tsx
deleted file mode 100644
index c9a93346..00000000
--- a/src/stories/Header.tsx
+++ /dev/null
@@ -1,50 +0,0 @@
-import React from 'react';
-
-import { Button } from './Button';
-import './header.css';
-
-type User = {
- name: string;
-};
-
-interface HeaderProps {
- user?: User;
- onLogin: () => void;
- onLogout: () => void;
- onCreateAccount: () => void;
-}
-
-export const Header = ({ user, onLogin, onLogout, onCreateAccount }: HeaderProps) => (
-
-);
diff --git a/src/stories/Page.stories.ts b/src/stories/Page.stories.ts
deleted file mode 100644
index 8d5aecb4..00000000
--- a/src/stories/Page.stories.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-import type { Meta, StoryObj } from '@storybook/react';
-import { within, userEvent } from '@storybook/testing-library';
-
-import { Page } from './Page';
-
-const meta = {
- title: 'Example/Page',
- component: Page,
- parameters: {
- // More on how to position stories at: https://storybook.js.org/docs/react/configure/story-layout
- layout: 'fullscreen'
- }
-} satisfies Meta;
-
-export default meta;
-type Story = StoryObj;
-
-export const CompactMode: Story = {};
-// More on interaction testing: https://storybook.js.org/docs/react/writing-tests/interaction-testing
-export const VanligMode: Story = {
- play: async ({ canvasElement }) => {
- const canvas = within(canvasElement);
- const loginButton = await canvas.getByRole('button', {
- name: /Log in/i
- });
- await userEvent.click(loginButton);
- }
-};
diff --git a/src/stories/Page.tsx b/src/stories/Page.tsx
index 2d3ac760..d9cc8794 100644
--- a/src/stories/Page.tsx
+++ b/src/stories/Page.tsx
@@ -1,14 +1,41 @@
-import React from 'react';
+import React, { useEffect } from 'react';
import '../global.css';
-import App from '../App';
-
-type User = {
- name: string;
-};
+import { Provider } from '../view/Provider';
+import StatusAdvarsel from '../view/statusAdvarsel/StatusAdvarsel';
+import { UppdateEventHandler } from '../utils/UpdateEvent';
+import cx from 'classnames';
+import { Routes } from '../routes';
+interface PageProps {
+ visAktivitet?: boolean;
+ path?: string;
+ erVeileder: boolean;
+}
const fnr = '1234567890';
-export const Page: React.FC = () => {
- return ;
+
+// "Forked" from app to avoid add props only used for storybook dx
+export const Page: React.FC = (props) => {
+ useEffect(() => {
+ window.location.hash = `#${props.path}`;
+ }, []);
+ const { visAktivitet, erVeileder } = props;
+ console.log({ erVeileder });
+ return (
+ <>
+
+
+
+
+
+
+
+ >
+ );
};
diff --git a/src/stories/PageCompact.stories.tsx b/src/stories/PageCompact.stories.tsx
new file mode 100644
index 00000000..687198e8
--- /dev/null
+++ b/src/stories/PageCompact.stories.tsx
@@ -0,0 +1,95 @@
+import type { Meta, StoryObj } from '@storybook/react';
+
+import { Page } from './Page';
+import { rest } from 'msw';
+import { FeatureToggle } from '../featureToggle/const';
+import { eksternbruker, veileder } from '../mock/Bruker';
+
+const meta = {
+ title: 'App/Compact',
+ component: Page,
+ parameters: {
+ chromatic: { delay: 500 },
+ // More on how to position stories at: https://storybook.js.org/docs/react/configure/story-layout
+ layout: 'fullscreen',
+ msw: {
+ handlers: {
+ brukerMock: [
+ rest.get('/veilarboppfolging/api/oppfolging/me', (_, res, ctx) => res(ctx.json(veileder)))
+ ],
+ featureToggle: [
+ rest.get('/veilarbaktivitet/api/feature', (_, res, ctx) =>
+ res(ctx.json({ [FeatureToggle.VIS_SKJUL_AKTIVITET_KNAPP]: true }))
+ )
+ ]
+ }
+ }
+ },
+ args: {
+ path: '/'
+ },
+ argTypes: {
+ visAktivitet: {
+ name: 'Vis aktivitet',
+ options: [false, true],
+ type: 'boolean'
+ }
+ }
+} satisfies Meta;
+
+export default meta;
+type Story = StoryObj;
+// More on interaction testing: https://storybook.js.org/docs/react/writing-tests/interaction-testing
+// export const CompactMode: Story = {};
+
+export const Landing: Story = {
+ args: {
+ path: '/',
+ erVeileder: true
+ }
+};
+
+export const MedAktivitetVisAktivitet: Story = {
+ args: {
+ visAktivitet: true,
+ path: '/10',
+ erVeileder: true
+ }
+};
+
+export const MedAktivitetIkkeVisAktivitet: Story = {
+ args: {
+ visAktivitet: false,
+ path: '/10',
+ erVeileder: true
+ }
+};
+
+export const DialogUtenAktivitet: Story = {
+ args: {
+ path: '/2',
+ erVeileder: true
+ }
+};
+
+export const HistoriskVisAktivitet: Story = {
+ args: {
+ visAktivitet: true,
+ path: '100',
+ erVeileder: true
+ }
+};
+export const HistoriskIkkeVisAktivitet: Story = {
+ args: {
+ visAktivitet: false,
+ path: '100',
+ erVeileder: true
+ }
+};
+
+export const NyDialog: Story = {
+ args: {
+ path: '/ny',
+ erVeileder: true
+ }
+};
diff --git a/src/stories/PageDefault.stories.ts b/src/stories/PageDefault.stories.ts
new file mode 100644
index 00000000..99217544
--- /dev/null
+++ b/src/stories/PageDefault.stories.ts
@@ -0,0 +1,62 @@
+import type { Meta, StoryObj } from '@storybook/react';
+
+import { Page } from './Page';
+import { rest } from 'msw';
+import { FeatureToggle } from '../featureToggle/const';
+import bruker, { eksternbruker } from '../mock/Bruker';
+
+const meta = {
+ title: 'App/Default',
+ component: Page,
+ parameters: {
+ chromatic: { delay: 500 },
+ // More on how to position stories at: https://storybook.js.org/docs/react/configure/story-layout
+ layout: 'fullscreen',
+ msw: {
+ handlers: {
+ brukerMock: [
+ rest.get('/veilarboppfolging/api/oppfolging/me', (_, res, ctx) => res(ctx.json(eksternbruker)))
+ ]
+ }
+ }
+ }
+} satisfies Meta;
+
+export default meta;
+type Story = StoryObj;
+
+// More on interaction testing: https://storybook.js.org/docs/react/writing-tests/interaction-testing
+export const Landing: Story = {
+ args: {
+ path: '/',
+ erVeileder: false
+ }
+};
+
+export const DialogMedAktivitet: Story = {
+ args: {
+ path: '/303',
+ erVeileder: false
+ }
+};
+
+export const DialogUtenAktivitet: Story = {
+ args: {
+ path: '/2',
+ erVeileder: false
+ }
+};
+
+export const Historisk: Story = {
+ args: {
+ path: '100',
+ erVeileder: false
+ }
+};
+
+export const NyDialog: Story = {
+ args: {
+ path: '/ny',
+ erVeileder: false
+ }
+};
diff --git a/src/stories/button.css b/src/stories/button.css
deleted file mode 100644
index 61c9c11f..00000000
--- a/src/stories/button.css
+++ /dev/null
@@ -1,30 +0,0 @@
-.storybook-button {
- font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
- font-weight: 700;
- border: 0;
- border-radius: 3em;
- cursor: pointer;
- display: inline-block;
- line-height: 1;
-}
-.storybook-button--primary {
- color: white;
- background-color: #1ea7fd;
-}
-.storybook-button--secondary {
- color: #333;
- background-color: transparent;
- box-shadow: rgba(0, 0, 0, 0.15) 0px 0px 0px 1px inset;
-}
-.storybook-button--small {
- font-size: 12px;
- padding: 10px 16px;
-}
-.storybook-button--medium {
- font-size: 14px;
- padding: 11px 20px;
-}
-.storybook-button--large {
- font-size: 16px;
- padding: 12px 24px;
-}
diff --git a/src/stories/header.css b/src/stories/header.css
deleted file mode 100644
index e3076607..00000000
--- a/src/stories/header.css
+++ /dev/null
@@ -1,32 +0,0 @@
-.storybook-header {
- font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
- border-bottom: 1px solid rgba(0, 0, 0, 0.1);
- padding: 15px 20px;
- display: flex;
- align-items: center;
- justify-content: space-between;
-}
-
-.storybook-header svg {
- display: inline-block;
- vertical-align: top;
-}
-
-.storybook-header h1 {
- font-weight: 700;
- font-size: 20px;
- line-height: 1;
- margin: 6px 0 6px 10px;
- display: inline-block;
- vertical-align: top;
-}
-
-.storybook-header button + button {
- margin-left: 10px;
-}
-
-.storybook-header .welcome {
- color: #333;
- font-size: 14px;
- margin-right: 10px;
-}
diff --git a/src/view/AktivitetToggleContext.tsx b/src/view/AktivitetToggleContext.tsx
index 4a641bd2..c491884f 100644
--- a/src/view/AktivitetToggleContext.tsx
+++ b/src/view/AktivitetToggleContext.tsx
@@ -1,4 +1,4 @@
-import React, { useContext, useMemo, useState } from 'react';
+import React, { useContext, useEffect, useMemo, useState } from 'react';
interface AktivitetToggleState {
visAktivitet: boolean;
@@ -11,10 +11,19 @@ export const AktivitetToggleContext = React.createContext(
export const useVisAktivitetContext = () => useContext(AktivitetToggleContext);
export const useVisAktivitet = () => useContext(AktivitetToggleContext).visAktivitet;
-export const AktivitetToggleProvider = (props: { children: React.ReactNode }) => {
- const [visAktivitet, setVisAktivitet] = useState(false);
+export const AktivitetToggleProvider = ({
+ children,
+ defaultValue
+}: {
+ children: React.ReactNode;
+ defaultValue: boolean;
+}) => {
+ const [visAktivitet, setVisAktivitet] = useState(defaultValue);
+ useEffect(() => {
+ if (defaultValue === false || defaultValue === true) setVisAktivitet(defaultValue);
+ }, [defaultValue]);
const context = useMemo(() => {
return { visAktivitet, setVisAktivitet };
- }, [visAktivitet]);
- return {props.children};
+ }, [visAktivitet, defaultValue]);
+ return {children};
};
diff --git a/src/view/Provider.tsx b/src/view/Provider.tsx
index 28670989..0a2bea63 100644
--- a/src/view/Provider.tsx
+++ b/src/view/Provider.tsx
@@ -56,10 +56,11 @@ interface Props {
enhet?: string;
erVeileder: boolean;
children: React.ReactNode;
+ visAktivitetDefault?: boolean;
}
export function Provider(props: Props) {
- const { fnr, erVeileder, children } = props;
+ const { fnr, erVeileder, children, visAktivitetDefault } = props;
const veilederNavn = useFetchVeilederNavn(erVeileder);
@@ -130,7 +131,9 @@ export function Provider(props: Props) {
- {children}
+
+ {children}
+
diff --git a/src/view/dialog/Dialog.tsx b/src/view/dialog/Dialog.tsx
index 3d2cd2d3..509838f6 100644
--- a/src/view/dialog/Dialog.tsx
+++ b/src/view/dialog/Dialog.tsx
@@ -17,7 +17,7 @@ import { useSelectedAktivitet, useSelectedDialog } from '../utils/useAktivitetId
import { useEventListener } from '../utils/useEventListner';
import { endreDialogSomVises } from '../ViewState';
import ManagedDialogCheckboxes from './DialogCheckboxes';
-import MeldingInputBox from './henvendelseInput/MeldingInputBox';
+import MeldingInputBox from './meldingInput/MeldingInputBox';
import HistoriskInfo from './HistoriskInfo';
export function Dialog() {
diff --git a/src/view/dialog/henvendelseInput/MeldingInputBox.tsx b/src/view/dialog/henvendelseInput/MeldingInputBox.tsx
deleted file mode 100644
index 0ce18b6f..00000000
--- a/src/view/dialog/henvendelseInput/MeldingInputBox.tsx
+++ /dev/null
@@ -1,210 +0,0 @@
-import { zodResolver } from '@hookform/resolvers/zod';
-import { Alert, Button, ErrorMessage } from '@navikt/ds-react';
-import classNames from 'classnames';
-import React, { ChangeEvent, useEffect, useLayoutEffect, useRef, useState } from 'react';
-import { FieldError, useForm } from 'react-hook-form';
-import { z } from 'zod';
-
-import loggEvent from '../../../felleskomponenter/logging';
-import { DialogData } from '../../../utils/Typer';
-import { UpdateTypes, dispatchUpdate } from '../../../utils/UpdateEvent';
-import { useVisAktivitet } from '../../AktivitetToggleContext';
-import { useDialogContext } from '../../DialogProvider';
-import { useCompactMode } from '../../../featureToggle/FeatureToggleProvider';
-import { useKladdContext } from '../../KladdProvider';
-import { useViewContext } from '../../Provider';
-import { HandlingsType, sendtNyMelding } from '../../ViewState';
-import useMeldingStartTekst from '../UseMeldingStartTekst';
-import TextareaAutosize from '@navikt/ds-react/esm/util/TextareaAutoSize';
-import { useSelectedAktivitet } from '../../utils/useAktivitetId';
-
-const maxMeldingsLengde = 5000;
-
-const schema = z.object({
- melding: z
- .string()
- .min(1, 'Du må fylle ut en melding')
- .max(maxMeldingsLengde, `Meldingen kan ikke være mer enn ${maxMeldingsLengde}`)
-});
-
-export type MeldingFormValues = z.infer;
-
-interface Props {
- dialog: DialogData;
- kanSendeHenveldelse: boolean;
- erBruker: boolean;
-}
-
-const MeldingInputBox = (props: Props) => {
- const { hentDialoger, nyMelding } = useDialogContext();
- const [noeFeilet, setNoeFeilet] = useState(false);
- const startTekst = useMeldingStartTekst();
- const compactMode = useCompactMode();
- const visAktivitet = useVisAktivitet();
- const aktivitet = useSelectedAktivitet();
-
- const { kladder, oppdaterKladd, slettKladd } = useKladdContext();
- const kladd = kladder.find((k) => k.aktivitetId === props.dialog.aktivitetId && k.dialogId === props.dialog.id);
-
- const { viewState, setViewState } = useViewContext();
-
- const valgtDialog = props.dialog;
- const kanSendeHenveldelse = props.kanSendeHenveldelse;
- const timer = useRef();
- const callback = useRef<() => any>();
-
- useLayoutEffect(() => {
- const match = window.matchMedia ? window.matchMedia(`(min-width: 900px)`).matches : false;
- const autoFocus = match && viewState.sistHandlingsType !== HandlingsType.nyDialog;
-
- if (autoFocus) {
- const el = document.getElementsByClassName('autosizing-textarea')[0] as HTMLInputElement;
- if (el) {
- el.focus();
- if (kladd?.tekst) {
- el.selectionStart = el.selectionEnd = el.value.length;
- }
- }
- }
- }, []);
-
- useEffect(() => {
- return () => {
- timer.current && clearInterval(timer.current);
- timer.current && callback.current && callback.current();
- };
- }, []);
-
- const defaultValues: MeldingFormValues = {
- melding: !!kladd?.tekst ? kladd.tekst : startTekst
- };
-
- const onChange = (e: ChangeEvent) => {
- const value = e.target.value;
- timer.current && clearInterval(timer.current);
- callback.current = () => {
- timer.current = undefined;
- oppdaterKladd(props.dialog.id, props.dialog.aktivitetId, null, value);
- };
- timer.current = window.setTimeout(callback.current, 500);
- };
-
- const {
- register,
- handleSubmit,
- reset,
- formState: { errors, isSubmitting },
- getValues
- } = useForm({
- defaultValues,
- resolver: zodResolver(schema)
- });
-
- const onSubmit = (data: MeldingFormValues) => {
- setNoeFeilet(false);
- const { melding } = data;
-
- timer.current && clearInterval(timer.current);
- timer.current = undefined;
-
- loggEvent('arbeidsrettet-dialog.ny.henvendelse', { paaAktivitet: !!valgtDialog.aktivitetId });
- return nyMelding(melding, valgtDialog)
- .then((dialog) => {
- slettKladd(valgtDialog.id, valgtDialog.aktivitetId);
- return dialog;
- })
- .then(hentDialoger)
- .then(() => {
- setNoeFeilet(false);
- reset({ melding: startTekst });
- setViewState(sendtNyMelding(viewState));
- dispatchUpdate(UpdateTypes.Dialog);
- })
- .catch((e) => {
- console.log({ e });
- setNoeFeilet(true);
- });
- };
-
- console.log(errors);
-
- return (
-
- );
-};
-
-const betterErrorMessage = (error: FieldError, melding: string): FieldError => {
- const tooBig = error.type === 'too_big';
- return {
- ...error,
- message: tooBig
- ? `Meldingen kan ikke være mer enn ${maxMeldingsLengde} tegn, men er ${melding.length}`
- : error.message
- };
-};
-
-export default MeldingInputBox;
diff --git a/src/view/dialog/meldingInput/MeldingBottomInput.tsx b/src/view/dialog/meldingInput/MeldingBottomInput.tsx
new file mode 100644
index 00000000..26923d29
--- /dev/null
+++ b/src/view/dialog/meldingInput/MeldingBottomInput.tsx
@@ -0,0 +1,70 @@
+import classNames from 'classnames';
+import TextareaAutosize from '@navikt/ds-react/esm/util/TextareaAutoSize';
+import { Alert, Button, ErrorMessage } from '@navikt/ds-react';
+import React, { ChangeEvent } from 'react';
+import { useCompactMode } from '../../../featureToggle/FeatureToggleProvider';
+import { useFormContext } from 'react-hook-form';
+import { betterErrorMessage } from './inputUtils';
+import { MeldingFormValues } from './MeldingInputBox';
+import { PaperplaneIcon } from '@navikt/aksel-icons';
+import { Breakpoint, useBreakpoint } from '../../utils/useBreakpoint';
+
+interface Props {
+ onSubmit: (e?: React.BaseSyntheticEvent) => Promise;
+ onChange: (e: ChangeEvent) => void;
+ noeFeilet: boolean;
+}
+export const MeldingBottomInput = ({ onSubmit, onChange, noeFeilet }: Props) => {
+ const {
+ register,
+ getValues,
+ formState: { errors, isSubmitting }
+ } = useFormContext();
+ const compactMode = useCompactMode();
+ const breakpoint = useBreakpoint();
+
+ return (
+
+ );
+};
diff --git a/src/view/dialog/henvendelseInput/HenvendelseInputBox.module.less b/src/view/dialog/meldingInput/MeldingInputBox.module.less
similarity index 100%
rename from src/view/dialog/henvendelseInput/HenvendelseInputBox.module.less
rename to src/view/dialog/meldingInput/MeldingInputBox.module.less
diff --git a/src/view/dialog/meldingInput/MeldingInputBox.tsx b/src/view/dialog/meldingInput/MeldingInputBox.tsx
new file mode 100644
index 00000000..fc3767ff
--- /dev/null
+++ b/src/view/dialog/meldingInput/MeldingInputBox.tsx
@@ -0,0 +1,143 @@
+import { zodResolver } from '@hookform/resolvers/zod';
+import React, { ChangeEvent, useEffect, useLayoutEffect, useRef, useState } from 'react';
+import { FormProvider, useForm } from 'react-hook-form';
+import { z } from 'zod';
+
+import loggEvent from '../../../felleskomponenter/logging';
+import { DialogData } from '../../../utils/Typer';
+import { dispatchUpdate, UpdateTypes } from '../../../utils/UpdateEvent';
+import { useDialogContext } from '../../DialogProvider';
+import { useCompactMode } from '../../../featureToggle/FeatureToggleProvider';
+import { useKladdContext } from '../../KladdProvider';
+import { useViewContext } from '../../Provider';
+import { HandlingsType, sendtNyMelding } from '../../ViewState';
+import useMeldingStartTekst from '../UseMeldingStartTekst';
+import { Breakpoint, useBreakpoint } from '../../utils/useBreakpoint';
+import { MeldingBottomInput } from './MeldingBottomInput';
+import { MeldingSideInput } from './MeldingSideInput';
+import { maxMeldingsLengde } from './inputUtils';
+import { useVisAktivitet } from '../../AktivitetToggleContext';
+
+const schema = z.object({
+ melding: z
+ .string()
+ .min(1, 'Du må fylle ut en melding')
+ .max(maxMeldingsLengde, `Meldingen kan ikke være mer enn ${maxMeldingsLengde}`)
+});
+
+export type MeldingFormValues = z.infer;
+
+interface Props {
+ dialog: DialogData;
+ kanSendeHenveldelse: boolean;
+}
+
+const MeldingInputBox = (props: Props) => {
+ const { hentDialoger, nyMelding } = useDialogContext();
+ const [noeFeilet, setNoeFeilet] = useState(false);
+ const startTekst = useMeldingStartTekst();
+ const visAktivitet = useVisAktivitet();
+ const compactMode = useCompactMode();
+
+ const { kladder, oppdaterKladd, slettKladd } = useKladdContext();
+ const kladd = kladder.find((k) => k.aktivitetId === props.dialog.aktivitetId && k.dialogId === props.dialog.id);
+
+ const { viewState, setViewState } = useViewContext();
+
+ const valgtDialog = props.dialog;
+ const kanSendeHenveldelse = props.kanSendeHenveldelse;
+ const timer = useRef();
+ const callback = useRef<() => any>();
+
+ useLayoutEffect(() => {
+ const match = window.matchMedia ? window.matchMedia(`(min-width: 900px)`).matches : false;
+ const autoFocus = match && viewState.sistHandlingsType !== HandlingsType.nyDialog;
+
+ if (autoFocus) {
+ const el = document.getElementsByClassName('autosizing-textarea')[0] as HTMLInputElement;
+ if (el) {
+ el.focus();
+ if (kladd?.tekst) {
+ el.selectionStart = el.selectionEnd = el.value.length;
+ }
+ }
+ }
+ }, []);
+
+ useEffect(() => {
+ return () => {
+ timer.current && clearInterval(timer.current);
+ timer.current && callback.current && callback.current();
+ };
+ }, []);
+
+ const defaultValues: MeldingFormValues = {
+ melding: !!kladd?.tekst ? kladd.tekst : startTekst
+ };
+
+ const onChange = (e: ChangeEvent) => {
+ const value = e.target.value;
+ timer.current && clearInterval(timer.current);
+ callback.current = () => {
+ timer.current = undefined;
+ oppdaterKladd(props.dialog.id, props.dialog.aktivitetId, null, value);
+ };
+ timer.current = window.setTimeout(callback.current, 500);
+ };
+
+ const formHandlers = useForm({
+ defaultValues,
+ resolver: zodResolver(schema)
+ });
+ const { handleSubmit, reset } = formHandlers;
+
+ const onSubmit = (data: MeldingFormValues) => {
+ setNoeFeilet(false);
+ const { melding } = data;
+
+ timer.current && clearInterval(timer.current);
+ timer.current = undefined;
+
+ loggEvent('arbeidsrettet-dialog.ny.henvendelse', { paaAktivitet: !!valgtDialog.aktivitetId });
+ return nyMelding(melding, valgtDialog)
+ .then((dialog) => {
+ slettKladd(valgtDialog.id, valgtDialog.aktivitetId);
+ return dialog;
+ })
+ .then(hentDialoger)
+ .then(() => {
+ setNoeFeilet(false);
+ reset({ melding: startTekst });
+ setViewState(sendtNyMelding(viewState));
+ dispatchUpdate(UpdateTypes.Dialog);
+ })
+ .catch((e) => {
+ console.log({ e });
+ setNoeFeilet(true);
+ });
+ };
+
+ const breakpoint = useBreakpoint();
+ const args = { noeFeilet, onSubmit: handleSubmit((data) => onSubmit(data)), onChange };
+
+ if (!kanSendeHenveldelse) return null;
+ const Input = () => {
+ if (!compactMode) {
+ return ;
+ } else if (visAktivitet && [Breakpoint.md, Breakpoint.lg, Breakpoint.xl].includes(breakpoint)) {
+ return ;
+ } else if ([Breakpoint.initial, Breakpoint.sm, Breakpoint.md].includes(breakpoint)) {
+ return ;
+ } else {
+ return ;
+ }
+ };
+
+ return (
+
+
+
+ );
+};
+
+export default MeldingInputBox;
diff --git a/src/view/dialog/meldingInput/MeldingSideInput.tsx b/src/view/dialog/meldingInput/MeldingSideInput.tsx
new file mode 100644
index 00000000..d6eeab6c
--- /dev/null
+++ b/src/view/dialog/meldingInput/MeldingSideInput.tsx
@@ -0,0 +1,65 @@
+import classNames from 'classnames';
+import TextareaAutosize from '@navikt/ds-react/esm/util/TextareaAutoSize';
+import { Alert, Button, ErrorMessage } from '@navikt/ds-react';
+import React, { ChangeEvent } from 'react';
+import { useCompactMode } from '../../../featureToggle/FeatureToggleProvider';
+import { useFormContext } from 'react-hook-form';
+import { betterErrorMessage } from './inputUtils';
+import { MeldingFormValues } from './MeldingInputBox';
+interface Props {
+ onSubmit: (e?: React.BaseSyntheticEvent) => Promise;
+ onChange: (e: ChangeEvent) => void;
+ noeFeilet: boolean;
+}
+export const MeldingSideInput = ({ onSubmit, onChange, noeFeilet }: Props) => {
+ const {
+ register,
+ getValues,
+ formState: { errors, isSubmitting }
+ } = useFormContext();
+ const compactMode = useCompactMode();
+
+ return (
+
+ );
+};
diff --git a/src/view/dialog/meldingInput/inputUtils.ts b/src/view/dialog/meldingInput/inputUtils.ts
new file mode 100644
index 00000000..49354e8e
--- /dev/null
+++ b/src/view/dialog/meldingInput/inputUtils.ts
@@ -0,0 +1,12 @@
+import { FieldError } from 'react-hook-form';
+
+export const maxMeldingsLengde = 5000;
+export const betterErrorMessage = (error: FieldError, melding: string): FieldError => {
+ const tooBig = error.type === 'too_big';
+ return {
+ ...error,
+ message: tooBig
+ ? `Meldingen kan ikke være mer enn ${maxMeldingsLengde} tegn, men er ${melding.length}`
+ : error.message
+ };
+};
diff --git a/src/view/dialogliste/DialogPreview.tsx b/src/view/dialogliste/DialogPreview.tsx
index 0992ddd1..4d1042f6 100644
--- a/src/view/dialogliste/DialogPreview.tsx
+++ b/src/view/dialogliste/DialogPreview.tsx
@@ -151,7 +151,7 @@ export function DialogPreviewListe({ dialoger, valgDialog }: ListeProps) {
[styles.fadeIn]: index === 0 && skalFadeIn
})}
>
-
+
))}
diff --git a/src/view/melding/Meldinger.tsx b/src/view/melding/Meldinger.tsx
index 5de5ed4b..9ed4aea2 100644
--- a/src/view/melding/Meldinger.tsx
+++ b/src/view/melding/Meldinger.tsx
@@ -53,11 +53,11 @@ export function Meldinger(props: Props) {