init commit
This commit is contained in:
+300
-93
@@ -1,101 +1,308 @@
|
||||
import Image from "next/image";
|
||||
'use client';
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import Link from "next/link";
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCaption,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogClose,
|
||||
} from "@/components/ui/dialog";
|
||||
import {
|
||||
Carousel,
|
||||
CarouselContent,
|
||||
CarouselItem,
|
||||
} from "@/components/ui/carousel";
|
||||
import Autoplay from "embla-carousel-autoplay";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import React, { useState, useRef } from "react";
|
||||
|
||||
// Mock data structure
|
||||
interface NzbItem {
|
||||
id: string;
|
||||
name: string;
|
||||
size: string;
|
||||
age: string;
|
||||
category: string;
|
||||
downloads: number;
|
||||
metadata: {
|
||||
description: string;
|
||||
groups: string[];
|
||||
files: string[];
|
||||
};
|
||||
}
|
||||
|
||||
// Mock data structure for Carousel items
|
||||
interface FeaturedItem {
|
||||
id: string;
|
||||
title: string;
|
||||
category: string;
|
||||
imageUrl?: string; // Optional: direct image URL or use a placeholder
|
||||
description: string;
|
||||
rating: string; // e.g., "IMDb: 8.5/10"
|
||||
}
|
||||
|
||||
// Placeholder SVG for Carousel Item Image
|
||||
const PlaceholderCarouselImage = ({ text }: { text: string }) => (
|
||||
<div className="w-full h-64 bg-muted flex items-center justify-center rounded-md">
|
||||
<span className="text-xl text-muted-foreground">{text}</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default function IndexerPage() {
|
||||
const categories = ["All", "Movies", "Series", "Anime", "Music"];
|
||||
const [selectedItem, setSelectedItem] = useState<NzbItem | null>(null);
|
||||
|
||||
// Autoplay plugin ref
|
||||
const autoplayPlugin = useRef(
|
||||
Autoplay({ delay: 10000, stopOnInteraction: true })
|
||||
);
|
||||
|
||||
const mockFeaturedData: FeaturedItem[] = [
|
||||
{
|
||||
id: "feat1",
|
||||
title: "Blockbuster Movie Premiere",
|
||||
category: "Movies",
|
||||
description: "The latest action-packed thriller hits the screens! Don't miss out.",
|
||||
rating: "IMDb: 9.2/10",
|
||||
},
|
||||
{
|
||||
id: "feat2",
|
||||
title: "New Hit Series - Episode 1",
|
||||
category: "Series",
|
||||
description: "A gripping new drama series that will keep you on the edge of your seat.",
|
||||
rating: "Rotten Tomatoes: 95%",
|
||||
},
|
||||
{
|
||||
id: "feat3",
|
||||
title: "Must-Watch Anime Film",
|
||||
category: "Anime",
|
||||
description: "Critically acclaimed anime feature with stunning visuals.",
|
||||
rating: "MyAnimeList: 8.9/10",
|
||||
},
|
||||
];
|
||||
|
||||
const mockNzbData: NzbItem[] = [
|
||||
{
|
||||
id: "1",
|
||||
name: "Example Movie Title",
|
||||
size: "2.5 GB",
|
||||
age: "1 day",
|
||||
category: "Movies",
|
||||
downloads: 1250,
|
||||
metadata: {
|
||||
description: "A fantastic movie about something exciting.",
|
||||
groups: ["alt.binaries.movies.hd"],
|
||||
files: ["movie_part1.rar", "movie_part2.rar"],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
name: "Awesome Series S01E01",
|
||||
size: "500 MB",
|
||||
age: "5 hours",
|
||||
category: "Series",
|
||||
downloads: 340,
|
||||
metadata: {
|
||||
description: "First episode of an awesome new series.",
|
||||
groups: ["alt.binaries.tv"],
|
||||
files: ["series_s01e01.mkv"],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "3",
|
||||
name: "Cool Anime Movie",
|
||||
size: "1.2 GB",
|
||||
age: "3 days",
|
||||
category: "Anime",
|
||||
downloads: 780,
|
||||
metadata: {
|
||||
description: "A visually stunning anime film.",
|
||||
groups: ["alt.binaries.anime"],
|
||||
files: ["anime.movie.mkv"],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "4",
|
||||
name: "Great Music Album",
|
||||
size: "300 MB",
|
||||
age: "10 days",
|
||||
category: "Music",
|
||||
downloads: 50,
|
||||
metadata: {
|
||||
description: "An album by a popular artist.",
|
||||
groups: ["alt.binaries.music.mp3"],
|
||||
files: ["track01.mp3", "track02.mp3", "cover.jpg"],
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
// Filter data based on category (simple filter for now)
|
||||
const getFilteredData = (category: string) => {
|
||||
if (category === "All") return mockNzbData;
|
||||
return mockNzbData.filter((item) => item.category === category);
|
||||
};
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]">
|
||||
<main className="flex flex-col gap-8 row-start-2 items-center sm:items-start">
|
||||
<Image
|
||||
className="dark:invert"
|
||||
src="/next.svg"
|
||||
alt="Next.js logo"
|
||||
width={180}
|
||||
height={38}
|
||||
priority
|
||||
/>
|
||||
<ol className="list-inside list-decimal text-sm text-center sm:text-left font-[family-name:var(--font-geist-mono)]">
|
||||
<li className="mb-2">
|
||||
Get started by editing{" "}
|
||||
<code className="bg-black/[.05] dark:bg-white/[.06] px-1 py-0.5 rounded font-semibold">
|
||||
app/page.tsx
|
||||
</code>
|
||||
.
|
||||
</li>
|
||||
<li>Save and see your changes instantly.</li>
|
||||
</ol>
|
||||
|
||||
<div className="flex gap-4 items-center flex-col sm:flex-row">
|
||||
<a
|
||||
className="rounded-full border border-solid border-transparent transition-colors flex items-center justify-center bg-foreground text-background gap-2 hover:bg-[#383838] dark:hover:bg-[#ccc] text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5"
|
||||
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
className="dark:invert"
|
||||
src="/vercel.svg"
|
||||
alt="Vercel logomark"
|
||||
width={20}
|
||||
height={20}
|
||||
/>
|
||||
Deploy now
|
||||
</a>
|
||||
<a
|
||||
className="rounded-full border border-solid border-black/[.08] dark:border-white/[.145] transition-colors flex items-center justify-center hover:bg-[#f2f2f2] dark:hover:bg-[#1a1a1a] hover:border-transparent text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 sm:min-w-44"
|
||||
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Read our docs
|
||||
</a>
|
||||
<div className="container mx-auto p-4 py-8 md:py-12 space-y-8">
|
||||
<div className="flex justify-between items-center mb-6">
|
||||
<h1 className="text-3xl font-bold">NZB Indexer</h1>
|
||||
<div className="space-x-2">
|
||||
<Link href="/news" passHref>
|
||||
<Button variant="ghost">News</Button>
|
||||
</Link>
|
||||
<Link href="/upgrade" passHref>
|
||||
<Button variant="ghost">Upgrade</Button>
|
||||
</Link>
|
||||
<Link href="/settings" passHref>
|
||||
<Button variant="ghost">Settings</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</main>
|
||||
<footer className="row-start-3 flex gap-6 flex-wrap items-center justify-center">
|
||||
<a
|
||||
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
|
||||
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
</div>
|
||||
|
||||
{/* Featured Carousel Section */}
|
||||
<div className="space-y-4">
|
||||
<h2
|
||||
className="text-2xl font-semibold tracking-tight text-primary drop-shadow-[0_2px_3px_hsl(var(--primary)/0.5)]"
|
||||
>
|
||||
<Image
|
||||
aria-hidden
|
||||
src="/file.svg"
|
||||
alt="File icon"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
Learn
|
||||
</a>
|
||||
<a
|
||||
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
|
||||
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
Newest Uploads
|
||||
</h2>
|
||||
<Carousel
|
||||
className="w-full"
|
||||
opts={{ loop: true }}
|
||||
plugins={[autoplayPlugin.current]}
|
||||
onMouseEnter={autoplayPlugin.current.stop}
|
||||
onMouseLeave={autoplayPlugin.current.reset}
|
||||
>
|
||||
<Image
|
||||
aria-hidden
|
||||
src="/window.svg"
|
||||
alt="Window icon"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
Examples
|
||||
</a>
|
||||
<a
|
||||
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
|
||||
href="https://nextjs.org?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
aria-hidden
|
||||
src="/globe.svg"
|
||||
alt="Globe icon"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
Go to nextjs.org →
|
||||
</a>
|
||||
</footer>
|
||||
<CarouselContent>
|
||||
{mockFeaturedData.map((featuredItem) => (
|
||||
<CarouselItem key={featuredItem.id}>
|
||||
<Card className="overflow-hidden">
|
||||
<CardContent className="flex flex-col md:flex-row items-center p-0">
|
||||
<div className="w-full md:w-1/3 h-64 md:h-auto">
|
||||
<PlaceholderCarouselImage text={featuredItem.title} />
|
||||
</div>
|
||||
<div className="p-6 space-y-3 flex-1">
|
||||
<h3 className="text-xl font-semibold">{featuredItem.title}</h3>
|
||||
<p className="text-sm text-muted-foreground">{featuredItem.description}</p>
|
||||
<div className="flex justify-between items-center pt-2">
|
||||
<span className="text-sm font-medium text-primary">{featuredItem.rating}</span>
|
||||
<Button variant="secondary" size="sm">View Details</Button>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</CarouselItem>
|
||||
))}
|
||||
</CarouselContent>
|
||||
</Carousel>
|
||||
</div>
|
||||
|
||||
<Tabs defaultValue="All" className="w-full">
|
||||
<TabsList className="mb-4">
|
||||
{categories.map((category) => (
|
||||
<TabsTrigger key={category} value={category}>
|
||||
{category}
|
||||
</TabsTrigger>
|
||||
))}
|
||||
</TabsList>
|
||||
|
||||
{categories.map((category) => (
|
||||
<TabsContent key={category} value={category}>
|
||||
<div className="flex w-full max-w-sm items-center space-x-2 mb-4">
|
||||
<Input type="text" placeholder={`Search in ${category}...`} />
|
||||
<Button type="submit">Search</Button>
|
||||
</div>
|
||||
<Table>
|
||||
<TableCaption>
|
||||
{category === "All"
|
||||
? "A list of NZBs."
|
||||
: `Results for ${category}.`}
|
||||
</TableCaption>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="w-[35%]">Name</TableHead>
|
||||
<TableHead>Size</TableHead>
|
||||
<TableHead>Age</TableHead>
|
||||
<TableHead>Downloads</TableHead>
|
||||
<TableHead className="text-right">Actions</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{getFilteredData(category).map((item) => (
|
||||
<TableRow key={item.id}>
|
||||
<TableCell className="font-medium">{item.name}</TableCell>
|
||||
<TableCell>{item.size}</TableCell>
|
||||
<TableCell>{item.age}</TableCell>
|
||||
<TableCell>{item.downloads.toLocaleString()}</TableCell>
|
||||
<TableCell className="text-right space-x-2">
|
||||
<Button variant="ghost" size="sm" onClick={() => setSelectedItem(item)}>
|
||||
Info
|
||||
</Button>
|
||||
<Button variant="outline" size="sm">
|
||||
Download
|
||||
</Button>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TabsContent>
|
||||
))}
|
||||
</Tabs>
|
||||
|
||||
{/* Dialog for NZB Info - controlled by selectedItem state */}
|
||||
{selectedItem && (
|
||||
<Dialog open={!!selectedItem} onOpenChange={(isOpen) => !isOpen && setSelectedItem(null)}>
|
||||
<DialogContent className="sm:max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{selectedItem.name}</DialogTitle>
|
||||
<DialogDescription>
|
||||
Category: {selectedItem.category} | Size: {selectedItem.size} | Age: {selectedItem.age} | Downloads: {selectedItem.downloads.toLocaleString()}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="py-4 space-y-2">
|
||||
<h4 className="font-semibold">Description:</h4>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{selectedItem.metadata.description}
|
||||
</p>
|
||||
<h4 className="font-semibold">Groups:</h4>
|
||||
<ul className="list-disc list-inside text-sm text-muted-foreground">
|
||||
{selectedItem.metadata.groups.map((group) => (
|
||||
<li key={group}>{group}</li>
|
||||
))}
|
||||
</ul>
|
||||
<h4 className="font-semibold">Files:</h4>
|
||||
<ul className="list-disc list-inside text-sm text-muted-foreground">
|
||||
{selectedItem.metadata.files.map((file) => (
|
||||
<li key={file}>{file}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
<DialogFooter className="sm:justify-start">
|
||||
<DialogClose asChild>
|
||||
<Button type="button" variant="secondary">
|
||||
Close
|
||||
</Button>
|
||||
</DialogClose>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user