diff --git a/package.json b/package.json index 86e87909..6b3b641b 100644 --- a/package.json +++ b/package.json @@ -10,8 +10,6 @@ "dev": "concurrently \"bun run --cwd packages/client dev\" \"bun run --cwd packages/server dev\"", "build": "bun run --cwd packages/client build && bun run --cwd packages/server build && bun run copy-assets", "copy-assets": "cp -r packages/client/dist/assets packages/server/public/ && cp -r packages/client/dist/~partytown packages/server/public/", - "serve": "bun run --cwd packages/server serve", - "serve-dev": "concurrently \"bun run --cwd packages/server serve-local\" \"bun run --cwd packages/client build:watch\"", "prepare": "husky install", "benchmarking": "./benchmarking/run", "partytown": "partytown copylib packages/server/public/~partytown", diff --git a/packages/client/src/main.css b/packages/client/src/main.css index db163e11..526e33f9 100644 --- a/packages/client/src/main.css +++ b/packages/client/src/main.css @@ -1,110 +1,4 @@ -:root, -:host { - --a-spacing-16: 4rem; - --a-spacing-12: 3rem; - --a-spacing-10: 2.5rem; - --a-spacing-9: 2.25rem; - --a-spacing-8: 2rem; - --a-spacing-6: 1.5rem; - --a-spacing-5: 1.25rem; - --a-spacing-4: 1rem; - --a-spacing-3: 0.75rem; - --a-spacing-2: 0.5rem; - --a-spacing-1: 0.25rem; - --a-shadow-xlarge: 0px 2px 5px 0px rgba(0, 0, 0, 0.15), - 0px 10px 24px 0px rgba(0, 0, 0, 0.18), - 0px 0px 1px 0px rgba(0, 0, 0, 0.08); - --a-shadow-small: 0px 3px 8px 0px rgba(0, 0, 0, 0.1), - 0px 1px 3px 0px rgba(0, 0, 0, 0.1), 0px 0px 1px 0px rgba(0, 0, 0, 0.18); - --a-shadow-xsmall: 0px 1px 3px 0px rgba(0, 0, 0, 0.15), - 0px 0px 1px 0px rgba(0, 0, 0, 0.2); - --a-lightblue-800: rgba(35, 107, 125, 1); - --a-lightblue-700: rgba(54, 141, 168, 1); - --a-lightblue-100: rgba(216, 249, 255, 1); - --a-deepblue-800: rgba(0, 52, 83, 1); - --a-deepblue-700: rgba(0, 67, 103, 1); - --a-deepblue-600: rgba(0, 80, 119, 1); - --a-deepblue-500: rgba(0, 91, 130, 1); - --a-deepblue-300: rgba(102, 163, 196, 1); - --a-deepblue-200: rgba(153, 196, 221, 1); - --a-red-500: rgba(195, 0, 0, 1); - --a-red-100: rgba(255, 194, 194, 1); - --a-blue-800: rgba(0, 52, 125, 1); - --a-blue-600: rgba(0, 86, 180, 1); - --a-blue-500: rgba(0, 103, 197, 1); - --a-blue-200: rgba(153, 195, 255, 1); - --a-blue-100: rgba(204, 225, 255, 1); - --a-blue-50: rgba(230, 240, 255, 1); - --a-grayalpha-700: rgba(0, 0, 0, 0.65); - --a-grayalpha-500: rgba(0, 0, 0, 0.44); - --a-grayalpha-300: rgba(0, 0, 0, 0.19); - --a-grayalpha-200: rgba(0, 0, 0, 0.1); - --a-gray-900: rgba(38, 38, 38, 1); - --a-gray-600: rgba(112, 112, 112, 1); - --a-gray-300: rgba(207, 207, 207, 1); - --a-gray-200: rgba(229, 229, 229, 1); - --a-gray-100: rgba(241, 241, 241, 1); - --a-gray-50: rgba(247, 247, 247, 1); - --a-white: rgba(255, 255, 255, 1); - --a-transparent: rgba(255, 255, 255, 0); - --a-border-radius-xlarge: 12px; - --a-border-radius-large: 8px; - --a-border-radius-medium: 4px; - --a-font-weight-regular: 400; - --a-font-weight-bold: 600; - --a-font-size-small: 0.875rem; - --a-font-size-medium: 1rem; - --a-font-size-large: 1.125rem; - --a-font-size-xlarge: 1.25rem; - --a-font-size-heading-small: 1.25rem; - --a-font-size-heading-medium: 1.5rem; - --a-font-size-heading-large: 1.75rem; - --a-font-size-heading-xlarge: 2rem; - --a-font-line-height-medium: 1.25rem; - --a-font-line-height-large: 1.5rem; - --a-font-line-height-xlarge: 1.75rem; - --a-font-line-height-heading-small: 1.75rem; - --a-font-line-height-heading-medium: 2rem; - --a-font-family: "Source Sans Pro", Arial, sans-serif; - --a-icon-default: var(--a-gray-900); - --a-icon-info: var(--a-lightblue-800); - --a-icon-danger: var(--a-red-500); - --a-nav-red: var(--a-red-500); - --a-icon-subtle: var(--a-grayalpha-700); - --a-surface-alt-3-strong: var(--a-deepblue-800); - --a-surface-info-subtle: var(--a-lightblue-100); - --a-surface-danger-subtle: var(--a-red-100); - --a-surface-action: var(--a-blue-500); - --a-surface-action-active: var(--a-deepblue-500); - --a-surface-action-hover: var(--a-blue-600); - --a-surface-action-subtle: var(--a-blue-50); - --a-surface-action-subtle-hover: var(--a-blue-100); - --a-surface-subtle: var(--a-gray-50); - --a-surface-active: var(--a-grayalpha-200); - --a-surface-default: var(--a-white); - --a-bg-subtle: var(--a-gray-100); - --a-bg-default: var(--a-white); - --a-text-on-action: var(--a-white); - --a-text-on-inverted: var(--a-white); - --a-text-action: var(--a-blue-500); - --a-text-action-on-action-subtle: var(--a-blue-600); - --a-text-danger: var(--a-red-500); - --a-text-subtle: var(--a-grayalpha-700); - --a-text-default: var(--a-gray-900); - --a-border-focus: var(--a-blue-800); - --a-border-focus-on-inverted: var(--a-blue-200); - --a-border-info: var(--a-lightblue-700); - --a-border-danger: var(--a-red-500); - --a-border-action: var(--a-blue-500); - --a-border-subtle: var(--a-grayalpha-200); - --a-border-divider: var(--a-grayalpha-300); - --a-border-default: var(--a-grayalpha-500); - --a-shadow-focus: 0 0 0 3px var(--a-border-focus); - - --header-height-mobil: 72px; - --header-height-desktop: 80px; - --desktop-min-width: 768px; -} +@import "@navikt/ds-tokens"; /* Vurdere å kun ta med i dev -> Start */ @font-face { @@ -171,7 +65,11 @@ body { line-height: 1.333; font-size: 1.125rem; color: var(--a-text-default); + + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; } + :where(#header-withmenu, #footer-withmenu) { font-family: var(--a-font-family, "Source Sans Pro", Arial, sans-serif); } diff --git a/packages/client/src/styles/read-more.module.css b/packages/client/src/styles/read-more.module.css index af3204a9..75c275ba 100644 --- a/packages/client/src/styles/read-more.module.css +++ b/packages/client/src/styles/read-more.module.css @@ -1,33 +1,48 @@ .summary { - color: var(--a-blue-500); - list-style-type: none; - list-style-image: url("/public/ikoner/read-more.svg"); - height: 32px; + display: flex; + align-items: flex-start; + gap: var(--a-spacing-05); + list-style: none; + border-radius: var(--a-border-radius-small); + color: var(--a-text-action); + cursor: pointer; + padding: var(--a-spacing-1) var(--a-spacing-2) var(--a-spacing-1) + var(--a-spacing-05); + width: fit-content; } -.summary:global(::marker) { - /* display: grid; */ - /* justify-content: center; */ - /* align-items: center; */ +.summary:hover { + background-color: var(--a-surface-hover); + color: var(--a-text-action-hover); } -.summary:hover { - list-style-image: none; - background-color: var(--a-bg-subtle); - display: inline-block; - cursor: pointer; +.summary:active { + background-color: var(--a-surface-active); } -.answer { - border-left: 2px solid var(--a-border-divider); - padding-left: var(--a-spacing-3); - margin-left: var(--a-spacing-2); - line-height: 1.5; +.icon { + font-size: 1.5rem; } -.details[open] .summary { - list-style-image: url("/public/ikoner/read-less.svg"); +.details[open] .icon { + transform: rotate(180deg); } -.details { +.summary:hover .icon { + position: relative; + top: 1px; +} + +.details[open] .summary:hover .icon { + top: -1px; +} + +.answer { + border-left: 2px solid var(--a-border-divider); + color: var(--a-text-default); + margin-left: 0.8125rem; + margin-top: var(--a-spacing-1); + padding-left: 0.8125rem; + font-weight: var(--a-font-weight-regular); + line-height: var(--a-font-line-height-xlarge); } diff --git a/packages/server/public/ikoner/advarsel-sirkel-fyll.svg b/packages/server/public/ikoner/advarsel-sirkel-fyll.svg deleted file mode 100644 index c5c28ad6..00000000 --- a/packages/server/public/ikoner/advarsel-sirkel-fyll.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/packages/server/public/ikoner/circle.svg b/packages/server/public/ikoner/circle.svg deleted file mode 100644 index 6e68cfd3..00000000 --- a/packages/server/public/ikoner/circle.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - diff --git a/packages/server/public/ikoner/del-skjerm/DelSkjerm.svg b/packages/server/public/ikoner/del-skjerm/DelSkjerm.svg deleted file mode 100644 index 462a02e5..00000000 --- a/packages/server/public/ikoner/del-skjerm/DelSkjerm.svg +++ /dev/null @@ -1,6 +0,0 @@ - diff --git a/packages/server/public/ikoner/del-skjerm/Veileder.svg b/packages/server/public/ikoner/del-skjerm/Veileder.svg deleted file mode 100644 index b520919f..00000000 --- a/packages/server/public/ikoner/del-skjerm/Veileder.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/packages/server/public/ikoner/favicon/android-chrome-192x192.png b/packages/server/public/ikoner/favicon/android-chrome-192x192.png deleted file mode 100644 index 006278f2..00000000 Binary files a/packages/server/public/ikoner/favicon/android-chrome-192x192.png and /dev/null differ diff --git a/packages/server/public/ikoner/favicon/android-chrome-512x512.png b/packages/server/public/ikoner/favicon/android-chrome-512x512.png deleted file mode 100644 index ee604a84..00000000 Binary files a/packages/server/public/ikoner/favicon/android-chrome-512x512.png and /dev/null differ diff --git a/packages/server/public/ikoner/favicon/apple-touch-icon.png b/packages/server/public/ikoner/favicon/apple-touch-icon.png deleted file mode 100644 index 13b0a1c0..00000000 Binary files a/packages/server/public/ikoner/favicon/apple-touch-icon.png and /dev/null differ diff --git a/packages/server/public/ikoner/favicon/favicon.ico b/packages/server/public/ikoner/favicon/favicon.ico deleted file mode 100644 index a5ab525c..00000000 Binary files a/packages/server/public/ikoner/favicon/favicon.ico and /dev/null differ diff --git a/packages/server/public/ikoner/favicon/favicon.svg b/packages/server/public/ikoner/favicon/favicon.svg deleted file mode 100644 index dd2d8dd1..00000000 --- a/packages/server/public/ikoner/favicon/favicon.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - \ No newline at end of file diff --git a/packages/server/public/ikoner/favicon/site.webmanifest b/packages/server/public/ikoner/favicon/site.webmanifest deleted file mode 100644 index 760e7cc6..00000000 --- a/packages/server/public/ikoner/favicon/site.webmanifest +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "nav.no", - "short_name": "nav.no", - "icons": [ - { - "src": "/android-chrome-192x192.png", - "sizes": "192x192", - "type": "image/png" - }, - { - "src": "/android-chrome-512x512.png", - "sizes": "512x512", - "type": "image/png" - } - ], - "theme_color": "#ffffff", - "background_color": "#ffffff", - "display": "standalone" -} diff --git a/packages/server/public/ikoner/globe.svg b/packages/server/public/ikoner/globe.svg deleted file mode 100644 index 37a19c69..00000000 --- a/packages/server/public/ikoner/globe.svg +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - diff --git a/packages/server/public/ikoner/home.svg b/packages/server/public/ikoner/home.svg deleted file mode 100644 index 9415b85b..00000000 --- a/packages/server/public/ikoner/home.svg +++ /dev/null @@ -1,13 +0,0 @@ - diff --git a/packages/server/public/ikoner/meny/Briefcase_icon_nav.svg b/packages/server/public/ikoner/meny/Briefcase_icon_nav.svg deleted file mode 100644 index 37a583da..00000000 --- a/packages/server/public/ikoner/meny/Briefcase_icon_nav.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/packages/server/public/ikoner/meny/Lock.svg b/packages/server/public/ikoner/meny/Lock.svg deleted file mode 100644 index bb0df77e..00000000 --- a/packages/server/public/ikoner/meny/Lock.svg +++ /dev/null @@ -1,6 +0,0 @@ - \ No newline at end of file diff --git a/packages/server/public/ikoner/meny/LoggutIkonMobil.svg b/packages/server/public/ikoner/meny/LoggutIkonMobil.svg deleted file mode 100644 index 1f5a27b9..00000000 --- a/packages/server/public/ikoner/meny/LoggutIkonMobil.svg +++ /dev/null @@ -1,6 +0,0 @@ - \ No newline at end of file diff --git a/packages/server/public/ikoner/meny/NavLogoRod.svg b/packages/server/public/ikoner/meny/NavLogoRod.svg deleted file mode 100644 index f2dba6ac..00000000 --- a/packages/server/public/ikoner/meny/NavLogoRod.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/packages/server/public/ikoner/meny/PilOppHvit.svg b/packages/server/public/ikoner/meny/PilOppHvit.svg deleted file mode 100644 index 8a8660b3..00000000 --- a/packages/server/public/ikoner/meny/PilOppHvit.svg +++ /dev/null @@ -1,6 +0,0 @@ - \ No newline at end of file diff --git a/packages/server/public/ikoner/meny/menu-close-white.svg b/packages/server/public/ikoner/meny/menu-close-white.svg deleted file mode 100644 index 09561626..00000000 --- a/packages/server/public/ikoner/meny/menu-close-white.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/server/public/ikoner/meny/menu-close.svg b/packages/server/public/ikoner/meny/menu-close.svg deleted file mode 100644 index 09f07a4f..00000000 --- a/packages/server/public/ikoner/meny/menu-close.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/server/public/ikoner/meny/nav-logo-black.svg b/packages/server/public/ikoner/meny/nav-logo-black.svg deleted file mode 100644 index ae6f5220..00000000 --- a/packages/server/public/ikoner/meny/nav-logo-black.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/packages/server/public/ikoner/meny/nav-logo-red.svg b/packages/server/public/ikoner/meny/nav-logo-red.svg deleted file mode 100644 index 68a14b0c..00000000 --- a/packages/server/public/ikoner/meny/nav-logo-red.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/packages/server/public/ikoner/meny/nav-logo-white.svg b/packages/server/public/ikoner/meny/nav-logo-white.svg deleted file mode 100644 index 59b3e05c..00000000 --- a/packages/server/public/ikoner/meny/nav-logo-white.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/packages/server/public/ikoner/read-less.svg b/packages/server/public/ikoner/read-less.svg deleted file mode 100644 index 069c8f9a..00000000 --- a/packages/server/public/ikoner/read-less.svg +++ /dev/null @@ -1,6 +0,0 @@ - - diff --git a/packages/server/public/ikoner/read-more.svg b/packages/server/public/ikoner/read-more.svg deleted file mode 100644 index 0b302cf7..00000000 --- a/packages/server/public/ikoner/read-more.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/server/public/ikoner/varsler/Clock.svg b/packages/server/public/ikoner/varsler/Clock.svg deleted file mode 100644 index c8101223..00000000 --- a/packages/server/public/ikoner/varsler/Clock.svg +++ /dev/null @@ -1,7 +0,0 @@ - \ No newline at end of file diff --git a/packages/server/public/ikoner/varsler/Close.svg b/packages/server/public/ikoner/varsler/Close.svg deleted file mode 100644 index e5960d44..00000000 --- a/packages/server/public/ikoner/varsler/Close.svg +++ /dev/null @@ -1,6 +0,0 @@ - \ No newline at end of file diff --git a/packages/server/public/ikoner/varsler/CollapseUp.svg b/packages/server/public/ikoner/varsler/CollapseUp.svg deleted file mode 100644 index 87d603a9..00000000 --- a/packages/server/public/ikoner/varsler/CollapseUp.svg +++ /dev/null @@ -1,8 +0,0 @@ - \ No newline at end of file diff --git a/packages/server/public/ikoner/varsler/Veiledervarsel.svg b/packages/server/public/ikoner/varsler/Veiledervarsel.svg deleted file mode 100644 index c0bb2be9..00000000 --- a/packages/server/public/ikoner/varsler/Veiledervarsel.svg +++ /dev/null @@ -1,32 +0,0 @@ - \ No newline at end of file diff --git a/packages/server/public/ikoner/varsler/alarm.svg b/packages/server/public/ikoner/varsler/alarm.svg deleted file mode 100644 index 582f6921..00000000 --- a/packages/server/public/ikoner/varsler/alarm.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - alarm - Created with Sketch. - - - - - - - - - - - \ No newline at end of file diff --git a/packages/server/public/ikoner/varsler/beskjedIkon.svg b/packages/server/public/ikoner/varsler/beskjedIkon.svg deleted file mode 100644 index 7e9448e6..00000000 --- a/packages/server/public/ikoner/varsler/beskjedIkon.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - \ No newline at end of file diff --git a/packages/server/public/ikoner/varsler/kattIngenNotifications.svg b/packages/server/public/ikoner/varsler/kattIngenNotifications.svg deleted file mode 100644 index bfbb1f3a..00000000 --- a/packages/server/public/ikoner/varsler/kattIngenNotifications.svg +++ /dev/null @@ -1,21 +0,0 @@ - diff --git a/packages/server/public/ikoner/varsler/lukkVarslerIkon.svg b/packages/server/public/ikoner/varsler/lukkVarslerIkon.svg deleted file mode 100644 index 1a1710ed..00000000 --- a/packages/server/public/ikoner/varsler/lukkVarslerIkon.svg +++ /dev/null @@ -1,21 +0,0 @@ - \ No newline at end of file diff --git a/packages/server/public/ikoner/varsler/oppgaveIkon.svg b/packages/server/public/ikoner/varsler/oppgaveIkon.svg deleted file mode 100644 index 453594a7..00000000 --- a/packages/server/public/ikoner/varsler/oppgaveIkon.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/packages/server/public/kattIngenVarsler.svg b/packages/server/public/kattIngenVarsler.svg deleted file mode 100644 index d3f3b156..00000000 --- a/packages/server/public/kattIngenVarsler.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/server/src/notifications.test.ts b/packages/server/src/notifications.test.ts index 7d8ca8ab..216cf377 100644 --- a/packages/server/src/notifications.test.ts +++ b/packages/server/src/notifications.test.ts @@ -10,23 +10,7 @@ import { HttpResponse, http } from "msw"; import { SetupServerApi, setupServer } from "msw/node"; import { env } from "./env/server"; import { Varsler, getNotifications } from "./notifications"; - -type AnyOkUnion = { - ok: boolean; - [key: string]: unknown; -}; - -export function expectOK( - result: T, -): asserts result is Extract { - expect(result.ok).toBe(true); -} - -export function expectNotOK( - result: T, -): asserts result is Extract { - expect(result.ok).toBe(false); -} +import { expectOK } from "./test-expect"; describe("notifications", () => { let server: SetupServerApi; diff --git a/packages/server/src/notifications.ts b/packages/server/src/notifications.ts index 6e7f8779..75469f47 100644 --- a/packages/server/src/notifications.ts +++ b/packages/server/src/notifications.ts @@ -1,5 +1,6 @@ -import { env } from "./env/server"; import { z } from "zod"; +import { env } from "./env/server"; +import { Result } from "./result"; const varselSchema = z.object({ eventId: z.string(), @@ -59,8 +60,6 @@ const varslerToNotifications = (varsler: Varsler): Notification[] => ), ); -type Result = { ok: true; data: Data } | { ok: false; error: Error }; - export const getNotifications = async ({ request, }: { @@ -76,10 +75,7 @@ export const getNotifications = async ({ ); if (!fetchResult.ok) { - return { - ok: false, - error: Error(await fetchResult.text()), - }; + return Result.Error(await fetchResult.text()); } try { @@ -87,22 +83,13 @@ export const getNotifications = async ({ const validationResult = varslerSchema.safeParse(json); if (!validationResult.success) { - return { - ok: false, - error: validationResult.error, - }; + return Result.Error(validationResult.error); } - return { - ok: true, - data: varslerToNotifications(validationResult.data), - }; + return Result.Ok(varslerToNotifications(validationResult.data)); } catch (error) { if (error instanceof Error) { - return { - ok: false, - error, - }; + return Result.Error(error); } throw error; } diff --git a/packages/server/src/request-handler.test.ts b/packages/server/src/request-handler.test.ts index 5bca1209..2e5ab83b 100644 --- a/packages/server/src/request-handler.test.ts +++ b/packages/server/src/request-handler.test.ts @@ -1,9 +1,8 @@ import { describe, expect, test } from "bun:test"; +import ContentService from "./content-service"; import content from "./content-test-data.json"; import requestHandler from "./request-handler"; -import ContentService from "./content-service"; import UnleashService from "./unleash-service"; -import TaConfigService from "./task-analytics-service"; const req = (url: string, rest?: any) => new Request(url, { @@ -24,12 +23,7 @@ const fetch = await requestHandler( }, ]), ), - { - getFilePaths: () => ["./public/yep.svg"], - getFile: () => Bun.file("./yep.svg"), - }, new UnleashService({ mock: true }), - new TaConfigService(), ); test("is alive", async () => { @@ -79,16 +73,3 @@ describe("notifications", () => { expect(response.status).toBe(404); }); }); - -describe("files", () => { - test("hit", async () => { - const response = await fetch(req("http://localhost/public/yep.svg")); - expect(response.status).toBe(200); - expect(response.headers.get("content-type")).toBe("image/svg+xml"); - }); - - test("miss", async () => { - const response = await fetch(req("http://localhost/public/nope.svg")); - expect(response.status).toBe(404); - }); -}); diff --git a/packages/server/src/request-handler.ts b/packages/server/src/request-handler.ts index b24fe048..03aae9a5 100644 --- a/packages/server/src/request-handler.ts +++ b/packages/server/src/request-handler.ts @@ -11,18 +11,13 @@ import { HandlerBuilder, responseBuilder } from "./lib/handler"; import { getMockSession, refreshToken } from "./mockAuth"; import renderIndex, { renderFooter, renderHeader } from "./render-index"; import { search } from "./search"; -import TaConfigService from "./task-analytics-service"; +import { getTaskAnalyticsConfig } from "./task-analytics-config"; import { texts } from "./texts"; import UnleashService from "./unleash-service"; import { validParams } from "./validateParams"; import { MainMenu } from "./views/header/main-menu"; import { SearchHits } from "./views/search-hits"; -type FileSystemService = { - getFile: (path: string) => Blob; - getFilePaths: (dir: string) => string[]; -}; - const rewriter = new HTMLRewriter().on("img", { element: (element) => { const src = element.getAttribute("src"); @@ -35,9 +30,7 @@ const rewriter = new HTMLRewriter().on("img", { const requestHandler = async ( contentService: ContentService, - fileSystemService: FileSystemService, unleashService: UnleashService, - taConfigService: TaConfigService, ) => { const handlersBuilder = new HandlerBuilder() .get("/api/auth", () => { @@ -50,9 +43,13 @@ const requestHandler = async ( .build(); }) .get("/api/ta", () => - taConfigService - .getTaConfig() - .then((config) => responseBuilder().json(config).build()), + getTaskAnalyticsConfig().then((result) => { + if (result.ok) { + return responseBuilder().json(result.data).build(); + } else { + throw result.error; + } + }), ) .get("/api/oauth2/session", () => { return responseBuilder() @@ -194,22 +191,6 @@ const requestHandler = async ( .use(assetsHandlers) .use([cspHandler]); - // Only serve files in local prod or dev mode - if (env.IS_LOCAL_PROD || env.NODE_ENV === "development") { - const filePaths = fileSystemService - .getFilePaths("./public") - .map((file) => file.replace("./", "/")); - - handlersBuilder.use( - filePaths.map((path) => ({ - method: "GET", - path, - handler: ({ url }) => - new Response(fileSystemService.getFile(`.${url.pathname}`)), - })), - ); - } - const handlers = handlersBuilder.build(); return async function fetch(request: Request): Promise { diff --git a/packages/server/src/result.ts b/packages/server/src/result.ts new file mode 100644 index 00000000..fb877200 --- /dev/null +++ b/packages/server/src/result.ts @@ -0,0 +1,14 @@ +export type Result = + | { ok: true; data: Payload } + | { ok: false; error: Error }; + +export const Result = { + Error: (error: Error | string): Result => ({ + ok: false, + error: error instanceof Error ? error : new Error(error), + }), + Ok: (data: Payload): Result => ({ + ok: true, + data, + }), +}; diff --git a/packages/server/src/server.ts b/packages/server/src/server.ts index 5a7d7e2b..3b714114 100644 --- a/packages/server/src/server.ts +++ b/packages/server/src/server.ts @@ -1,22 +1,12 @@ import { HttpResponse, http } from "msw"; import { setupServer } from "msw/node"; -import { readdirSync, statSync } from "node:fs"; import ContentService from "./content-service"; import menu from "./content-test-data.json"; import { fetchMenu, fetchOpsMessages } from "./enonic"; import { env } from "./env/server"; import notificationsMock from "./notifications-mock.json"; import requestHandler from "./request-handler"; -import TaConfigService from "./task-analytics-service"; import UnleashService from "./unleash-service"; -// import { corsSchema } from './cors'; -// corsSchema.parse('https://www.google.com') - -const getFilePaths = (dir: string): string[] => - readdirSync(dir).flatMap((name) => { - const file = dir + "/" + name; - return statSync(file).isDirectory() ? getFilePaths(file) : file; - }); console.log("Starting decorator-next server"); @@ -54,12 +44,7 @@ const server = Bun.serve({ }, ]), ), - { - getFilePaths, - getFile: Bun.file, - }, new UnleashService({}), - new TaConfigService(), ), }); diff --git a/packages/server/src/task-analytics-config.test.ts b/packages/server/src/task-analytics-config.test.ts new file mode 100644 index 00000000..6e423b8b --- /dev/null +++ b/packages/server/src/task-analytics-config.test.ts @@ -0,0 +1,33 @@ +import { describe, expect, test } from "bun:test"; +import { getTaskAnalyticsConfig } from "./task-analytics-config"; +import { expectOK } from "./test-expect"; + +describe("task analytics", () => { + test("returns config", async () => { + const result = await getTaskAnalyticsConfig(); + expectOK(result); + expect(result.data.length).toBe(3); + expect(result.data[2]).toEqual({ + duration: { + end: "2023-02-28", + start: "2023-01-30T08:00", + }, + id: "2357", + urls: [ + { + match: "startsWith", + url: "https://www.dev.nav.no/soknader", + }, + ], + }); + }); + + test("caches config", async () => { + const res1 = await getTaskAnalyticsConfig(); + const res3 = await getTaskAnalyticsConfig(); + + expectOK(res1); + expectOK(res3); + expect(res1.data).toBe(res3.data); + }); +}); diff --git a/packages/server/src/task-analytics-config.ts b/packages/server/src/task-analytics-config.ts new file mode 100644 index 00000000..e341073e --- /dev/null +++ b/packages/server/src/task-analytics-config.ts @@ -0,0 +1,57 @@ +import { contextSchema, languageSchema } from "decorator-shared/params"; +import { z } from "zod"; +import { Result } from "./result"; +import { ResponseCache } from "decorator-shared/cache"; + +const configSchema = z.array( + z.object({ + id: z.string(), + selection: z.optional(z.number()), + duration: z.optional( + z.object({ + start: z.optional(z.string()), + end: z.optional(z.string()), + }), + ), + urls: z.optional( + z.array( + z.object({ + url: z.string(), + match: z.enum(["exact", "startsWith"]), + exclude: z.optional(z.boolean()), + }), + ), + ), + audience: z.optional(z.array(contextSchema)), + language: z.optional(z.array(languageSchema)), + }), +); + +type TaskAnalyticsSurveyConfig = z.infer; + +const TEN_SECONDS_MS = 10 * 1000; + +const cache = new ResponseCache({ + ttl: TEN_SECONDS_MS, +}); + +export const getTaskAnalyticsConfig = async (): Promise< + Result +> => + cache + .get("task-analytics-config", async () => { + const json = await Bun.file( + `${process.cwd()}/config/ta-config.json`, + ).json(); + + const result = configSchema.safeParse(json); + + if (!result.success) { + throw result.error; + } + + return result.data; + }) + .then((config) => + config ? Result.Ok(config) : Result.Error("Failed to fetch config"), + ); diff --git a/packages/server/src/task-analytics-service.ts b/packages/server/src/task-analytics-service.ts deleted file mode 100644 index b4db8ab8..00000000 --- a/packages/server/src/task-analytics-service.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { TaskAnalyticsSurveyConfig } from "decorator-shared/types"; -import { z } from "zod"; -import { contextSchema, languageSchema } from "decorator-shared/params"; - -// To mock this locally, create the file /config/ta-config.json on the server package root -const filePath = `${process.cwd()}/config/ta-config.json`; - -const configSchema = z.object({ - id: z.string(), - selection: z.optional(z.number()), - duration: z.optional( - z.object({ - start: z.optional(z.string()), - end: z.optional(z.string()), - }), - ), - urls: z.optional( - z.array( - z.object({ - url: z.string(), - match: z.enum(["exact", "startsWith"]), - exclude: z.optional(z.boolean()), - }), - ), - ), - audience: z.optional(z.array(contextSchema)), - language: z.optional(z.array(languageSchema)), -}); - -type ConfigSchema = z.infer; - -type Cache = { - config: TaskAnalyticsSurveyConfig[]; - expires: number; -}; - -const CACHE_TTL_MS = 10000; - -export default class TaConfigService { - private readonly cache: Cache = { - config: [], - expires: 0, - }; - - async getTaConfig(): Promise { - if (Date.now() < this.cache.expires) { - return this.cache.config; - } - - try { - const fileContent = Bun.file(filePath); - - const json = await fileContent.json(); - if (!Array.isArray(json)) { - console.error( - `Invalid TA config type - expected array, got ${typeof json}`, - ); - return []; - } - - const config = json.filter(this.validateConfig); - - this.cache.config = config; - this.cache.expires = Date.now() + CACHE_TTL_MS; - - return config; - } catch (e) { - console.error(`Error loading TA config from ${filePath} - ${e}`); - return []; - } - } - - private validateConfig(config: unknown): config is ConfigSchema { - const result = configSchema.safeParse(config); - - if (!result.success) { - console.error( - `Validation error for TA config - ${result.error.message}`, - ); - return false; - } - - return true; - } -} diff --git a/packages/server/src/test-expect.ts b/packages/server/src/test-expect.ts new file mode 100644 index 00000000..ba9592a8 --- /dev/null +++ b/packages/server/src/test-expect.ts @@ -0,0 +1,18 @@ +import { expect } from "bun:test"; + +type AnyOkUnion = { + ok: boolean; + [key: string]: unknown; +}; + +export function expectOK( + result: T, +): asserts result is Extract { + expect(result.ok).toBe(true); +} + +export function expectNotOK( + result: T, +): asserts result is Extract { + expect(result.ok).toBe(false); +} diff --git a/packages/server/src/views/notifications/notifications-empty.ts b/packages/server/src/views/notifications/notifications-empty.ts index 52b54818..3aaa81fe 100644 --- a/packages/server/src/views/notifications/notifications-empty.ts +++ b/packages/server/src/views/notifications/notifications-empty.ts @@ -1,6 +1,7 @@ import cls from "decorator-client/src/styles/notifications-empty.module.css"; import html from "decorator-shared/html"; import { Texts } from "decorator-shared/types"; +import { KattIngenNotifications } from "decorator-shared/views/illustrations"; export type NotificationsEmptyProps = { texts: Texts; @@ -23,11 +24,7 @@ export function NotificationsEmpty({ texts }: NotificationsEmptyProps) { ${texts.notifications_show_all} - + ${KattIngenNotifications({ className: cls.image })} `; } diff --git a/packages/shared/types.ts b/packages/shared/types.ts index 53adeb79..18f6792b 100644 --- a/packages/shared/types.ts +++ b/packages/shared/types.ts @@ -1,4 +1,4 @@ -import { Context, Environment, Language, Params } from "./params"; +import { Environment, Params } from "./params"; export enum MenuValue { PRIVATPERSON = "privatperson", @@ -148,24 +148,6 @@ export type AppState = { features: Features; }; -export type TaskAnalyticsUrlRule = { - url: string; - match: "exact" | "startsWith"; - exclude?: boolean; -}; - -export type TaskAnalyticsSurveyConfig = { - id: string; - selection?: number; - duration?: { - start?: string; - end?: string; - }; - urls?: TaskAnalyticsUrlRule[]; - audience?: Context[]; - language?: Language[]; -}; - export type MainMenuContextLink = { content: string; description?: string; diff --git a/packages/shared/views/illustrations/index.ts b/packages/shared/views/illustrations/index.ts index 4ea4abdb..5a652382 100644 --- a/packages/shared/views/illustrations/index.ts +++ b/packages/shared/views/illustrations/index.ts @@ -1 +1,2 @@ export * from "./veileder"; +export * from "./katt-ingen-notifications"; diff --git a/packages/shared/views/illustrations/katt-ingen-notifications.ts b/packages/shared/views/illustrations/katt-ingen-notifications.ts new file mode 100644 index 00000000..013ba499 --- /dev/null +++ b/packages/shared/views/illustrations/katt-ingen-notifications.ts @@ -0,0 +1,107 @@ +import html from "../../html"; + +export const KattIngenNotifications = ({ + className, +}: { className?: string } = {}) => + html``; diff --git a/packages/shared/views/read-more.stories.ts b/packages/shared/views/read-more.stories.ts new file mode 100644 index 00000000..ab15b331 --- /dev/null +++ b/packages/shared/views/read-more.stories.ts @@ -0,0 +1,31 @@ +import type { StoryObj, Meta } from "@storybook/html"; +import { ReadMore, ReadMoreProps } from "./read-more"; +import html from "../html"; + +const meta: Meta = { + title: "read-more", + tags: ["autodocs"], + render: ReadMore, +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + header: "Hva er skjermdeling?", + content: html` +
+ Når du deler skjerm med NAV kontaktsenter kan veilederen hjelpe + deg med å finne fram på nav.no. +
+
+ Veilederen ser kun det du ser på nav.no og kan ikke fylle inn + opplysninger eller sende inn noe på dine vegne. +
+
+ Det er du som godkjenner skjermdeling. Ingenting blir lagret. +
+ `, + }, +}; diff --git a/packages/shared/views/read-more.ts b/packages/shared/views/read-more.ts index e51ca52f..aecc650e 100644 --- a/packages/shared/views/read-more.ts +++ b/packages/shared/views/read-more.ts @@ -1,19 +1,20 @@ -import html from "../html"; - import cls from "decorator-client/src/styles/read-more.module.css"; +import html, { Template } from "../html"; +import { DownChevronIcon } from "./icons"; -type ReadMoreProps = { +export type ReadMoreProps = { header: string; - content: string[]; + content: Template; }; export const ReadMore = (props: ReadMoreProps) => { return html`
- ${props.header} -
- ${props.content.map((a) => html`

${a}

`)} -
+ + ${DownChevronIcon({ className: cls.icon })} +
${props.header}
+
+
${props.content}
`; }; diff --git a/packages/shared/views/screensharing-modal.ts b/packages/shared/views/screensharing-modal.ts index ffc7c64c..7409b2c9 100644 --- a/packages/shared/views/screensharing-modal.ts +++ b/packages/shared/views/screensharing-modal.ts @@ -1,15 +1,15 @@ import html, { Template } from "../html"; -import cls from "decorator-client/src/styles/screensharing-modal.module.css"; -import clsModal from "decorator-client/src/styles/modal.module.css"; import clsInputs from "decorator-client/src/styles/inputs.module.css"; +import clsModal from "decorator-client/src/styles/modal.module.css"; +import cls from "decorator-client/src/styles/screensharing-modal.module.css"; import { VeilederIllustration } from "decorator-shared/views/illustrations"; -import { Button } from "./components/button"; +import { match } from "ts-pattern"; import { Texts, WithTexts } from "../types"; -import { ReadMore } from "./read-more"; import { Alert } from "./alert"; -import { match } from "ts-pattern"; +import { Button } from "./components/button"; +import { ReadMore } from "./read-more"; export type ScreensharingModalProps = { texts: Texts; @@ -35,11 +35,11 @@ const ScreensharingModal = ({

${texts.delskjerm_modal_beskrivelse}

${ReadMore({ header: texts.delskjerm_modal_hjelpetekst_overskrift, - content: [ - texts.delskjerm_modal_hjelpetekst_0, - texts.delskjerm_modal_hjelpetekst_1, - texts.delskjerm_modal_hjelpetekst_2, - ], + content: html` +
${texts.delskjerm_modal_hjelpetekst_0}
+
${texts.delskjerm_modal_hjelpetekst_1}
+
${texts.delskjerm_modal_hjelpetekst_2}
+ `, })}
${children}