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