diff --git a/src/app/(main)/websites/[websiteId]/replays/ReplayModal.tsx b/src/app/(main)/websites/[websiteId]/replays/ReplayModal.tsx
new file mode 100644
index 000000000..362724bf5
--- /dev/null
+++ b/src/app/(main)/websites/[websiteId]/replays/ReplayModal.tsx
@@ -0,0 +1,43 @@
+'use client';
+import { Column, Dialog, Modal, type ModalProps } from '@umami/react-zen';
+import { ReplayPlayback } from '@/app/(main)/websites/[websiteId]/replays/[sessionId]/ReplayPlayback';
+import { useNavigation } from '@/components/hooks';
+
+export interface ReplayModalProps extends ModalProps {
+ websiteId: string;
+}
+
+export function ReplayModal({ websiteId, ...props }: ReplayModalProps) {
+ const {
+ router,
+ query: { replay },
+ updateParams,
+ } = useNavigation();
+
+ const handleOpenChange = (isOpen: boolean) => {
+ if (!isOpen) {
+ router.push(updateParams({ replay: undefined }));
+ }
+ };
+
+ return (
+
+
+
+
+
+ );
+}
diff --git a/src/app/(main)/websites/[websiteId]/replays/ReplaysDataTable.tsx b/src/app/(main)/websites/[websiteId]/replays/ReplaysDataTable.tsx
index 19e195d80..6d047081d 100644
--- a/src/app/(main)/websites/[websiteId]/replays/ReplaysDataTable.tsx
+++ b/src/app/(main)/websites/[websiteId]/replays/ReplaysDataTable.tsx
@@ -8,7 +8,7 @@ export function ReplaysDataTable({ websiteId }: { websiteId: string }) {
return (
{({ data }) => {
- return ;
+ return ;
}}
);
diff --git a/src/app/(main)/websites/[websiteId]/replays/ReplaysPage.tsx b/src/app/(main)/websites/[websiteId]/replays/ReplaysPage.tsx
index d4127fc6d..414fae0fb 100644
--- a/src/app/(main)/websites/[websiteId]/replays/ReplaysPage.tsx
+++ b/src/app/(main)/websites/[websiteId]/replays/ReplaysPage.tsx
@@ -3,6 +3,7 @@ import { Column } from '@umami/react-zen';
import { SessionModal } from '@/app/(main)/websites/[websiteId]/sessions/SessionModal';
import { WebsiteControls } from '@/app/(main)/websites/[websiteId]/WebsiteControls';
import { Panel } from '@/components/common/Panel';
+import { ReplayModal } from './ReplayModal';
import { ReplaysDataTable } from './ReplaysDataTable';
export function ReplaysPage({ websiteId }: { websiteId: string }) {
@@ -13,6 +14,7 @@ export function ReplaysPage({ websiteId }: { websiteId: string }) {
+
);
}
diff --git a/src/app/(main)/websites/[websiteId]/replays/ReplaysTable.tsx b/src/app/(main)/websites/[websiteId]/replays/ReplaysTable.tsx
index 5549d3ddc..2ab8f860d 100644
--- a/src/app/(main)/websites/[websiteId]/replays/ReplaysTable.tsx
+++ b/src/app/(main)/websites/[websiteId]/replays/ReplaysTable.tsx
@@ -13,10 +13,10 @@ function formatDuration(ms: number) {
return `${minutes}:${secs.toString().padStart(2, '0')}`;
}
-export function ReplaysTable({ websiteId, ...props }: DataTableProps & { websiteId: string }) {
+export function ReplaysTable({ ...props }: DataTableProps) {
const { t, labels } = useMessages();
const { formatValue } = useFormat();
- const { updateParams } = useNavigation();
+ const { router, updateParams } = useNavigation();
return (
@@ -65,13 +65,11 @@ export function ReplaysTable({ websiteId, ...props }: DataTableProps & { website
{(row: any) => (
-
-
-
+
)}
diff --git a/src/app/(main)/websites/[websiteId]/replays/[sessionId]/ReplayPlayback.tsx b/src/app/(main)/websites/[websiteId]/replays/[sessionId]/ReplayPlayback.tsx
index 9dfcf3793..5046f144a 100644
--- a/src/app/(main)/websites/[websiteId]/replays/[sessionId]/ReplayPlayback.tsx
+++ b/src/app/(main)/websites/[websiteId]/replays/[sessionId]/ReplayPlayback.tsx
@@ -1,12 +1,21 @@
'use client';
-import { Column, Row, Text } from '@umami/react-zen';
+import { Button, Column, Icon, Row, Text } from '@umami/react-zen';
+import { X } from 'lucide-react';
import { SessionInfo } from '@/app/(main)/websites/[websiteId]/sessions/SessionInfo';
import { Avatar } from '@/components/common/Avatar';
import { LoadingPanel } from '@/components/common/LoadingPanel';
import { useMessages, useReplayQuery, useWebsiteSessionQuery } from '@/components/hooks';
import { ReplayPlayer } from './ReplayPlayer';
-export function ReplayPlayback({ websiteId, sessionId }: { websiteId: string; sessionId: string }) {
+export function ReplayPlayback({
+ websiteId,
+ sessionId,
+ onClose,
+}: {
+ websiteId: string;
+ sessionId: string;
+ onClose?: () => void;
+}) {
const { data: replay, isLoading, error } = useReplayQuery(websiteId, sessionId);
const { data: session } = useWebsiteSessionQuery(websiteId, sessionId);
const { t, labels } = useMessages();
@@ -22,14 +31,23 @@ export function ReplayPlayback({ websiteId, sessionId }: { websiteId: string; se
{replay && (
{session && (
-
-
-
- {t(labels.replay)}
-
- {replay.eventCount} {t(labels.actions).toLowerCase()}
-
-
+
+
+
+
+ {t(labels.replay)}
+
+ {replay.eventCount} {t(labels.actions).toLowerCase()}
+
+
+
+ {onClose && (
+
+ )}
)}
diff --git a/src/app/(main)/websites/[websiteId]/replays/[sessionId]/ReplayPlayer.tsx b/src/app/(main)/websites/[websiteId]/replays/[sessionId]/ReplayPlayer.tsx
index bb2a99a35..dfc3a12f0 100644
--- a/src/app/(main)/websites/[websiteId]/replays/[sessionId]/ReplayPlayer.tsx
+++ b/src/app/(main)/websites/[websiteId]/replays/[sessionId]/ReplayPlayer.tsx
@@ -1,21 +1,24 @@
'use client';
import { Column } from '@umami/react-zen';
import { useEffect, useRef, useState } from 'react';
+import { useMobile } from '@/components/hooks';
import 'rrweb-player/dist/style.css';
export function ReplayPlayer({ events }: { events: any[] }) {
const containerRef = useRef(null);
const playerRef = useRef(null);
const [loaded, setLoaded] = useState(false);
+ const { isMobile, isPhone } = useMobile();
+
+ const playerWidth = isPhone ? 360 : isMobile ? 640 : 1024;
+ const playerHeight = isPhone ? 202 : isMobile ? 360 : 576;
useEffect(() => {
if (!containerRef.current || !events?.length) return;
- // Dynamically import rrweb-player to avoid SSR issues
import('rrweb-player').then(mod => {
const RRWebPlayer = mod.default;
- // Clear any previous player
if (containerRef.current) {
containerRef.current.innerHTML = '';
}
@@ -24,8 +27,8 @@ export function ReplayPlayer({ events }: { events: any[] }) {
target: containerRef.current,
props: {
events,
- width: 1024,
- height: 576,
+ width: playerWidth,
+ height: playerHeight,
autoPlay: false,
showController: true,
speedOption: [1, 2, 4, 8],
@@ -41,15 +44,15 @@ export function ReplayPlayer({ events }: { events: any[] }) {
playerRef.current = null;
}
};
- }, [events]);
+ }, [events, playerWidth, playerHeight]);
return (