From c961ee3c14d6218c6486fb75901e3ad1d781d1ee Mon Sep 17 00:00:00 2001 From: Maximilian Kaske <56969857+mxkaske@users.noreply.github.com> Date: Sun, 10 Mar 2024 15:07:55 +0100 Subject: [PATCH] feat: add preferred-settings cookie to save user preferences (#707) --- .../_components/combined-chart-wrapper.tsx | 17 +++++-- .../monitors/[id]/overview/page.tsx | 3 ++ apps/web/src/lib/preferred-settings/client.ts | 46 +++++++++++++++++++ apps/web/src/lib/preferred-settings/server.ts | 14 ++++++ apps/web/src/lib/preferred-settings/shared.ts | 1 + .../src/lib/preferred-settings/validation.ts | 11 +++++ 6 files changed, 89 insertions(+), 3 deletions(-) create mode 100644 apps/web/src/lib/preferred-settings/client.ts create mode 100644 apps/web/src/lib/preferred-settings/server.ts create mode 100644 apps/web/src/lib/preferred-settings/shared.ts create mode 100644 apps/web/src/lib/preferred-settings/validation.ts diff --git a/apps/web/src/app/app/[workspaceSlug]/(dashboard)/monitors/[id]/overview/_components/combined-chart-wrapper.tsx b/apps/web/src/app/app/[workspaceSlug]/(dashboard)/monitors/[id]/overview/_components/combined-chart-wrapper.tsx index df8afd1b..21431780 100644 --- a/apps/web/src/app/app/[workspaceSlug]/(dashboard)/monitors/[id]/overview/_components/combined-chart-wrapper.tsx +++ b/apps/web/src/app/app/[workspaceSlug]/(dashboard)/monitors/[id]/overview/_components/combined-chart-wrapper.tsx @@ -1,6 +1,6 @@ "use client"; -import { useMemo, useState } from "react"; +import { useMemo } from "react"; import { LineChart } from "lucide-react"; import type { Monitor } from "@openstatus/db/src/schema"; @@ -11,6 +11,8 @@ import type { } from "@openstatus/tinybird"; import { Toggle } from "@openstatus/ui"; +import { usePreferredSettings } from "@/lib/preferred-settings/client"; +import type { PreferredSettings } from "@/lib/preferred-settings/server"; import { IntervalPreset } from "../../_components/interval-preset"; import { QuantilePreset } from "../../_components/quantile-preset"; import { RegionsPreset } from "../../_components/region-preset"; @@ -28,6 +30,7 @@ export function CombinedChartWrapper({ monitor, isQuantileDisabled, metricsByRegion, + preferredSettings: defaultPreferredSettings, }: { data: ResponseGraph[]; period: Period; @@ -37,12 +40,18 @@ export function CombinedChartWrapper({ monitor: Monitor; isQuantileDisabled: boolean; metricsByRegion: ResponseTimeMetricsByRegion[]; + preferredSettings: PreferredSettings; }) { const chartData = useMemo( () => groupDataByTimestamp(data, period, quantile), [data, period, quantile], ); - const [combinedRegions, setCombinedRegions] = useState(false); + + const [preferredSettings, setPreferredSettings] = usePreferredSettings( + defaultPreferredSettings, + ); + + const combinedRegions = preferredSettings?.combinedRegions ?? false; return ( <> @@ -52,7 +61,9 @@ export function CombinedChartWrapper({

Change the view

{ + setPreferredSettings({ combinedRegions: value }); + }} variant="outline" className="w-max" > diff --git a/apps/web/src/app/app/[workspaceSlug]/(dashboard)/monitors/[id]/overview/page.tsx b/apps/web/src/app/app/[workspaceSlug]/(dashboard)/monitors/[id]/overview/page.tsx index eca5092b..dba01e4d 100644 --- a/apps/web/src/app/app/[workspaceSlug]/(dashboard)/monitors/[id]/overview/page.tsx +++ b/apps/web/src/app/app/[workspaceSlug]/(dashboard)/monitors/[id]/overview/page.tsx @@ -8,6 +8,7 @@ import { OSTinybird } from "@openstatus/tinybird"; import { Separator } from "@openstatus/ui"; import { env } from "@/env"; +import { getPreferredSettings } from "@/lib/preferred-settings/server"; import { api } from "@/trpc/server"; import { ButtonReset } from "../_components/button-reset"; import { DatePickerPreset } from "../_components/date-picker-preset"; @@ -51,6 +52,7 @@ export default async function Page({ }) { const id = params.id; const search = searchParamsSchema.safeParse(searchParams); + const preferredSettings = getPreferredSettings(); const monitor = await api.monitor.getMonitorById.query({ id: Number(id), @@ -110,6 +112,7 @@ export default async function Page({ monitor={monitor} isQuantileDisabled={isQuantileDisabled} metricsByRegion={metricsByRegion} + preferredSettings={preferredSettings} /> ); diff --git a/apps/web/src/lib/preferred-settings/client.ts b/apps/web/src/lib/preferred-settings/client.ts new file mode 100644 index 00000000..ea8281a1 --- /dev/null +++ b/apps/web/src/lib/preferred-settings/client.ts @@ -0,0 +1,46 @@ +import { useState } from "react"; + +import { COOKIE_NAME } from "./shared"; +import type { PreferredSettings } from "./validation"; +import { preferencesSchema } from "./validation"; + +function getPreferredSettingsCookie() { + const cookie = document.cookie + .split(";") + .find((cookie) => cookie.trim().startsWith(`${COOKIE_NAME}=`)); + if (!cookie) return {}; + const settings = preferencesSchema.safeParse( + JSON.parse(cookie.split("=")[1]), + ); + if (!settings.success) return {}; + return settings.data; +} + +function setPreferredSettingsCookie(value: Record) { + const month = 30 * 24 * 60 * 60 * 1000; + const expires = new Date(Date.now() + month).toUTCString(); + document.cookie = `${COOKIE_NAME}=${JSON.stringify( + value, + )}; path=/; expires=${expires}`; +} + +/** + * Update user preferences and store them in a cookie accessible on the client and server + */ +export function usePreferredSettings(defaultValue: PreferredSettings) { + const [settings, setSettings] = useState(defaultValue); + + const handleChange = (value: Record) => { + const settings = preferencesSchema.safeParse(value); + + if (!settings.success) return; + + const currentSettings = getPreferredSettingsCookie(); + const newSettings = { ...currentSettings, ...settings.data }; + + setPreferredSettingsCookie(newSettings); + setSettings(newSettings); + }; + + return [settings, handleChange] as const; +} diff --git a/apps/web/src/lib/preferred-settings/server.ts b/apps/web/src/lib/preferred-settings/server.ts new file mode 100644 index 00000000..e02ae476 --- /dev/null +++ b/apps/web/src/lib/preferred-settings/server.ts @@ -0,0 +1,14 @@ +import { cookies } from "next/headers"; + +import { COOKIE_NAME } from "./shared"; +import { preferencesSchema } from "./validation"; + +export function getPreferredSettings() { + const cookie = cookies().get(COOKIE_NAME); + const parsed = cookie ? JSON.parse(cookie.value) : {}; + const settings = preferencesSchema.safeParse(parsed); + if (!settings.success) return undefined; + return settings.data; +} + +export type PreferredSettings = ReturnType; diff --git a/apps/web/src/lib/preferred-settings/shared.ts b/apps/web/src/lib/preferred-settings/shared.ts new file mode 100644 index 00000000..cd847d59 --- /dev/null +++ b/apps/web/src/lib/preferred-settings/shared.ts @@ -0,0 +1 @@ +export const COOKIE_NAME = "preferred-settings"; diff --git a/apps/web/src/lib/preferred-settings/validation.ts b/apps/web/src/lib/preferred-settings/validation.ts new file mode 100644 index 00000000..341ca0fc --- /dev/null +++ b/apps/web/src/lib/preferred-settings/validation.ts @@ -0,0 +1,11 @@ +import { z } from "zod"; + +export const preferencesSchema = z + .object({ + combinedRegions: z.boolean().nullable().default(false).optional(), + // ... other settings to store user preferences + // accessible via document.cookie in the client and cookies() on the server + }) + .optional(); + +export type PreferredSettings = z.infer; -- 2.51.2