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."
>