tahamajs/IE / Projects /front /src /components /async /AsyncVideoThread.tsx
tahamajs's picture
download
raw
5.34 kB
// src/components/async/AsyncVideoThread.tsx
import { useState, useRef } from "react";
import { VideoNoteRecorder } from "@/components/social/VideoNoteRecorder";
import { VideoNoteBubble } from "@/components/messages/VideoNoteBubble";
import { MessageCircle, Play, Clock, ChevronDown, Reply, Send, User } from "lucide-react";
interface VideoThreadMessage {
id: string;
type: "video_note" | "audio_note" | "text";
url?: string;
duration?: number;
body?: string;
author: string;
timestamp: string;
replies: { id: string; body: string; author: string; timestamp: string }[];
}
interface AsyncVideoThreadProps {
threadId: string;
title: string;
messages: VideoThreadMessage[];
onAddVideoNote: (blob: Blob, duration: number, mode: "video" | "audio") => void;
onAddTextReply: (threadId: string, body: string, parentMessageId?: string) => void;
}
export function AsyncVideoThread({ threadId, title, messages, onAddVideoNote, onAddTextReply }: AsyncVideoThreadProps) {
const [showRecorder, setShowRecorder] = useState(false);
const [expandedMessageId, setExpandedMessageId] = useState<string | null>(null);
const [replyInput, setReplyInput] = useState("");
const [replyingToId, setReplyingToId] = useState<string | null>(null);
return (
<div className="glass rounded-2xl border border-white/20 dark:border-white/10 overflow-hidden flex flex-col max-h-[80vh]">
{/* Header */}
<div className="px-5 py-4 border-b border-white/10 flex items-center justify-between bg-linear-to-r from-blue-500/5 to-purple-500/5">
<div>
<h3 className="font-bold flex items-center gap-2">
<Play className="w-5 h-5 text-blue-500" /> Async Video Thread
</h3>
<p className="text-xs text-gray-500 mt-0.5">{title}</p>
</div>
<button onClick={() => setShowRecorder(!showRecorder)} className="btn btn-primary btn-sm gap-1.5">
<Play className="w-3.5 h-3.5" /> Record Note
</button>
</div>
{/* Recorder */}
{showRecorder && (
<div className="p-4 border-b border-white/10">
<VideoNoteRecorder
onSend={(blob, duration, mode) => {
onAddVideoNote(blob, duration, mode);
setShowRecorder(false);
}}
onCancel={() => setShowRecorder(false)}
/>
</div>
)}
{/* Messages */}
<div className="flex-1 overflow-y-auto p-4 space-y-4">
{messages.map(msg => (
<div key={msg.id}>
<div className="space-y-2">
<div className="flex items-center gap-2 text-xs text-gray-400">
<User className="w-3 h-3" /> {msg.author}
<Clock className="w-3 h-3" /> {new Date(msg.timestamp).toLocaleString()}
</div>
{msg.type === "video_note" || msg.type === "audio_note" ? (
<VideoNoteBubble
url={msg.url!}
duration={msg.duration!}
mode={msg.type === "video_note" ? "video" : "audio"}
isOwn={false}
timestamp={msg.timestamp}
/>
) : (
<div className="bg-white/30 dark:bg-white/10 rounded-2xl px-4 py-2.5 text-sm">
{msg.body}
</div>
)}
{/* Replies */}
{msg.replies.length > 0 && (
<div className="ml-8 space-y-2">
{msg.replies.map(reply => (
<div key={reply.id} className="flex items-start gap-2 text-sm">
<span className="text-xs font-semibold text-gray-500 mt-0.5">{reply.author}:</span>
<span>{reply.body}</span>
</div>
))}
</div>
)}
{/* Reply button */}
<button
onClick={() => { setReplyingToId(replyingToId === msg.id ? null : msg.id); setReplyInput(""); }}
className="text-xs text-primary hover:underline flex items-center gap-1"
>
<Reply className="w-3 h-3" /> Reply
</button>
{/* Reply input */}
{replyingToId === msg.id && (
<div className="ml-4 flex gap-2 mt-2">
<input
type="text"
value={replyInput}
onChange={e => setReplyInput(e.target.value)}
placeholder="Write a reply..."
className="flex-1 text-xs px-3 py-1.5 border rounded-lg dark:bg-gray-700"
autoFocus
onKeyDown={e => { if (e.key === "Enter" && replyInput.trim()) { onAddTextReply(threadId, replyInput.trim(), msg.id); setReplyInput(""); setReplyingToId(null); } }}
/>
<button
onClick={() => { onAddTextReply(threadId, replyInput.trim(), msg.id); setReplyInput(""); setReplyingToId(null); }}
disabled={!replyInput.trim()}
className="btn btn-primary btn-xs"
>
<Send className="w-3 h-3" />
</button>
</div>
)}
</div>
</div>
))}
</div>
</div>
);
}

Xet Storage Details

Size:
5.34 kB
·
Xet hash:
f6267f931aeb468334fea9b19b05e5a3d4cbcae07eb7a7c7431963c174f7e5fa

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.