diff --git a/apps/desktop/src/lib/conversationFeatures.ts b/apps/desktop/src/lib/conversationFeatures.ts
index 2dc52a0..4fd2ed1 100644
--- a/apps/desktop/src/lib/conversationFeatures.ts
+++ b/apps/desktop/src/lib/conversationFeatures.ts
@@ -5,6 +5,7 @@ import {
type DecryptedMessage,
type PollOption,
type WhiteboardPayload,
+ type WatchTogetherPayload,
} from '@chat-app/shared/chat';
export type AttachmentBucket = 'media' | 'audio' | 'files';
@@ -107,6 +108,15 @@ export function createWhiteboardPayload(whiteboardId: string): string {
return serializeMessagePayload(payload);
}
+export function createWatchTogetherPayload(sessionId: string): string {
+ const payload: WatchTogetherPayload = {
+ v: 1,
+ type: 'watch_together',
+ session_id: sessionId,
+ };
+ return serializeMessagePayload(payload);
+}
+
export function summarizePollVotes(
options: PollOption[],
reactions: ReactionSummaryInput[],
diff --git a/apps/desktop/src/pages/ConversationPage.tsx b/apps/desktop/src/pages/ConversationPage.tsx
index 83871e6..03062a6 100644
--- a/apps/desktop/src/pages/ConversationPage.tsx
+++ b/apps/desktop/src/pages/ConversationPage.tsx
@@ -46,9 +46,11 @@ import {
collectConversationAttachments,
createPollPayload,
createWhiteboardPayload,
+ createWatchTogetherPayload,
} from '../lib/conversationFeatures';
import { WhiteboardModal } from '../components/WhiteboardModal';
-import { createWhiteboard } from '@chat-app/shared/chat';
+import { WatchTogetherModal } from '../components/WatchTogetherModal';
+import { createWhiteboard, createWatchSession, parseYouTubeUrl } from '@chat-app/shared/chat';
import { compressImages } from '../lib/imageCompress';
import { ensureInstallId } from '../lib/installId';
import { searchCachedMessages } from '../lib/messageCache';
@@ -169,6 +171,11 @@ export function ConversationPage() {
const [pollSending, setPollSending] = useState(false);
const [openWhiteboardId, setOpenWhiteboardId] = useState
(null);
const [creatingWhiteboard, setCreatingWhiteboard] = useState(false);
+ const [openWatchSessionId, setOpenWatchSessionId] = useState(null);
+ const [watchDialogOpen, setWatchDialogOpen] = useState(false);
+ const [watchUrl, setWatchUrl] = useState('');
+ const [watchError, setWatchError] = useState(null);
+ const [watchCreating, setWatchCreating] = useState(false);
const [pollError, setPollError] = useState(null);
const [replyTo, setReplyTo] = useState(null);
const [forwardTarget, setForwardTarget] = useState(null);
@@ -472,6 +479,15 @@ export function ConversationPage() {
return () => window.removeEventListener('chatapp:open-whiteboard', onOpen);
}, []);
+ useEffect(() => {
+ const onOpen = (e: Event) => {
+ const detail = (e as CustomEvent<{ id?: string }>).detail;
+ if (detail?.id) setOpenWatchSessionId(detail.id);
+ };
+ window.addEventListener('chatapp:open-watch-together', onOpen);
+ return () => window.removeEventListener('chatapp:open-watch-together', onOpen);
+ }, []);
+
useEffect(() => {
if (id && messages.length > 0) markRead(id);
}, [id, messages.length, markRead]);
@@ -622,6 +638,31 @@ export function ConversationPage() {
}
}, [id, creatingWhiteboard, send, replyTo?.id]);
+ const handleStartWatchTogether = useCallback(async () => {
+ if (!id) return;
+ const videoId = parseYouTubeUrl(watchUrl);
+ if (!videoId) {
+ setWatchError('Ungültige YouTube-URL.');
+ return;
+ }
+ setWatchCreating(true);
+ setWatchError(null);
+ try {
+ const ws = await createWatchSession(supabase, { conversationId: id, videoId });
+ const payload = createWatchTogetherPayload(ws.id);
+ await send(payload, [], replyTo?.id ?? null);
+ setReplyTo(null);
+ setStickToBottom(true);
+ setWatchDialogOpen(false);
+ setWatchUrl('');
+ setOpenWatchSessionId(ws.id);
+ } catch (err: unknown) {
+ setWatchError(err instanceof Error ? err.message : 'Konnte Watch-Together nicht starten');
+ } finally {
+ setWatchCreating(false);
+ }
+ }, [id, watchUrl, send, replyTo?.id]);
+
async function ingestFiles(files: File[]) {
const compressed = await compressImages(files);
const next: File[] = [];
@@ -1025,6 +1066,15 @@ export function ConversationPage() {
>
+
);
}
@@ -1520,3 +1623,13 @@ function WhiteboardIcon(props: React.SVGProps) {
);
}
+
+function PlayBoxIcon(props: React.SVGProps) {
+ return (
+
+ );
+}