ui fixes yooooooo

This commit is contained in:
2025-11-01 05:47:15 +05:30
parent 0ad067efb2
commit bb2dfbbe80
12 changed files with 479 additions and 302 deletions

View File

@@ -104,16 +104,17 @@ const Home = () => {
const currentTheme = VSCODE_THEMES[currentThemeIndex]; const currentTheme = VSCODE_THEMES[currentThemeIndex];
return ( return (
<div className="relative min-h-screen flex items-center justify-center bg-background dark:bg-background ui-font"> <div className="relative min-h-dvh flex flex-col items-center justify-between bg-background dark:bg-background ui-font">
<main className="flex-grow flex items-center justify-center">
<Card className="relative z-10 px-6 md:px-12 py-12 md:py-24 backdrop-blur-sm shadow-lg bg-card/0 bg-opacity-0 dark:bg-card/70 border border-border dark:border-border flex flex-col items-center"> <Card className="relative z-10 px-6 md:px-12 py-12 md:py-24 backdrop-blur-sm shadow-lg bg-card/0 bg-opacity-0 dark:bg-card/70 border border-border dark:border-border flex flex-col items-center">
<div className="flex flex-col items-center"> <div className="flex flex-col items-center">
<h1 className="text-6xl md:text-8xl translate-x-1.5 font-bold text-foreground mb-4"> <h1 className="text-6xl md:text-8xl translate-x-1.5 font-bold text-foreground mb-6 md:mb-8">
Osborne Osborne
</h1> </h1>
</div> </div>
{/* Theme Switcher - Pill Button */} {/* Theme Switcher - Pill Button */}
<div className="mb-12"> <div className="mb-6 md:mb-8">
<button <button
onClick={nextTheme} onClick={nextTheme}
className="px-4 min-w-36 py-2 bg-muted hover:bg-muted/80 rounded-full text-sm font-medium text-foreground transition-colors border border-border/50 hover:border-border" className="px-4 min-w-36 py-2 bg-muted hover:bg-muted/80 rounded-full text-sm font-medium text-foreground transition-colors border border-border/50 hover:border-border"
@@ -165,6 +166,7 @@ const Home = () => {
</div> </div>
</CardContent> </CardContent>
</Card> </Card>
</main>
<LegalFooter <LegalFooter
onDisclaimerOpen={() => setIsDisclaimerOpen(true)} onDisclaimerOpen={() => setIsDisclaimerOpen(true)}
onDMCAOpen={() => setIsDMCAOpen(true)} onDMCAOpen={() => setIsDMCAOpen(true)}

View File

@@ -11,13 +11,16 @@ import {
import { useRouter, useSearchParams } from "next/navigation"; import { useRouter, useSearchParams } from "next/navigation";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { import {
HoverCard, Card,
HoverCardContent, CardContent,
HoverCardTrigger, CardHeader,
} from "@/components/ui/hover-card"; CardTitle,
CardFooter,
} from "@/components/ui/card";
import { import {
WifiOff, WifiOff,
RefreshCw, RefreshCw,
TriangleAlert,
} from "lucide-react"; } from "lucide-react";
import { Skeleton } from "@/components/ui/skeleton"; import { Skeleton } from "@/components/ui/skeleton";
import { CommentsPanel } from "@/components/RightPanel"; import { CommentsPanel } from "@/components/RightPanel";
@@ -35,6 +38,7 @@ import dotenv from "dotenv";
import { JetBrains_Mono } from "next/font/google"; import { JetBrains_Mono } from "next/font/google";
import { ThemeProvider } from "next-themes"; import { ThemeProvider } from "next-themes";
import { ContentWarningModal } from "@/components/ContentWarningModal"; import { ContentWarningModal } from "@/components/ContentWarningModal";
import { BetterHoverCard, HoverCardProvider } from "@/components/ui/BetterHoverCard";
dotenv.config(); dotenv.config();
@@ -210,7 +214,7 @@ const Room = () => {
const [error, setError] = useState(""); const [error, setError] = useState("");
const [isModalOpen, setIsModalOpen] = useState(false); const [isModalOpen, setIsModalOpen] = useState(false);
const [isPurgeModalOpen, setIsPurgeModalOpen] = useState(false); const [isPurgeModalOpen, setIsPurgeModalOpen] = useState(false);
const [showDisconnectToast, setShowDisconnectToast] = useState(false); const [showReconnectOverlay, setShowReconnectOverlay] = useState(false);
const [currentThemeId, setCurrentThemeId] = useState("one-dark"); const [currentThemeId, setCurrentThemeId] = useState("one-dark");
const [selectedLineStart, setSelectedLineStart] = useState<number>(); const [selectedLineStart, setSelectedLineStart] = useState<number>();
const [selectedLineEnd, setSelectedLineEnd] = useState<number>(); const [selectedLineEnd, setSelectedLineEnd] = useState<number>();
@@ -223,6 +227,8 @@ const Room = () => {
const [rightPanelForced, setRightPanelForced] = useState(false); const [rightPanelForced, setRightPanelForced] = useState(false);
const [popupMessage, setPopupMessage] = useState<{text: string; type?: 'default' | 'warning'} | null>(null); const [popupMessage, setPopupMessage] = useState<{text: string; type?: 'default' | 'warning'} | null>(null);
const [isMobile, setIsMobile] = useState(false); const [isMobile, setIsMobile] = useState(false);
const [fileSizeError, setFileSizeError] = useState<string | null>(null);
const [purgeError, setPurgeError] = useState<string | null>(null);
// Detect mobile screen size // Detect mobile screen size
useEffect(() => { useEffect(() => {
@@ -380,25 +386,19 @@ const Room = () => {
} }
}, [currentThemeId]); }, [currentThemeId]);
// Show disconnect toast only if still disconnected after a delay // Show reconnect overlay only if still disconnected after a delay
useEffect(() => { useEffect(() => {
let showTimer: NodeJS.Timeout | null = null; let showTimer: NodeJS.Timeout | null = null;
let hideTimer: NodeJS.Timeout | null = null;
if (status === "Disconnected") { if (status === "Disconnected") {
// Wait 800ms before showing toast // Wait 800ms before showing overlay
showTimer = setTimeout(() => { showTimer = setTimeout(() => {
setShowDisconnectToast(true); setShowReconnectOverlay(true);
// Auto-hide after 10 seconds
hideTimer = setTimeout(() => {
setShowDisconnectToast(false);
}, 10000);
}, 800); }, 800);
} else { } else {
setShowDisconnectToast(false); setShowReconnectOverlay(false);
} }
return () => { return () => {
if (showTimer) clearTimeout(showTimer); if (showTimer) clearTimeout(showTimer);
if (hideTimer) clearTimeout(hideTimer);
}; };
}, [status]); }, [status]);
@@ -707,7 +707,7 @@ const Room = () => {
router.push("/"); router.push("/");
} catch (error) { } catch (error) {
console.error("Error purging room:", error); console.error("Error purging room:", error);
showPopup("Failed to purge room", "warning"); setPurgeError("Failed to purge room");
} }
setIsPurgeModalOpen(false); setIsPurgeModalOpen(false);
@@ -725,7 +725,7 @@ const Room = () => {
// Check file size limit // Check file size limit
if (file.size > maxFileSize) { if (file.size > maxFileSize) {
const fileSizeInMB = (file.size / (1024 * 1024)).toFixed(2); const fileSizeInMB = (file.size / (1024 * 1024)).toFixed(2);
showPopup(`File "${file.name}" (${fileSizeInMB}MB) exceeds 10MB limit`, 'warning'); setFileSizeError(`File "${file.name}" (${fileSizeInMB}MB) exceeds 10MB limit`);
continue; // Skip this file and continue with others continue; // Skip this file and continue with others
} }
@@ -795,6 +795,7 @@ const Room = () => {
} }
return ( return (
<HoverCardProvider>
<div className="relative min-h-screen bg-background dark:bg-background ui-font"> <div className="relative min-h-screen bg-background dark:bg-background ui-font">
<div <div
className="absolute inset-0 transition-all duration-300" className="absolute inset-0 transition-all duration-300"
@@ -810,8 +811,8 @@ const Room = () => {
> >
<div className="flex flex-row items-center justify-between p-1 w-full"> <div className="flex flex-row items-center justify-between p-1 w-full">
<div className="flex gap-1"> <div className="flex gap-1">
<HoverCard> <BetterHoverCard
<HoverCardTrigger> trigger={
<Button <Button
variant="default" variant="default"
className="text-foreground bg-secondary px-2 py-0 h-5 rounded-sm text-xs btn-micro" className="text-foreground bg-secondary px-2 py-0 h-5 rounded-sm text-xs btn-micro"
@@ -822,13 +823,13 @@ const Room = () => {
> >
share share
</Button> </Button>
</HoverCardTrigger> }
<HoverCardContent className="py-1 px-2 w-auto text-popover-foreground bg-popover text-xs border-foreground"> contentClassName="py-1 px-2 w-auto text-popover-foreground bg-popover text-xs border-foreground"
copy link to this page >
</HoverCardContent> copy link to clipboard
</HoverCard> </BetterHoverCard>
<HoverCard> <BetterHoverCard
<HoverCardTrigger> trigger={
<Button <Button
className="bg-destructive px-2 py-0 h-5 text-xs rounded-sm hover:bg-destructive/80 btn-micro" className="bg-destructive px-2 py-0 h-5 text-xs rounded-sm hover:bg-destructive/80 btn-micro"
variant="destructive" variant="destructive"
@@ -836,13 +837,13 @@ const Room = () => {
> >
purge purge
</Button> </Button>
</HoverCardTrigger> }
<HoverCardContent className="py-1 px-2 w-auto text-popover-foreground bg-popover text-xs border-foreground"> contentClassName="py-1 px-2 w-auto text-popover-foreground bg-popover text-xs border-foreground"
permanently delete this room and all its contents >
</HoverCardContent> permanently delete this room
</HoverCard> </BetterHoverCard>
<HoverCard> <BetterHoverCard
<HoverCardTrigger> trigger={
<Button <Button
className="bg-destructive px-2 py-0 h-5 text-xs rounded-sm hover:bg-destructive/80 btn-micro" className="bg-destructive px-2 py-0 h-5 text-xs rounded-sm hover:bg-destructive/80 btn-micro"
variant="destructive" variant="destructive"
@@ -850,15 +851,15 @@ const Room = () => {
> >
exit exit
</Button> </Button>
</HoverCardTrigger> }
<HoverCardContent className="py-1 px-2 w-auto text-popover-foreground bg-popover text-xs border-foreground"> contentClassName="py-1 px-2 w-auto text-popover-foreground bg-popover text-xs border-foreground"
>
return to home return to home
</HoverCardContent> </BetterHoverCard>
</HoverCard>
</div> </div>
<div className="flex gap-1"> <div className="flex gap-1">
<HoverCard> <BetterHoverCard
<HoverCardTrigger> trigger={
<Button <Button
className="bg-chart-2 px-2 py-0 h-5 text-xs rounded-sm hover:bg-chart-2/80 btn-micro" className="bg-chart-2 px-2 py-0 h-5 text-xs rounded-sm hover:bg-chart-2/80 btn-micro"
onClick={() => { onClick={() => {
@@ -868,13 +869,13 @@ const Room = () => {
> >
upload upload
</Button> </Button>
</HoverCardTrigger> }
<HoverCardContent className="py-1 px-2 w-auto text-xs border-foreground"> contentClassName="py-1 px-2 w-auto text-xs border-foreground"
>
upload files upload files
</HoverCardContent> </BetterHoverCard>
</HoverCard> <BetterHoverCard
<HoverCard> trigger={
<HoverCardTrigger>
<Button <Button
className="bg-chart-4 px-2 py-0 h-5 text-xs rounded-sm hover:bg-chart-4/80 btn-micro" className="bg-chart-4 px-2 py-0 h-5 text-xs rounded-sm hover:bg-chart-4/80 btn-micro"
onClick={() => { onClick={() => {
@@ -886,41 +887,41 @@ const Room = () => {
> >
theme theme
</Button> </Button>
</HoverCardTrigger> }
<HoverCardContent className="py-1 px-2 w-auto text-xs border-foreground"> contentClassName="py-1 px-2 w-auto text-xs border-foreground"
{getThemeById(currentThemeId)?.name || "Switch theme"} >
</HoverCardContent> {`switch to ${getThemeById(getNextTheme(currentThemeId)?.id)?.name}`}
</HoverCard> </BetterHoverCard>
{/* Panel Controls for mobile and when panels are hidden due to width */} {/* Panel Controls for mobile and when panels are hidden due to width */}
{(isMobile || !showSidePanels) && ( {(isMobile || !showSidePanels) && (
<> <>
<HoverCard> <BetterHoverCard
<HoverCardTrigger> trigger={
<Button <Button
className="bg-chart-1 px-2 py-0 h-5 text-xs rounded-sm hover:bg-chart-1/80 btn-micro" className="bg-chart-1 px-2 py-0 h-5 text-xs rounded-sm hover:bg-chart-1/80 btn-micro"
onClick={() => setLeftPanelForced(!leftPanelForced)} onClick={() => setLeftPanelForced(!leftPanelForced)}
> >
media media
</Button> </Button>
</HoverCardTrigger> }
<HoverCardContent className="py-1 px-2 w-auto text-xs border-foreground z-[999]"> contentClassName="py-1 px-2 w-auto text-xs border-foreground z-[999]"
toggle users & media panel >
</HoverCardContent> show media
</HoverCard> </BetterHoverCard>
<HoverCard> <BetterHoverCard
<HoverCardTrigger> trigger={
<Button <Button
className="bg-chart-3 px-2 py-0 h-5 text-xs rounded-sm hover:bg-chart-3/80 btn-micro" className="bg-chart-3 px-2 py-0 h-5 text-xs rounded-sm hover:bg-chart-3/80 btn-micro"
onClick={() => setRightPanelForced(!rightPanelForced)} onClick={() => setRightPanelForced(!rightPanelForced)}
> >
notes notes
</Button> </Button>
</HoverCardTrigger> }
<HoverCardContent className="py-1 px-2 w-auto text-xs border-foreground"> contentClassName="py-1 px-2 w-auto text-xs border-foreground"
toggle comments panel >
</HoverCardContent> show comments
</HoverCard> </BetterHoverCard>
</> </>
)} )}
</div> </div>
@@ -935,8 +936,8 @@ const Room = () => {
<textarea <textarea
value={content} value={content}
onChange={(e) => handleContentChange(e.target.value)} onChange={(e) => handleContentChange(e.target.value)}
className="flex-grow w-full p-3 bg-background text-foreground border border-border rounded resize-none font-mono text-sm focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary" className="flex-grow w-full p-3 bg-background text-foreground border border-border rounded resize-none font-mono text-sm focus:outline-none focus:border-primary"
placeholder="Start typing your code here..." placeholder="Start typing..."
style={{ fontFamily: 'JetBrains Mono, Consolas, Monaco, "Courier New", monospace' }} style={{ fontFamily: 'JetBrains Mono, Consolas, Monaco, "Courier New", monospace' }}
/> />
) : ( ) : (
@@ -983,7 +984,57 @@ const Room = () => {
currentUser={currentUser} currentUser={currentUser}
/> />
{/* Custom Popup */} {/* File Size Error Modal */}
{fileSizeError && (
<div className="fixed inset-0 bg-background/80 backdrop-blur-sm z-50 flex items-center justify-center p-4" onClick={() => setFileSizeError(null)}>
<Card className="max-w-md text-center animate-in fade-in slide-in-from-bottom-4 duration-300" onClick={(e) => e.stopPropagation()}>
<CardHeader>
<TriangleAlert className="mx-auto mb-2 text-warning" size={48} />
<CardTitle className="text-lg text-warning">
File Too Large
</CardTitle>
</CardHeader>
<CardContent className="space-y-4 text-sm">
<p className="text-muted-foreground">
{fileSizeError}
</p>
<Button
onClick={() => setFileSizeError(null)}
className="w-full"
>
OK
</Button>
</CardContent>
</Card>
</div>
)}
{/* Purge Error Modal */}
{purgeError && (
<div className="fixed inset-0 bg-background/80 backdrop-blur-sm z-50 flex items-center justify-center p-4" onClick={() => setPurgeError(null)}>
<Card className="max-w-md text-center animate-in fade-in slide-in-from-bottom-4 duration-300" onClick={(e) => e.stopPropagation()}>
<CardHeader>
<TriangleAlert className="mx-auto mb-2 text-destructive" size={48} />
<CardTitle className="text-lg text-destructive">
Error
</CardTitle>
</CardHeader>
<CardContent className="space-y-4 text-sm">
<p className="text-muted-foreground">
{purgeError}
</p>
<Button
onClick={() => setPurgeError(null)}
className="w-full"
>
OK
</Button>
</CardContent>
</Card>
</div>
)}
{/* Custom Popup for non-critical messages */}
{popupMessage && ( {popupMessage && (
<div className="fixed top-4 right-4 z-50"> <div className="fixed top-4 right-4 z-50">
<div className={`px-3 py-2 border rounded-lg shadow-lg animate-in fade-in slide-in-from-top-2 duration-200 ${ <div className={`px-3 py-2 border rounded-lg shadow-lg animate-in fade-in slide-in-from-top-2 duration-200 ${
@@ -1011,6 +1062,7 @@ const Room = () => {
<LeftPanel <LeftPanel
isVisible={isMobile ? leftPanelForced : showLeftPanel} isVisible={isMobile ? leftPanelForced : showLeftPanel}
users={users} users={users}
currentUser={currentUser}
mediaFiles={mediaFiles} mediaFiles={mediaFiles}
onFileUpload={handleFileUpload} onFileUpload={handleFileUpload}
onFileDelete={handleFileDelete} onFileDelete={handleFileDelete}
@@ -1019,26 +1071,24 @@ const Room = () => {
{/* Purge Confirmation Modal */} {/* Purge Confirmation Modal */}
{isPurgeModalOpen && ( {isPurgeModalOpen && (
<div className="fixed inset-0 z-50 flex items-center justify-center"> <div className="fixed inset-0 bg-background/80 backdrop-blur-sm z-50 flex items-center justify-center p-4" onClick={() => setIsPurgeModalOpen(false)}>
{/* Blurred overlay */} <Card className="max-w-md animate-in fade-in slide-in-from-bottom-4 duration-300" onClick={(e) => e.stopPropagation()}>
<div <CardHeader>
className="absolute inset-0 bg-background/60 backdrop-blur-sm transition-all duration-300" <CardTitle>Purge Room</CardTitle>
onClick={() => setIsPurgeModalOpen(false)} </CardHeader>
/> <CardContent>
{/* Modal */} <p className="text-sm text-muted-foreground mb-4">
<div className="relative bg-card border border-border rounded-lg shadow-lg p-6 max-w-md w-full mx-4 animate-in fade-in slide-in-from-bottom-4 duration-300">
<h2 className="text-lg font-semibold text-foreground mb-4">Purge Room</h2>
<p className="text-sm text-muted-foreground mb-6">
Are you sure you want to permanently delete this room and all its contents? Are you sure you want to permanently delete this room and all its contents?
This action cannot be undone and will remove: This action cannot be undone and will remove:
</p> </p>
<ul className="text-sm text-muted-foreground mb-6 list-disc list-inside space-y-1"> <ul className="text-sm text-muted-foreground mb-4 list-disc list-inside space-y-1">
<li>All code content</li> <li>All code content</li>
<li>All comments</li> <li>All comments</li>
<li>All uploaded files</li> <li>All uploaded files</li>
<li>Room history</li> <li>Room history</li>
</ul> </ul>
<div className="flex gap-3 justify-end"> </CardContent>
<CardFooter className="flex gap-3 justify-end">
<Button <Button
variant="outline" variant="outline"
onClick={() => setIsPurgeModalOpen(false)} onClick={() => setIsPurgeModalOpen(false)}
@@ -1053,42 +1103,35 @@ const Room = () => {
> >
Purge Room Purge Room
</Button> </Button>
</div> </CardFooter>
</div> </Card>
</div> </div>
)} )}
{/* Comments Panel */} {/* Reconnect Overlay */}
{showDisconnectToast && ( {showReconnectOverlay && (
<div className="fixed inset-0 z-50 flex items-center justify-center pointer-events-none"> <div className="fixed inset-0 bg-background/80 backdrop-blur-sm z-50 flex items-center justify-center p-4">
{/* Blurred overlay */} <Card className="max-w-md text-center animate-in fade-in slide-in-from-bottom-4 duration-300">
<div className="absolute inset-0 bg-background/60 backdrop-blur-sm pointer-events-auto transition-all duration-300" /> <CardHeader>
{/* Toast */} <WifiOff className="mx-auto mb-2 text-destructive" size={48} />
<div <CardTitle className="text-lg text-destructive">
className="relative pointer-events-auto flex items-center space-x-2 px-4 py-3 rounded-lg shadow-lg border animate-in fade-in duration-300" Connection Lost
style={{ </CardTitle>
background: "var(--popover, var(--card, #fff))", </CardHeader>
color: "var(--popover-foreground, var(--foreground, #222))", <CardContent className="space-y-4 text-sm">
borderColor: "var(--border, #e5e7eb)", <p className="text-muted-foreground">
borderWidth: 1, The connection to the server was lost. Please check your
borderStyle: "solid", internet connection and try to reconnect.
fontWeight: 500, </p>
width: "auto", <Button
minWidth: undefined,
maxWidth: undefined,
}}
>
<WifiOff size={18} className="text-destructive" />
<span className="text-sm font-medium">Connection Lost</span>
<button
onClick={() => window.location.reload()} onClick={() => window.location.reload()}
className="ml-2 bg-primary/10 hover:bg-primary/20 text-primary rounded p-1 transition-colors" className="w-full"
title="Refresh to reconnect"
style={{ display: "flex", alignItems: "center" }}
> >
<RefreshCw size={15} /> <RefreshCw className="mr-2 h-4 w-4" />
</button> Reconnect
</div> </Button>
</CardContent>
</Card>
</div> </div>
)} )}
@@ -1098,6 +1141,7 @@ const Room = () => {
{/* Content Warning Modal */} {/* Content Warning Modal */}
<ContentWarningModal /> <ContentWarningModal />
</div> </div>
</HoverCardProvider>
); );
}; };

View File

@@ -0,0 +1,20 @@
import React from 'react';
import { cn } from '@/lib/utils';
interface AnimatedAvatarProps {
className?: string;
}
export const AnimatedAvatar: React.FC<AnimatedAvatarProps> = ({ className }) => {
return (
<div
className={cn(
'w-8 h-8 rounded-full',
'bg-gradient-to-r from-purple-400 via-pink-500 to-red-500',
'bg-[length:200%_200%]',
'animate-gradient-flow',
className
)}
/>
);
};

View File

@@ -25,7 +25,7 @@ export const ContentWarningModal = () => {
return ( return (
<div className="fixed inset-0 bg-background/80 backdrop-blur-sm z-50 flex items-center justify-center p-4"> <div className="fixed inset-0 bg-background/80 backdrop-blur-sm z-50 flex items-center justify-center p-4">
<Card className="max-w-md"> <Card className="max-w-md animate-in fade-in slide-in-from-bottom-4 duration-300">
<CardHeader> <CardHeader>
<TriangleAlert className="mx-auto mb-2 text-yellow-600 dark:text-yellow-400" size={48} /> <TriangleAlert className="mx-auto mb-2 text-yellow-600 dark:text-yellow-400" size={48} />
<CardTitle className="text-center text-lg text-yellow-600 dark:text-yellow-400">Content Disclaimer</CardTitle> <CardTitle className="text-center text-lg text-yellow-600 dark:text-yellow-400">Content Disclaimer</CardTitle>

View File

@@ -12,8 +12,8 @@ export const DMCAModalComponent = ({ isOpen, onClose }: DMCAModalProps) => {
if (!isOpen) return null; if (!isOpen) return null;
return ( return (
<div className="fixed inset-0 bg-background/80 backdrop-blur-sm z-[9999] flex items-center justify-center p-4 overflow-auto"> <div className="fixed inset-0 bg-background/80 backdrop-blur-sm z-[9999] flex items-center justify-center p-4 overflow-auto" onClick={onClose}>
<Card className="max-w-4xl w-full max-h-[90vh] flex flex-col mx-auto my-auto"> <Card className="max-w-4xl w-full max-h-[90vh] flex flex-col mx-auto my-auto animate-in fade-in slide-in-from-bottom-4 duration-300" onClick={(e) => e.stopPropagation()}>
<CardHeader className="flex-shrink-0 sticky top-0 bg-card z-10 border-b"> <CardHeader className="flex-shrink-0 sticky top-0 bg-card z-10 border-b">
<CardTitle className="flex items-center justify-between"> <CardTitle className="flex items-center justify-between">
DMCA Copyright Policy & Takedown Notice DMCA Copyright Policy & Takedown Notice

View File

@@ -12,8 +12,8 @@ export const DisclaimerModalComponent = ({ isOpen, onClose }: DisclaimerModalPro
if (!isOpen) return null; if (!isOpen) return null;
return ( return (
<div className="fixed inset-0 bg-background/80 backdrop-blur-sm z-[9999] flex items-center justify-center p-4 overflow-auto"> <div className="fixed inset-0 bg-background/80 backdrop-blur-sm z-[9999] flex items-center justify-center p-4 overflow-auto" onClick={onClose}>
<Card className="max-w-4xl w-full max-h-[90vh] flex flex-col mx-auto my-auto"> <Card className="max-w-4xl w-full max-h-[90vh] flex flex-col mx-auto my-auto animate-in fade-in slide-in-from-bottom-4 duration-300" onClick={(e) => e.stopPropagation()}>
<CardHeader className="flex-shrink-0 sticky top-0 bg-card z-10 border-b"> <CardHeader className="flex-shrink-0 sticky top-0 bg-card z-10 border-b">
<CardTitle className="flex items-center justify-between"> <CardTitle className="flex items-center justify-between">
Legal Terms & Disclaimers Legal Terms & Disclaimers

View File

@@ -9,7 +9,7 @@ export const LegalFooter = ({ onDisclaimerOpen, onDMCAOpen }: LegalFooterProps)
return ( return (
<> <>
{/* Legal Notice Footer */} {/* Legal Notice Footer */}
<div className="fixed bottom-0 left-0 right-0 bg-background/90 backdrop-blur-sm border-t border-border p-4 z-50"> <div className="w-full bg-background/90 backdrop-blur-sm border-t border-border p-4">
<div className="max-w-4xl mx-auto text-center text-xs text-muted-foreground"> <div className="max-w-4xl mx-auto text-center text-xs text-muted-foreground">
<p className="mb-2"> <p className="mb-2">
By using this service, you agree to our Terms of Service and acknowledge our disclaimers. By using this service, you agree to our Terms of Service and acknowledge our disclaimers.

View File

@@ -3,6 +3,7 @@ import { Card, CardContent } from '@/components/ui/card';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge'; import { Badge } from '@/components/ui/badge';
import { MediaModal } from '@/components/MediaModal'; import { MediaModal } from '@/components/MediaModal';
import { AnimatedAvatar } from '@/components/AnimatedAvatar';
import { import {
Users, Users,
Circle, Circle,
@@ -41,6 +42,7 @@ interface LeftPanelProps {
isVisible: boolean; isVisible: boolean;
className?: string; className?: string;
users?: ActiveUser[]; users?: ActiveUser[];
currentUser?: ActiveUser | null;
mediaFiles?: MediaFile[]; mediaFiles?: MediaFile[];
onFileUpload?: (files: FileList) => void; onFileUpload?: (files: FileList) => void;
onFileDelete?: (fileId: string) => void; onFileDelete?: (fileId: string) => void;
@@ -50,6 +52,7 @@ interface LeftPanelProps {
export const LeftPanel: React.FC<LeftPanelProps> = ({ export const LeftPanel: React.FC<LeftPanelProps> = ({
isVisible, isVisible,
users = [], users = [],
currentUser,
mediaFiles = [], mediaFiles = [],
onFileDelete, onFileDelete,
onModalStateChange onModalStateChange
@@ -280,115 +283,8 @@ export const LeftPanel: React.FC<LeftPanelProps> = ({
isVisible ? 'transform-none' : '-translate-x-full' isVisible ? 'transform-none' : '-translate-x-full'
}`} }`}
> >
{/* Users Panel */}
<div className="h-1/2 flex flex-col border-b border-border">
<div className="flex items-center justify-center py-2 border-b border-border/50 bg-muted/20">
<h3 className="text-sm font-medium text-foreground flex items-center gap-2">
<Users size={16} />
Users
</h3>
</div>
<div
ref={usersScrollRef}
className={`flex-1 overflow-y-auto hide-scrollbar scroll-shadow p-2 space-y-2 ${
usersScrollState.top ? 'scroll-top' : ''
} ${usersScrollState.bottom ? 'scroll-bottom' : ''}`}
>
{activeUsers.length === 0 ? (
<div className="text-center text-muted-foreground py-4">
<Users size={20} className="mx-auto mb-2 opacity-50" />
<p className="text-xs">No active users</p>
</div>
) : (
activeUsers.map((user) => {
const { status, color } = getStatusIndicator(user);
return (
<Card key={user.id} className="bg-background border-border">
<CardContent className="p-3">
<div className="flex items-center justify-between mb-2">
<div className="flex items-center space-x-2">
<div className="relative">
<div
className="w-8 h-8 rounded-full flex items-center justify-center text-white text-sm font-medium"
style={{ backgroundColor: user.color }}
>
{user.name.charAt(0).toUpperCase()}
</div>
<Circle
size={8}
className="absolute -bottom-0.5 -right-0.5 border-2 border-background rounded-full"
style={{ color, fill: color }}
/>
</div>
<div>
<p className="text-sm font-medium text-foreground">
{user.name}
</p>
<p className="text-xs text-muted-foreground">
{formatLastSeen(user.lastSeen)}
</p>
</div>
</div>
<Badge
variant="outline"
className="text-xs"
style={{ borderColor: user.color, color: user.color }}
>
{status}
</Badge>
</div>
{user.currentLine && (
<div className="flex items-center justify-between text-xs">
<span className="text-muted-foreground">
Line {user.currentLine}
</span>
{user.isTyping && (
<div className="flex space-x-1">
<div
className="w-1 h-1 rounded-full animate-pulse"
style={{ backgroundColor: user.color }}
/>
<div
className="w-1 h-1 rounded-full animate-pulse"
style={{
backgroundColor: user.color,
animationDelay: '0.1s'
}}
/>
<div
className="w-1 h-1 rounded-full animate-pulse"
style={{
backgroundColor: user.color,
animationDelay: '0.2s'
}}
/>
</div>
)}
</div>
)}
</CardContent>
</Card>
);
})
)}
</div>
<div className="px-4 py-3 border-t border-border bg-muted/20">
<div className="flex items-center justify-between text-xs text-muted-foreground">
<span>
{activeUsers.filter(u => getStatusIndicator(u).status === 'online').length} online
</span>
<span>
{activeUsers.filter(u => u.isTyping).length} typing
</span>
</div>
</div>
</div>
{/* Media Panel */} {/* Media Panel */}
<div className="h-1/2 flex flex-col"> <div className="h-[65%] flex flex-col border-b border-border">
<div className="flex items-center justify-center py-2 border-b border-border/50 bg-muted/20"> <div className="flex items-center justify-center py-2 border-b border-border/50 bg-muted/20">
<h3 className="text-sm font-medium text-foreground flex items-center gap-2"> <h3 className="text-sm font-medium text-foreground flex items-center gap-2">
<Upload size={16} /> <Upload size={16} />
@@ -543,6 +439,118 @@ export const LeftPanel: React.FC<LeftPanelProps> = ({
</div> </div>
</div> </div>
{/* Users Panel */}
<div className="h-[35%] flex flex-col">
<div className="flex items-center justify-center py-2 border-b border-border/50 bg-muted/20">
<h3 className="text-sm font-medium text-foreground flex items-center gap-2">
<Users size={16} />
Users
</h3>
</div>
<div
ref={usersScrollRef}
className={`flex-1 overflow-y-auto hide-scrollbar scroll-shadow p-2 space-y-2 ${
usersScrollState.top ? 'scroll-top' : ''
} ${usersScrollState.bottom ? 'scroll-bottom' : ''}`}
>
{activeUsers.length === 0 ? (
<div className="text-center text-muted-foreground py-4">
<Users size={20} className="mx-auto mb-2 opacity-50" />
<p className="text-xs">No active users</p>
</div>
) : (
activeUsers.map((user) => {
const { status, color } = getStatusIndicator(user);
const isCurrentUser = currentUser && user.id === currentUser.id;
return (
<Card key={user.id} className="bg-background border-border">
<CardContent className="p-3">
<div className="flex items-center justify-between mb-2">
<div className="flex items-center space-x-2">
<div className="relative">
{isCurrentUser ? (
<AnimatedAvatar />
) : (
<div
className="w-8 h-8 rounded-full flex items-center justify-center text-white text-sm font-medium"
style={{ backgroundColor: user.color }}
>
{user.name.charAt(0).toUpperCase()}
</div>
)}
<Circle
size={8}
className="absolute -bottom-0.5 -right-0.5 border-2 border-background rounded-full"
style={{ color, fill: color }}
/>
</div>
<div>
<p className="text-sm font-medium text-foreground">
{isCurrentUser ? 'You' : user.name}
</p>
<p className="text-xs text-muted-foreground">
{formatLastSeen(user.lastSeen)}
</p>
</div>
</div>
<Badge
variant="outline"
className="text-xs"
style={{ borderColor: user.color, color: user.color }}
>
{status}
</Badge>
</div>
{user.currentLine && (
<div className="flex items-center justify-between text-xs">
<span className="text-muted-foreground">
Line {user.currentLine}
</span>
{user.isTyping && (
<div className="flex space-x-1">
<div
className="w-1 h-1 rounded-full animate-pulse"
style={{ backgroundColor: user.color }}
/>
<div
className="w-1 h-1 rounded-full animate-pulse"
style={{
backgroundColor: user.color,
animationDelay: '0.1s'
}}
/>
<div
className="w-1 h-1 rounded-full animate-pulse"
style={{
backgroundColor: user.color,
animationDelay: '0.2s'
}}
/>
</div>
)}
</div>
)}
</CardContent>
</Card>
);
})
)}
</div>
<div className="px-4 py-3 border-t border-border bg-muted/20">
<div className="flex items-center justify-between text-xs text-muted-foreground">
<span>
{activeUsers.filter(u => getStatusIndicator(u).status === 'online').length} online
</span>
<span>
{activeUsers.filter(u => u.isTyping).length} typing
</span>
</div>
</div>
</div>
{/* Media Modal */} {/* Media Modal */}
<MediaModal <MediaModal
file={modalFile} file={modalFile}

View File

@@ -90,12 +90,12 @@ export const MediaModal: React.FC<MediaModalProps> = ({
return ( return (
<div <div
className="fixed inset-0 backdrop-blur-sm flex items-center justify-center z-50 p-4" className="fixed inset-0 bg-background/80 backdrop-blur-sm flex items-center justify-center z-50 p-4"
onClick={onClose} onClick={onClose}
onKeyDown={handleKeyDown} onKeyDown={handleKeyDown}
tabIndex={-1} tabIndex={-1}
> >
<Card className="relative max-w-[95vw] max-h-[95vh] w-auto h-auto bg-card border-border shadow-xl flex flex-col"> <Card className="relative max-w-[95vw] max-h-[95vh] w-auto h-auto bg-card border-border shadow-xl flex flex-col animate-in fade-in slide-in-from-bottom-4 duration-300" onClick={(e) => e.stopPropagation()}>
<div <div
className="relative flex flex-col h-full" className="relative flex flex-col h-full"
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}

View File

@@ -0,0 +1,95 @@
"use client";
import * as React from "react";
import { HoverCard, HoverCardTrigger, HoverCardContent } from "@/components/ui/hover-card";
import { cn } from "@/lib/utils";
interface CustomHoverCardProps {
trigger: React.ReactNode;
children: React.ReactNode;
holdDelay?: number;
contentClassName?: string;
}
// Context for managing mutually exclusive hover cards
const HoverCardContext = React.createContext<{
openId: string | null;
setOpenId: (id: string | null) => void;
}>({
openId: null,
setOpenId: () => {},
});
export const BetterHoverCard = ({ trigger, children, holdDelay = 600, contentClassName }: CustomHoverCardProps) => {
const [isOpen, setIsOpen] = React.useState(false);
const [isTouchHeld, setIsTouchHeld] = React.useState(false);
const timerRef = React.useRef<NodeJS.Timeout>();
const isTouchDevice = React.useMemo(() => typeof window !== 'undefined' && ('ontouchstart' in window || navigator.maxTouchPoints > 0), []);
const idRef = React.useRef(Math.random().toString(36).substr(2, 9));
const { openId, setOpenId } = React.useContext(HoverCardContext);
const handlePointerDown = (e: React.PointerEvent) => {
if (isTouchDevice && e.pointerType === 'touch') {
e.currentTarget.addEventListener('contextmenu', (e) => e.preventDefault(), { once: true });
timerRef.current = setTimeout(() => {
setIsOpen(true);
setIsTouchHeld(true);
setOpenId(idRef.current);
}, holdDelay);
}
};
const handlePointerUp = (e: React.PointerEvent) => {
if (isTouchDevice && e.pointerType === 'touch') {
if (timerRef.current) {
clearTimeout(timerRef.current);
}
}
};
const handleOpenChange = (open: boolean) => {
if (isTouchDevice && open && !isTouchHeld) {
return; // Ignore open on tap for touch devices
}
if (open) {
setOpenId(idRef.current);
} else if (openId === idRef.current) {
setOpenId(null);
}
setIsOpen(open);
if (!open) {
setIsTouchHeld(false);
}
};
// Sync isOpen with context
React.useEffect(() => {
setIsOpen(openId === idRef.current);
}, [openId]);
return (
<HoverCard open={isOpen} onOpenChange={handleOpenChange}>
<HoverCardTrigger asChild
onPointerDown={handlePointerDown}
onPointerUp={handlePointerUp}
onPointerLeave={handlePointerUp}
>
{trigger}
</HoverCardTrigger>
<HoverCardContent className={cn(contentClassName)}>
{children}
</HoverCardContent>
</HoverCard>
);
};
// Provider component to wrap the app or relevant section
export const HoverCardProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [openId, setOpenId] = React.useState<string | null>(null);
return (
<HoverCardContext.Provider value={{ openId, setOpenId }}>
{children}
</HoverCardContext.Provider>
);
};

View File

@@ -5,7 +5,9 @@ import * as HoverCardPrimitive from "@radix-ui/react-hover-card"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
const HoverCard = HoverCardPrimitive.Root const HoverCard = ({ openDelay = 200, ...props }: React.ComponentPropsWithoutRef<typeof HoverCardPrimitive.Root>) => (
<HoverCardPrimitive.Root openDelay={openDelay} {...props} />
);
const HoverCardTrigger = HoverCardPrimitive.Trigger const HoverCardTrigger = HoverCardPrimitive.Trigger

View File

@@ -87,6 +87,11 @@ const config = {
sm: "calc(var(--radius) - 4px)", sm: "calc(var(--radius) - 4px)",
}, },
keyframes: { keyframes: {
"gradient-flow": {
'0%': { backgroundPosition: '0% 50%' },
'50%': { backgroundPosition: '100% 50%' },
'100%': { backgroundPosition: '0% 50%' },
},
"accordion-down": { "accordion-down": {
from: { height: "0" }, from: { height: "0" },
to: { height: "var(--radix-accordion-content-height)" }, to: { height: "var(--radix-accordion-content-height)" },
@@ -97,6 +102,7 @@ const config = {
}, },
}, },
animation: { animation: {
"gradient-flow": "gradient-flow 3s ease-in-out infinite",
"accordion-down": "accordion-down 0.2s ease-out", "accordion-down": "accordion-down 0.2s ease-out",
"accordion-up": "accordion-up 0.2s ease-out", "accordion-up": "accordion-up 0.2s ease-out",
}, },