Restored to 'da5c38c23546b6ac8177dac9774fb40023d30283'

Replit-Restored-To: da5c38c235
This commit is contained in:
sebastjanartic 2025-08-04 20:37:56 +00:00
parent 5bbf9e6c1b
commit 76f430775e
9 changed files with 13 additions and 538 deletions

View File

@ -37,6 +37,3 @@ author = "agent"
task = "shell.exec"
args = "npm run dev"
waitForPort = 5000
[agent]
integrations = ["javascript_database==1.0.0", "javascript_log_in_with_replit==1.0.0"]

View File

@ -1,154 +0,0 @@
import { useState } from "react";
import { Copy, Check, Share2 } from "lucide-react";
import {
SiX,
SiFacebook,
SiLinkedin,
SiWhatsapp,
SiTelegram,
SiReddit
} from "react-icons/si";
import { Button } from "@/components/ui/button";
import { type Video } from "@shared/schema";
interface SocialShareProps {
video: Video;
className?: string;
}
export function SocialShare({ video, className = "" }: SocialShareProps) {
const [copied, setCopied] = useState(false);
const [shareOpen, setShareOpen] = useState(false);
// Generate shareable URL (in production, this would be your actual domain)
const shareUrl = `${window.location.origin}/?video=${video.id}`;
const shareText = `Check out this video: ${video.title}`;
const shareTextEncoded = encodeURIComponent(shareText);
const shareUrlEncoded = encodeURIComponent(shareUrl);
const handleCopyLink = async () => {
try {
await navigator.clipboard.writeText(shareUrl);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
} catch (err) {
console.error('Failed to copy link:', err);
}
};
const socialPlatforms = [
{
name: "X",
icon: SiX,
url: `https://twitter.com/intent/tweet?text=${shareTextEncoded}&url=${shareUrlEncoded}`,
color: "hover:bg-gray-900 hover:text-white",
},
{
name: "Facebook",
icon: SiFacebook,
url: `https://www.facebook.com/sharer/sharer.php?u=${shareUrlEncoded}`,
color: "hover:bg-blue-600 hover:text-white",
},
{
name: "LinkedIn",
icon: SiLinkedin,
url: `https://www.linkedin.com/sharing/share-offsite/?url=${shareUrlEncoded}`,
color: "hover:bg-blue-700 hover:text-white",
},
{
name: "WhatsApp",
icon: SiWhatsapp,
url: `https://wa.me/?text=${shareTextEncoded}%20${shareUrlEncoded}`,
color: "hover:bg-green-500 hover:text-white",
},
{
name: "Telegram",
icon: SiTelegram,
url: `https://t.me/share/url?url=${shareUrlEncoded}&text=${shareTextEncoded}`,
color: "hover:bg-blue-400 hover:text-white",
},
{
name: "Reddit",
icon: SiReddit,
url: `https://reddit.com/submit?url=${shareUrlEncoded}&title=${shareTextEncoded}`,
color: "hover:bg-orange-500 hover:text-white",
},
];
const handlePlatformShare = (platform: typeof socialPlatforms[0]) => {
window.open(platform.url, '_blank', 'width=600,height=400');
};
return (
<div className={`relative ${className}`}>
<Button
variant="ghost"
size="sm"
onClick={() => setShareOpen(!shareOpen)}
className="text-gray-300 hover:text-white transition-colors"
data-testid="button-share-toggle"
>
<Share2 className="w-4 h-4 mr-2" />
Share
</Button>
{shareOpen && (
<>
{/* Backdrop */}
<div
className="fixed inset-0 z-40"
onClick={() => setShareOpen(false)}
/>
{/* Share Menu */}
<div className="absolute bottom-full right-0 mb-2 bg-gray-800 rounded-lg p-4 shadow-xl z-50 min-w-64">
<h3 className="text-white font-semibold mb-3">Share this video</h3>
{/* Copy Link */}
<div className="mb-4">
<Button
variant="secondary"
size="sm"
onClick={handleCopyLink}
className="w-full justify-start"
data-testid="button-copy-link"
>
{copied ? (
<>
<Check className="w-4 h-4 mr-2 text-green-500" />
Copied!
</>
) : (
<>
<Copy className="w-4 h-4 mr-2" />
Copy Link
</>
)}
</Button>
</div>
{/* Social Platforms */}
<div className="grid grid-cols-3 gap-2">
{socialPlatforms.map((platform) => {
const IconComponent = platform.icon;
return (
<Button
key={platform.name}
variant="ghost"
size="sm"
onClick={() => handlePlatformShare(platform)}
className={`flex flex-col items-center p-3 h-auto ${platform.color} transition-colors`}
data-testid={`button-share-${platform.name.toLowerCase()}`}
>
<IconComponent className="w-5 h-5 mb-1" />
<span className="text-xs">{platform.name}</span>
</Button>
);
})}
</div>
</div>
</>
)}
</div>
);
}

View File

@ -1,6 +1,5 @@
import { Play, Share2 } from "lucide-react";
import { Play } from "lucide-react";
import { type Video } from "@shared/schema";
import { Button } from "@/components/ui/button";
interface VideoCardProps {
video: Video;
@ -41,30 +40,6 @@ function formatDate(date: Date | string): string {
}
export default function VideoCard({ video, onClick }: VideoCardProps) {
const handleShare = async (e: React.MouseEvent) => {
e.stopPropagation(); // Prevent video modal from opening
const shareUrl = `${window.location.origin}/?video=${video.id}`;
const shareData = {
title: video.title,
text: `Check out this video: ${video.title}`,
url: shareUrl,
};
try {
if (navigator.share) {
// Use native share API if available (mobile devices)
await navigator.share(shareData);
} else {
// Fallback: copy to clipboard
await navigator.clipboard.writeText(shareUrl);
alert('Video link copied to clipboard!');
}
} catch (err) {
console.error('Error sharing:', err);
}
};
return (
<div
className="group cursor-pointer"
@ -90,19 +65,6 @@ export default function VideoCard({ video, onClick }: VideoCardProps) {
{formatDuration(video.duration)}
</span>
</div>
{/* Share button */}
<div className="absolute top-3 right-3 opacity-0 group-hover:opacity-100 transition-opacity">
<Button
variant="secondary"
size="sm"
onClick={handleShare}
className="bg-black/80 hover:bg-black/90 text-white border-0 h-8 w-8 p-0"
data-testid={`button-share-${video.id}`}
>
<Share2 className="w-4 h-4" />
</Button>
</div>
</div>
<div className="space-y-2">

View File

@ -1,10 +1,9 @@
import { useEffect, useRef } from "react";
import { X, Share2, Copy, Check } from "lucide-react";
import { X } from "lucide-react";
import { type Video } from "@shared/schema";
import { Button } from "@/components/ui/button";
import { apiRequest } from "@/lib/queryClient";
import Hls from "hls.js";
import { SocialShare } from "@/components/social-share";
interface VideoModalProps {
video: Video | null;
@ -213,19 +212,16 @@ export default function VideoModal({ video, isOpen, onClose }: VideoModalProps)
>
{video.title}
</h3>
<div className="flex items-center justify-between">
<div className="flex items-center space-x-4 text-sm text-gray-300">
<span data-testid="text-modal-views">
{formatViews(video.views)}
</span>
<span data-testid="text-modal-date">
{formatDate(video.createdAt)}
</span>
<span data-testid="text-modal-duration">
{formatDuration(video.duration)}
</span>
</div>
<SocialShare video={video} />
<div className="flex items-center space-x-4 text-sm text-gray-300">
<span data-testid="text-modal-views">
{formatViews(video.views)}
</span>
<span data-testid="text-modal-date">
{formatDate(video.createdAt)}
</span>
<span data-testid="text-modal-duration">
{formatDuration(video.duration)}
</span>
</div>
{video.description && (
<p className="mt-3 text-gray-300 text-sm" data-testid="text-modal-description">

View File

@ -3,7 +3,6 @@ import { useQuery } from "@tanstack/react-query";
import { type Video } from "@shared/schema";
import SearchHeader from "@/components/search-header";
import VideoGrid from "@/components/video-grid";
import VideoModal from "@/components/video-modal";
interface VideosResponse {
videos: Video[];
@ -16,23 +15,6 @@ export default function Home() {
const [viewMode, setViewMode] = useState<"grid" | "list">("grid");
const [offset, setOffset] = useState(0);
const [allVideos, setAllVideos] = useState<Video[]>([]);
const [selectedVideo, setSelectedVideo] = useState<Video | null>(null);
const [isVideoModalOpen, setIsVideoModalOpen] = useState(false);
// Check for shared video in URL and open modal when videos are loaded
useEffect(() => {
const urlParams = new URLSearchParams(window.location.search);
const videoId = urlParams.get('video');
if (videoId && allVideos.length > 0) {
const sharedVideo = allVideos.find(v => v.id === videoId);
if (sharedVideo) {
setSelectedVideo(sharedVideo);
setIsVideoModalOpen(true);
// Clean up URL after opening modal
window.history.replaceState({}, '', window.location.pathname);
}
}
}, [allVideos]);
// Fetch videos
const { data: videosResponse, isLoading, refetch } = useQuery<VideosResponse>({
@ -86,11 +68,6 @@ export default function Home() {
refetch();
}, [searchQuery, offset, refetch]);
const handleCloseModal = () => {
setIsVideoModalOpen(false);
setSelectedVideo(null);
};
return (
<div className="min-h-screen bg-bunny-dark">
<SearchHeader
@ -109,15 +86,6 @@ export default function Home() {
/>
</main>
{/* Shared video modal - handles deep links */}
{selectedVideo && isVideoModalOpen && (
<VideoModal
video={selectedVideo}
isOpen={isVideoModalOpen}
onClose={handleCloseModal}
/>
)}
{/* Footer */}
<footer className="bg-bunny-gray/50 border-t border-gray-700 mt-16">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">

198
package-lock.json generated
View File

@ -40,7 +40,6 @@
"@radix-ui/react-toggle-group": "^1.1.3",
"@radix-ui/react-tooltip": "^1.2.0",
"@tanstack/react-query": "^5.60.5",
"@types/memoizee": "^0.4.12",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "^1.1.1",
@ -55,10 +54,8 @@
"hls.js": "^1.6.7",
"input-otp": "^1.4.2",
"lucide-react": "^0.453.0",
"memoizee": "^0.4.17",
"memorystore": "^1.6.7",
"next-themes": "^0.4.6",
"openid-client": "^6.6.2",
"passport": "^0.7.0",
"passport-local": "^1.0.0",
"react": "^18.3.1",
@ -3468,12 +3465,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/@types/memoizee": {
"version": "0.4.12",
"resolved": "https://registry.npmjs.org/@types/memoizee/-/memoizee-0.4.12.tgz",
"integrity": "sha512-EdtpwNYNhe3kZ+4TlXj/++pvBoU0KdrAICMzgI7vjWgu9sIvvUhu9XR8Ks4L6Wh3sxpZ22wkZR7yCLAqUjnZuQ==",
"license": "MIT"
},
"node_modules/@types/mime": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz",
@ -4121,19 +4112,6 @@
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
"license": "MIT"
},
"node_modules/d": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/d/-/d-1.0.2.tgz",
"integrity": "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==",
"license": "ISC",
"dependencies": {
"es5-ext": "^0.10.64",
"type": "^2.7.2"
},
"engines": {
"node": ">=0.12"
}
},
"node_modules/d3-array": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz",
@ -5032,58 +5010,6 @@
"node": ">= 0.4"
}
},
"node_modules/es5-ext": {
"version": "0.10.64",
"resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz",
"integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==",
"hasInstallScript": true,
"license": "ISC",
"dependencies": {
"es6-iterator": "^2.0.3",
"es6-symbol": "^3.1.3",
"esniff": "^2.0.1",
"next-tick": "^1.1.0"
},
"engines": {
"node": ">=0.10"
}
},
"node_modules/es6-iterator": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz",
"integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==",
"license": "MIT",
"dependencies": {
"d": "1",
"es5-ext": "^0.10.35",
"es6-symbol": "^3.1.1"
}
},
"node_modules/es6-symbol": {
"version": "3.1.4",
"resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.4.tgz",
"integrity": "sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==",
"license": "ISC",
"dependencies": {
"d": "^1.0.2",
"ext": "^1.7.0"
},
"engines": {
"node": ">=0.12"
}
},
"node_modules/es6-weak-map": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz",
"integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==",
"license": "ISC",
"dependencies": {
"d": "1",
"es5-ext": "^0.10.46",
"es6-iterator": "^2.0.3",
"es6-symbol": "^3.1.1"
}
},
"node_modules/esbuild": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.0.tgz",
@ -5154,21 +5080,6 @@
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
"license": "MIT"
},
"node_modules/esniff": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz",
"integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==",
"license": "ISC",
"dependencies": {
"d": "^1.0.1",
"es5-ext": "^0.10.62",
"event-emitter": "^0.3.5",
"type": "^2.7.2"
},
"engines": {
"node": ">=0.10"
}
},
"node_modules/etag": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
@ -5178,16 +5089,6 @@
"node": ">= 0.6"
}
},
"node_modules/event-emitter": {
"version": "0.3.5",
"resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz",
"integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==",
"license": "MIT",
"dependencies": {
"d": "1",
"es5-ext": "~0.10.14"
}
},
"node_modules/eventemitter3": {
"version": "4.0.7",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
@ -5304,15 +5205,6 @@
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
"license": "MIT"
},
"node_modules/ext": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz",
"integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==",
"license": "ISC",
"dependencies": {
"type": "^2.7.2"
}
},
"node_modules/fast-equals": {
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.2.2.tgz",
@ -5792,12 +5684,6 @@
"node": ">=0.12.0"
}
},
"node_modules/is-promise": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz",
"integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==",
"license": "MIT"
},
"node_modules/isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
@ -5828,15 +5714,6 @@
"jiti": "bin/jiti.js"
}
},
"node_modules/jose": {
"version": "6.0.12",
"resolved": "https://registry.npmjs.org/jose/-/jose-6.0.12.tgz",
"integrity": "sha512-T8xypXs8CpmiIi78k0E+Lk7T2zlK4zDyg+o1CZ4AkOHgDg98ogdP2BeZ61lTFKFyoEwJ9RgAgN+SdM3iPgNonQ==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/panva"
}
},
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@ -6163,15 +6040,6 @@
"yallist": "^3.0.2"
}
},
"node_modules/lru-queue": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz",
"integrity": "sha512-BpdYkt9EvGl8OfWHDQPISVpcl5xZthb+XPsbELj5AQXxIC8IriDZIQYjBJPEm5rS420sjZ0TLEzRcq5KdBhYrQ==",
"license": "MIT",
"dependencies": {
"es5-ext": "~0.10.2"
}
},
"node_modules/lucide-react": {
"version": "0.453.0",
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.453.0.tgz",
@ -6198,25 +6066,6 @@
"node": ">= 0.6"
}
},
"node_modules/memoizee": {
"version": "0.4.17",
"resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.17.tgz",
"integrity": "sha512-DGqD7Hjpi/1or4F/aYAspXKNm5Yili0QDAFAY4QYvpqpgiY6+1jOfqpmByzjxbWd/T9mChbCArXAbDAsTm5oXA==",
"license": "ISC",
"dependencies": {
"d": "^1.0.2",
"es5-ext": "^0.10.64",
"es6-weak-map": "^2.0.3",
"event-emitter": "^0.3.5",
"is-promise": "^2.2.2",
"lru-queue": "^0.1.0",
"next-tick": "^1.1.0",
"timers-ext": "^0.1.7"
},
"engines": {
"node": ">=0.12"
}
},
"node_modules/memorystore": {
"version": "1.6.7",
"resolved": "https://registry.npmjs.org/memorystore/-/memorystore-1.6.7.tgz",
@ -6417,12 +6266,6 @@
"react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc"
}
},
"node_modules/next-tick": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz",
"integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==",
"license": "ISC"
},
"node_modules/node-gyp-build": {
"version": "4.8.3",
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.3.tgz",
@ -6461,15 +6304,6 @@
"node": ">=0.10.0"
}
},
"node_modules/oauth4webapi": {
"version": "3.6.1",
"resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-3.6.1.tgz",
"integrity": "sha512-b39+drVyA4aNUptFOhkkmGWnG/BE7dT29SW/8PVYElqp7j/DBqzm5SS1G+MUD07XlTcBOAG+6Cb/35Cx2kHIuQ==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/panva"
}
},
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
@ -6527,19 +6361,6 @@
"node": ">= 0.8"
}
},
"node_modules/openid-client": {
"version": "6.6.2",
"resolved": "https://registry.npmjs.org/openid-client/-/openid-client-6.6.2.tgz",
"integrity": "sha512-Xya5TNMnnZuTM6DbHdB4q0S3ig2NTAELnii/ASie1xDEr8iiB8zZbO871OWBdrw++sd3hW6bqWjgcmSy1RTWHA==",
"license": "MIT",
"dependencies": {
"jose": "^6.0.11",
"oauth4webapi": "^3.5.4"
},
"funding": {
"url": "https://github.com/sponsors/panva"
}
},
"node_modules/package-json-from-dist": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
@ -7910,19 +7731,6 @@
"node": ">=0.8"
}
},
"node_modules/timers-ext": {
"version": "0.1.8",
"resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.8.tgz",
"integrity": "sha512-wFH7+SEAcKfJpfLPkrgMPvvwnEtj8W4IurvEyrKsDleXnKLCDw71w8jltvfLa8Rm4qQxxT4jmDBYbJG/z7qoww==",
"license": "ISC",
"dependencies": {
"es5-ext": "^0.10.64",
"next-tick": "^1.1.0"
},
"engines": {
"node": ">=0.12"
}
},
"node_modules/tiny-invariant": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz",
@ -8438,12 +8246,6 @@
"url": "https://github.com/sponsors/Wombosvideo"
}
},
"node_modules/type": {
"version": "2.7.3",
"resolved": "https://registry.npmjs.org/type/-/type-2.7.3.tgz",
"integrity": "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==",
"license": "ISC"
},
"node_modules/type-is": {
"version": "1.6.18",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",

View File

@ -42,7 +42,6 @@
"@radix-ui/react-toggle-group": "^1.1.3",
"@radix-ui/react-tooltip": "^1.2.0",
"@tanstack/react-query": "^5.60.5",
"@types/memoizee": "^0.4.12",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "^1.1.1",
@ -57,10 +56,8 @@
"hls.js": "^1.6.7",
"input-otp": "^1.4.2",
"lucide-react": "^0.453.0",
"memoizee": "^0.4.17",
"memorystore": "^1.6.7",
"next-themes": "^0.4.6",
"openid-client": "^6.6.2",
"passport": "^0.7.0",
"passport-local": "^1.0.0",
"react": "^18.3.1",

View File

@ -1,15 +0,0 @@
import { Pool, neonConfig } from '@neondatabase/serverless';
import { drizzle } from 'drizzle-orm/neon-serverless';
import ws from "ws";
import * as schema from "@shared/schema";
neonConfig.webSocketConstructor = ws;
if (!process.env.DATABASE_URL) {
throw new Error(
"DATABASE_URL must be set. Did you forget to provision a database?",
);
}
export const pool = new Pool({ connectionString: process.env.DATABASE_URL });
export const db = drizzle({ client: pool, schema });

View File

@ -1,4 +1,4 @@
import { sql, relations } from "drizzle-orm";
import { sql } from "drizzle-orm";
import { pgTable, text, varchar, integer, timestamp } from "drizzle-orm/pg-core";
import { createInsertSchema } from "drizzle-zod";
import { z } from "zod";
@ -17,88 +17,10 @@ export const videos = pgTable("videos", {
createdAt: timestamp("created_at").notNull().default(sql`CURRENT_TIMESTAMP`),
});
// User playlists table
export const playlists = pgTable("playlists", {
id: varchar("id").primaryKey().default(sql`gen_random_uuid()`),
name: text("name").notNull(),
description: text("description"),
isPublic: integer("is_public").notNull().default(0), // 0 = private, 1 = public
userId: varchar("user_id"), // For future user authentication
createdAt: timestamp("created_at").notNull().default(sql`CURRENT_TIMESTAMP`),
updatedAt: timestamp("updated_at").notNull().default(sql`CURRENT_TIMESTAMP`),
});
// Junction table for playlist-video relationships
export const playlistVideos = pgTable("playlist_videos", {
id: varchar("id").primaryKey().default(sql`gen_random_uuid()`),
playlistId: varchar("playlist_id").notNull(),
videoId: varchar("video_id").notNull(),
position: integer("position").notNull().default(0), // Order within playlist
addedAt: timestamp("added_at").notNull().default(sql`CURRENT_TIMESTAMP`),
});
// Favorites table (simplified playlist for favorites)
export const favorites = pgTable("favorites", {
id: varchar("id").primaryKey().default(sql`gen_random_uuid()`),
videoId: varchar("video_id").notNull(),
userId: varchar("user_id"), // For future user authentication
addedAt: timestamp("added_at").notNull().default(sql`CURRENT_TIMESTAMP`),
});
export const insertVideoSchema = createInsertSchema(videos).omit({
id: true,
createdAt: true,
});
export const insertPlaylistSchema = createInsertSchema(playlists).omit({
id: true,
createdAt: true,
updatedAt: true,
});
export const insertPlaylistVideoSchema = createInsertSchema(playlistVideos).omit({
id: true,
addedAt: true,
});
export const insertFavoriteSchema = createInsertSchema(favorites).omit({
id: true,
addedAt: true,
});
// Relations
export const playlistsRelations = relations(playlists, ({ many }) => ({
playlistVideos: many(playlistVideos),
}));
export const playlistVideosRelations = relations(playlistVideos, ({ one }) => ({
playlist: one(playlists, {
fields: [playlistVideos.playlistId],
references: [playlists.id],
}),
video: one(videos, {
fields: [playlistVideos.videoId],
references: [videos.id],
}),
}));
export const videosRelations = relations(videos, ({ many }) => ({
playlistVideos: many(playlistVideos),
favorites: many(favorites),
}));
export const favoritesRelations = relations(favorites, ({ one }) => ({
video: one(videos, {
fields: [favorites.videoId],
references: [videos.id],
}),
}));
export type InsertVideo = z.infer<typeof insertVideoSchema>;
export type Video = typeof videos.$inferSelect;
export type Playlist = typeof playlists.$inferSelect;
export type InsertPlaylist = z.infer<typeof insertPlaylistSchema>;
export type PlaylistVideo = typeof playlistVideos.$inferSelect;
export type InsertPlaylistVideo = z.infer<typeof insertPlaylistVideoSchema>;
export type Favorite = typeof favorites.$inferSelect;
export type InsertFavorite = z.infer<typeof insertFavoriteSchema>;