From 5bd92ce950421a19d45c2c8446f394a5029990ec Mon Sep 17 00:00:00 2001 From: Francis Cao Date: Tue, 19 May 2026 15:46:01 -0700 Subject: [PATCH] implement sepearte replay/heatmap enabled routes and collection --- .../migrations/20_add_heatmap/migration.sql | 6 + prisma/schema.prisma | 4 +- public/intl/messages/bs-BA.json | 2 +- public/intl/messages/ca-ES.json | 2 +- public/intl/messages/cs-CZ.json | 2 +- public/intl/messages/da-DK.json | 2 +- public/intl/messages/de-CH.json | 2 +- public/intl/messages/de-DE.json | 2 +- public/intl/messages/en-GB.json | 2 +- public/intl/messages/en-US.json | 2 +- public/intl/messages/es-ES.json | 2 +- public/intl/messages/fi-FI.json | 2 +- public/intl/messages/fo-FO.json | 2 +- public/intl/messages/fr-FR.json | 2 +- public/intl/messages/ga-ES.json | 2 +- public/intl/messages/hr-HR.json | 2 +- public/intl/messages/hu-HU.json | 2 +- public/intl/messages/id-ID.json | 2 +- public/intl/messages/it-IT.json | 2 +- public/intl/messages/lt-LT.json | 2 +- public/intl/messages/ms-MY.json | 2 +- public/intl/messages/nb-NO.json | 2 +- public/intl/messages/nl-NL.json | 2 +- public/intl/messages/pl-PL.json | 2 +- public/intl/messages/pt-BR.json | 2 +- public/intl/messages/pt-PT.json | 2 +- public/intl/messages/ro-RO.json | 2 +- public/intl/messages/sk-SK.json | 2 +- public/intl/messages/sl-SI.json | 2 +- public/intl/messages/sv-SE.json | 2 +- public/intl/messages/tr-TR.json | 2 +- public/intl/messages/uz-UZ.json | 2 +- .../settings/WebsiteReplaySettings.tsx | 193 ++++++---- src/app/api/record/route.ts | 154 ++++++-- .../websites/[websiteId]/recorder/route.ts | 16 +- src/app/api/websites/[websiteId]/route.ts | 29 +- src/components/messages.ts | 2 +- src/lib/recorder.ts | 54 +++ src/recorder/index.js | 363 +++++++++++------- 39 files changed, 586 insertions(+), 295 deletions(-) create mode 100644 src/lib/recorder.ts diff --git a/prisma/migrations/20_add_heatmap/migration.sql b/prisma/migrations/20_add_heatmap/migration.sql index e5d5dface..f3e3d30f5 100644 --- a/prisma/migrations/20_add_heatmap/migration.sql +++ b/prisma/migrations/20_add_heatmap/migration.sql @@ -1,3 +1,9 @@ +ALTER TABLE "website" RENAME COLUMN "replay_enabled" TO "recorder_enabled"; + +UPDATE "website" +SET "replay_config" = COALESCE("replay_config", '{}'::jsonb) || '{"replayEnabled": true}'::jsonb +WHERE "recorder_enabled" = true; + -- CreateTable CREATE TABLE "heatmap_event" ( "heatmap_event_id" UUID NOT NULL, diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 23d29c964..16993f067 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -75,8 +75,8 @@ model Website { updatedAt DateTime? @updatedAt @map("updated_at") @db.Timestamptz(6) deletedAt DateTime? @map("deleted_at") @db.Timestamptz(6) - replayEnabled Boolean @default(false) @map("replay_enabled") - replayConfig Json? @map("replay_config") + recorderEnabled Boolean @default(false) @map("recorder_enabled") + replayConfig Json? @map("replay_config") user User? @relation("user", fields: [userId], references: [id]) createUser User? @relation("createUser", fields: [createdBy], references: [id]) diff --git a/public/intl/messages/bs-BA.json b/public/intl/messages/bs-BA.json index 931956515..30abc781a 100644 --- a/public/intl/messages/bs-BA.json +++ b/public/intl/messages/bs-BA.json @@ -256,7 +256,7 @@ "play": "Reprodukuj", "replay": "Ponovna reprodukcija", "replay-id": "Replay ID", - "replay-code": "Kod ponavljanja sesije", + "recorder-code": "Kod ponavljanja sesije", "replay-enabled": "Ponovna reprodukcija omogućena", "replays": "Ponovne reprodukcije", "reports": "Izvještaji", diff --git a/public/intl/messages/ca-ES.json b/public/intl/messages/ca-ES.json index 5c7b62b47..f141503b3 100644 --- a/public/intl/messages/ca-ES.json +++ b/public/intl/messages/ca-ES.json @@ -256,7 +256,7 @@ "play": "Reprodueix", "replay": "Repetició", "replay-id": "Replay ID", - "replay-code": "Codi de repetició de sessió", + "recorder-code": "Codi de repetició de sessió", "replay-enabled": "Repetició activada", "replays": "Repeticions", "reports": "Informes", diff --git a/public/intl/messages/cs-CZ.json b/public/intl/messages/cs-CZ.json index e3789ff28..bac565d41 100644 --- a/public/intl/messages/cs-CZ.json +++ b/public/intl/messages/cs-CZ.json @@ -256,7 +256,7 @@ "play": "Přehrát", "replay": "Přehrávání", "replay-id": "Replay ID", - "replay-code": "Kód přehrání relace", + "recorder-code": "Kód přehrání relace", "replay-enabled": "Přehrávání povoleno", "replays": "Přehrávání", "reports": "Hlášení", diff --git a/public/intl/messages/da-DK.json b/public/intl/messages/da-DK.json index 8d3ed3cda..f214774df 100644 --- a/public/intl/messages/da-DK.json +++ b/public/intl/messages/da-DK.json @@ -256,7 +256,7 @@ "play": "Afspil", "replay": "Afspilning", "replay-id": "Replay ID", - "replay-code": "Kode til sessionsgengivelse", + "recorder-code": "Kode til sessionsgengivelse", "replay-enabled": "Afspilning aktiveret", "replays": "Afspilninger", "reports": "Rapporter", diff --git a/public/intl/messages/de-CH.json b/public/intl/messages/de-CH.json index 71cce4823..5fbca2798 100644 --- a/public/intl/messages/de-CH.json +++ b/public/intl/messages/de-CH.json @@ -256,7 +256,7 @@ "play": "Abspielen", "replay": "Wiedergabe", "replay-id": "Replay ID", - "replay-code": "Session-Replay-Code", + "recorder-code": "Session-Replay-Code", "replay-enabled": "Wiedergabe aktiviert", "replays": "Wiedergaben", "reports": "Brichte", diff --git a/public/intl/messages/de-DE.json b/public/intl/messages/de-DE.json index 907d3768c..7c0afe6f7 100644 --- a/public/intl/messages/de-DE.json +++ b/public/intl/messages/de-DE.json @@ -256,7 +256,7 @@ "play": "Abspielen", "replay": "Wiedergabe", "replay-id": "Replay ID", - "replay-code": "Session-Replay-Code", + "recorder-code": "Session-Replay-Code", "replay-enabled": "Wiedergabe aktiviert", "replays": "Wiedergaben", "reports": "Berichte", diff --git a/public/intl/messages/en-GB.json b/public/intl/messages/en-GB.json index 243b6e9a5..9c54feb16 100644 --- a/public/intl/messages/en-GB.json +++ b/public/intl/messages/en-GB.json @@ -256,7 +256,7 @@ "play": "Play", "replay": "Replay", "replay-id": "Replay ID", - "replay-code": "Session replay code", + "recorder-code": "Recorder code", "replay-enabled": "Replay enabled", "replays": "Replays", "reports": "Reports", diff --git a/public/intl/messages/en-US.json b/public/intl/messages/en-US.json index b62ed30a0..d1fdfd2ca 100644 --- a/public/intl/messages/en-US.json +++ b/public/intl/messages/en-US.json @@ -264,7 +264,7 @@ "play": "Play", "replay": "Replay", "replay-id": "Replay ID", - "replay-code": "Session replay code", + "recorder-code": "Recorder code", "replay-enabled": "Replay enabled", "replays": "Replays", "reports": "Reports", diff --git a/public/intl/messages/es-ES.json b/public/intl/messages/es-ES.json index 33dbcea29..5efbe9c15 100644 --- a/public/intl/messages/es-ES.json +++ b/public/intl/messages/es-ES.json @@ -256,7 +256,7 @@ "play": "Reproducir", "replay": "Repetición", "replay-id": "Replay ID", - "replay-code": "Código de repetición de sesión", + "recorder-code": "Código de repetición de sesión", "replay-enabled": "Repetición activada", "replays": "Repeticiones", "reports": "Informes", diff --git a/public/intl/messages/fi-FI.json b/public/intl/messages/fi-FI.json index 2fa0135e8..2c0971ffe 100644 --- a/public/intl/messages/fi-FI.json +++ b/public/intl/messages/fi-FI.json @@ -256,7 +256,7 @@ "play": "Toista", "replay": "Toisto", "replay-id": "Replay ID", - "replay-code": "Istunnon toistokoodi", + "recorder-code": "Istunnon toistokoodi", "replay-enabled": "Toisto käytössä", "replays": "Toistot", "reports": "Raportit", diff --git a/public/intl/messages/fo-FO.json b/public/intl/messages/fo-FO.json index 758148dee..14a884e04 100644 --- a/public/intl/messages/fo-FO.json +++ b/public/intl/messages/fo-FO.json @@ -256,7 +256,7 @@ "play": "Spæla", "replay": "Uppspæling", "replay-id": "Replay ID", - "replay-code": "Kodi fyri endurspæling av setuni", + "recorder-code": "Kodi fyri endurspæling av setuni", "replay-enabled": "Uppspæling virkjað", "replays": "Uppspælingar", "reports": "Frágreiðingar", diff --git a/public/intl/messages/fr-FR.json b/public/intl/messages/fr-FR.json index a02786858..b4521bd9a 100644 --- a/public/intl/messages/fr-FR.json +++ b/public/intl/messages/fr-FR.json @@ -259,7 +259,7 @@ "remove": "Retirer", "remove-member": "Retirer le membre", "replay": "Relecture", - "replay-code": "Code de relecture de session", + "recorder-code": "Code de relecture de session", "replay-enabled": "Relecture activée", "replay-id": "Replay ID", "replays": "Relectures", diff --git a/public/intl/messages/ga-ES.json b/public/intl/messages/ga-ES.json index 0ac9bdf82..7088a5204 100644 --- a/public/intl/messages/ga-ES.json +++ b/public/intl/messages/ga-ES.json @@ -256,7 +256,7 @@ "play": "Reproducir", "replay": "Reprodución", "replay-id": "Replay ID", - "replay-code": "Código de repetición da sesión", + "recorder-code": "Código de repetición da sesión", "replay-enabled": "Reprodución activada", "replays": "Reproducións", "reports": "Reportes", diff --git a/public/intl/messages/hr-HR.json b/public/intl/messages/hr-HR.json index a5c022162..4e7d10889 100644 --- a/public/intl/messages/hr-HR.json +++ b/public/intl/messages/hr-HR.json @@ -256,7 +256,7 @@ "play": "Reproduciraj", "replay": "Ponovna reprodukcija", "replay-id": "Replay ID", - "replay-code": "Kôd ponavljanja sesije", + "recorder-code": "Kôd ponavljanja sesije", "replay-enabled": "Ponovna reprodukcija omogućena", "replays": "Ponovne reprodukcije", "reports": "Izvješća", diff --git a/public/intl/messages/hu-HU.json b/public/intl/messages/hu-HU.json index d72587e01..181c3e53a 100644 --- a/public/intl/messages/hu-HU.json +++ b/public/intl/messages/hu-HU.json @@ -256,7 +256,7 @@ "play": "Lejátszás", "replay": "Visszajátszás", "replay-id": "Replay ID", - "replay-code": "Munkamenet-újrajátszási kód", + "recorder-code": "Munkamenet-újrajátszási kód", "replay-enabled": "Visszajátszás engedélyezve", "replays": "Visszajátszások", "reports": "Jelentések", diff --git a/public/intl/messages/id-ID.json b/public/intl/messages/id-ID.json index 64e68bbbe..a609fe768 100644 --- a/public/intl/messages/id-ID.json +++ b/public/intl/messages/id-ID.json @@ -256,7 +256,7 @@ "play": "Putar", "replay": "Pemutaran ulang", "replay-id": "Replay ID", - "replay-code": "Kode putar ulang sesi", + "recorder-code": "Kode putar ulang sesi", "replay-enabled": "Pemutaran ulang diaktifkan", "replays": "Pemutaran ulang", "reports": "Laporan", diff --git a/public/intl/messages/it-IT.json b/public/intl/messages/it-IT.json index 0ad631dcd..4f460295c 100644 --- a/public/intl/messages/it-IT.json +++ b/public/intl/messages/it-IT.json @@ -256,7 +256,7 @@ "play": "Riproduci", "replay": "Replay", "replay-id": "Replay ID", - "replay-code": "Codice replay sessione", + "recorder-code": "Codice replay sessione", "replay-enabled": "Replay abilitato", "replays": "Replay", "reports": "Report", diff --git a/public/intl/messages/lt-LT.json b/public/intl/messages/lt-LT.json index cdb4c8191..4e6e8e078 100644 --- a/public/intl/messages/lt-LT.json +++ b/public/intl/messages/lt-LT.json @@ -256,7 +256,7 @@ "play": "Leisti", "replay": "Pakartojimas", "replay-id": "Replay ID", - "replay-code": "Sesijos pakartojimo kodas", + "recorder-code": "Sesijos pakartojimo kodas", "replay-enabled": "Pakartojimas įjungtas", "replays": "Pakartojimai", "reports": "Ataskaitos", diff --git a/public/intl/messages/ms-MY.json b/public/intl/messages/ms-MY.json index 56ed4f7ac..aa81cb9ef 100644 --- a/public/intl/messages/ms-MY.json +++ b/public/intl/messages/ms-MY.json @@ -256,7 +256,7 @@ "play": "Main", "replay": "Main semula", "replay-id": "Replay ID", - "replay-code": "Kod main semula sesi", + "recorder-code": "Kod main semula sesi", "replay-enabled": "Main semula diaktifkan", "replays": "Main semula", "reports": "Laporan", diff --git a/public/intl/messages/nb-NO.json b/public/intl/messages/nb-NO.json index 279336971..552fcf4df 100644 --- a/public/intl/messages/nb-NO.json +++ b/public/intl/messages/nb-NO.json @@ -256,7 +256,7 @@ "play": "Spill av", "replay": "Avspilling", "replay-id": "Replay ID", - "replay-code": "Kode for øktavspilling", + "recorder-code": "Kode for øktavspilling", "replay-enabled": "Avspilling aktivert", "replays": "Avspillinger", "reports": "Rapporter", diff --git a/public/intl/messages/nl-NL.json b/public/intl/messages/nl-NL.json index 446f84ff4..e4f0df26b 100644 --- a/public/intl/messages/nl-NL.json +++ b/public/intl/messages/nl-NL.json @@ -256,7 +256,7 @@ "play": "Afspelen", "replay": "Herhaling", "replay-id": "Replay ID", - "replay-code": "Sessieherhalingscode", + "recorder-code": "Sessieherhalingscode", "replay-enabled": "Herhaling ingeschakeld", "replays": "Herhalingen", "reports": "Rapporten", diff --git a/public/intl/messages/pl-PL.json b/public/intl/messages/pl-PL.json index aff7aa1da..b6a5ed716 100644 --- a/public/intl/messages/pl-PL.json +++ b/public/intl/messages/pl-PL.json @@ -256,7 +256,7 @@ "play": "Odtwórz", "replay": "Odtwarzanie", "replay-id": "Replay ID", - "replay-code": "Kod odtworzenia sesji", + "recorder-code": "Kod odtworzenia sesji", "replay-enabled": "Odtwarzanie włączone", "replays": "Odtwarzania", "reports": "Raporty", diff --git a/public/intl/messages/pt-BR.json b/public/intl/messages/pt-BR.json index 698c190fd..3b4fce82c 100644 --- a/public/intl/messages/pt-BR.json +++ b/public/intl/messages/pt-BR.json @@ -256,7 +256,7 @@ "play": "Reproduzir", "replay": "Reprodução", "replay-id": "Replay ID", - "replay-code": "Código de replay da sessão", + "recorder-code": "Código de replay da sessão", "replay-enabled": "Reprodução habilitada", "replays": "Reproduções", "reports": "Relatórios", diff --git a/public/intl/messages/pt-PT.json b/public/intl/messages/pt-PT.json index 7e24883cf..ddc8fef52 100644 --- a/public/intl/messages/pt-PT.json +++ b/public/intl/messages/pt-PT.json @@ -256,7 +256,7 @@ "play": "Reproduzir", "replay": "Reprodução", "replay-id": "Replay ID", - "replay-code": "Código de repetição de sessão", + "recorder-code": "Código de repetição de sessão", "replay-enabled": "Reprodução ativada", "replays": "Reproduções", "reports": "Relatórios", diff --git a/public/intl/messages/ro-RO.json b/public/intl/messages/ro-RO.json index 84ee2bc69..43e1ab49c 100644 --- a/public/intl/messages/ro-RO.json +++ b/public/intl/messages/ro-RO.json @@ -256,7 +256,7 @@ "play": "Redă", "replay": "Reluare", "replay-id": "Replay ID", - "replay-code": "Cod de reluare sesiune", + "recorder-code": "Cod de reluare sesiune", "replay-enabled": "Reluare activată", "replays": "Reluări", "reports": "Rapoarte", diff --git a/public/intl/messages/sk-SK.json b/public/intl/messages/sk-SK.json index 1f429dfa2..fcfc46ed6 100644 --- a/public/intl/messages/sk-SK.json +++ b/public/intl/messages/sk-SK.json @@ -256,7 +256,7 @@ "play": "Prehrať", "replay": "Prehrávanie", "replay-id": "Replay ID", - "replay-code": "Kód prehrania relácie", + "recorder-code": "Kód prehrania relácie", "replay-enabled": "Prehrávanie povolené", "replays": "Prehrávanie", "reports": "Správy", diff --git a/public/intl/messages/sl-SI.json b/public/intl/messages/sl-SI.json index f8fc8407f..6f9c7e2de 100644 --- a/public/intl/messages/sl-SI.json +++ b/public/intl/messages/sl-SI.json @@ -256,7 +256,7 @@ "play": "Predvajaj", "replay": "Predvajanje", "replay-id": "Replay ID", - "replay-code": "Koda ponovitve seje", + "recorder-code": "Koda ponovitve seje", "replay-enabled": "Predvajanje omogočeno", "replays": "Predvajanja", "reports": "Poročila", diff --git a/public/intl/messages/sv-SE.json b/public/intl/messages/sv-SE.json index b600465ff..322fb70d6 100644 --- a/public/intl/messages/sv-SE.json +++ b/public/intl/messages/sv-SE.json @@ -256,7 +256,7 @@ "play": "Spela upp", "replay": "Uppspelning", "replay-id": "Replay ID", - "replay-code": "Kod för sessionsuppspelning", + "recorder-code": "Kod för sessionsuppspelning", "replay-enabled": "Uppspelning aktiverad", "replays": "Uppspelningar", "reports": "Rapporter", diff --git a/public/intl/messages/tr-TR.json b/public/intl/messages/tr-TR.json index 0725081cb..a795f4ef2 100644 --- a/public/intl/messages/tr-TR.json +++ b/public/intl/messages/tr-TR.json @@ -256,7 +256,7 @@ "play": "Oynat", "replay": "Tekrar oynatma", "replay-id": "Replay ID", - "replay-code": "Oturum tekrar oynatma kodu", + "recorder-code": "Oturum tekrar oynatma kodu", "replay-enabled": "Tekrar oynatma etkin", "replays": "Tekrar oynatmalar", "reports": "Raporlar", diff --git a/public/intl/messages/uz-UZ.json b/public/intl/messages/uz-UZ.json index 9b54f139c..78b1412d8 100644 --- a/public/intl/messages/uz-UZ.json +++ b/public/intl/messages/uz-UZ.json @@ -256,7 +256,7 @@ "play": "Ijro etish", "replay": "Qayta ijro", "replay-id": "Replay ID", - "replay-code": "Sessiya takrorlash kodi", + "recorder-code": "Sessiya takrorlash kodi", "replay-enabled": "Qayta ijro yoqilgan", "replays": "Qayta ijrolar", "reports": "Hisobotlar", diff --git a/src/app/(main)/websites/[websiteId]/settings/WebsiteReplaySettings.tsx b/src/app/(main)/websites/[websiteId]/settings/WebsiteReplaySettings.tsx index 39cd4e816..d7309affe 100644 --- a/src/app/(main)/websites/[websiteId]/settings/WebsiteReplaySettings.tsx +++ b/src/app/(main)/websites/[websiteId]/settings/WebsiteReplaySettings.tsx @@ -10,48 +10,59 @@ import { Text, TextField, } from '@umami/react-zen'; -import { useState } from 'react'; +import { useEffect, useState } from 'react'; import { EmptyPlaceholder } from '@/components/common/EmptyPlaceholder'; import { useMessages, useSubscription, useUpdateQuery, useWebsite } from '@/components/hooks'; import { Video } from '@/components/icons'; +import { getRecorderConfig, type RecorderConfig } from '@/lib/recorder'; const RECORDER_NAME = 'recorder.js'; -interface ReplayConfig { - sampleRate?: number; - maskLevel?: string; - maxDuration?: number; - blockSelector?: string; -} - export function WebsiteReplaySettings({ websiteId }: { websiteId: string }) { const website = useWebsite(); const { t, labels, messages } = useMessages(); const { hasFeature, cloudMode } = useSubscription(website?.teamId); const { mutateAsync, touch, toast, isPending } = useUpdateQuery(`/websites/${websiteId}`); - const [enabled, setEnabled] = useState(website?.replayEnabled ?? false); - - const config = (website?.replayConfig as ReplayConfig) || {}; + const config = getRecorderConfig(website?.replayConfig); + const [replayEnabled, setReplayEnabled] = useState(config.replayEnabled === true); + const [heatmapEnabled, setHeatmapEnabled] = useState(config.heatmapEnabled === true); const [sampleRate, setSampleRate] = useState(config.sampleRate ?? 0.15); + const [heatmapSampleRate, setHeatmapSampleRate] = useState(config.heatmapSampleRate ?? 0.15); const [maskLevel, setMaskLevel] = useState(config.maskLevel ?? 'moderate'); const [maxDuration, setMaxDuration] = useState(String(config.maxDuration ?? 300000)); const [blockSelector, setBlockSelector] = useState(config.blockSelector ?? ''); + useEffect(() => { + setReplayEnabled(config.replayEnabled === true); + setHeatmapEnabled(config.heatmapEnabled === true); + setSampleRate(config.sampleRate ?? 0.15); + setHeatmapSampleRate(config.heatmapSampleRate ?? 0.15); + setMaskLevel(config.maskLevel ?? 'moderate'); + setMaxDuration(String(config.maxDuration ?? 300000)); + setBlockSelector(config.blockSelector ?? ''); + }, [ + config.blockSelector, + config.heatmapEnabled, + config.heatmapSampleRate, + config.maskLevel, + config.maxDuration, + config.replayEnabled, + config.sampleRate, + ]); + const recorderUrl = cloudMode ? `${process.env.cloudUrl}/${RECORDER_NAME}` : `${window?.location?.origin || ''}${process.env.basePath || ''}/${RECORDER_NAME}`; const recorderCode = ``; + const sectionLabel = `${t(labels.replays)} & ${t(labels.heatmaps)}`; - const handleToggle = async (value: boolean) => { - const previous = enabled; - setEnabled(value); - + const saveRecorderConfig = async (nextConfig: RecorderConfig, rollback?: () => void) => { try { await mutateAsync( { - replayEnabled: value, + replayConfig: nextConfig, }, { onSuccess: async () => { @@ -62,39 +73,48 @@ export function WebsiteReplaySettings({ websiteId }: { websiteId: string }) { }, ); } catch { - setEnabled(previous); + rollback?.(); } }; + const getNextConfig = (overrides: Partial = {}): RecorderConfig => ({ + ...config, + replayEnabled, + heatmapEnabled, + sampleRate, + heatmapSampleRate, + maskLevel, + maxDuration: parseInt(maxDuration, 10) || 300000, + blockSelector, + ...overrides, + }); + + const handleReplayToggle = async (value: boolean) => { + const previous = replayEnabled; + + setReplayEnabled(value); + await saveRecorderConfig(getNextConfig({ replayEnabled: value }), () => setReplayEnabled(previous)); + }; + + const handleHeatmapToggle = async (value: boolean) => { + const previous = heatmapEnabled; + + setHeatmapEnabled(value); + await saveRecorderConfig(getNextConfig({ heatmapEnabled: value }), () => setHeatmapEnabled(previous)); + }; + const handleSave = async () => { - await mutateAsync( - { - replayEnabled: enabled, - replayConfig: { - sampleRate, - maskLevel, - maxDuration: parseInt(maxDuration, 10) || 300000, - ...(blockSelector && { blockSelector }), - }, - }, - { - onSuccess: async () => { - toast(t(messages.saved)); - touch('websites'); - touch(`website:${websiteId}`); - }, - }, - ); + await saveRecorderConfig(getNextConfig()); }; if (cloudMode && !hasFeature('replays')) { return ( - + } title={t(messages.upgradeRequired, { plan: 'Business' })} - description="Watch real user sessions to see exactly how visitors interact with your site." + description="Watch real user sessions and build heatmaps from real visitor behavior." >