Files
netrunner-landing/app/[lang]/download/download-client.tsx
T
2026-04-19 16:05:05 +10:00

225 lines
8.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { useState, useEffect } from "react";
import { motion, Variants } from "framer-motion";
import { Button } from "@/components/ui/button";
import {
Monitor,
Smartphone,
Terminal,
Apple,
Download,
ExternalLink,
Bell,
Loader2,
Clock,
} from "lucide-react";
import { Dictionary } from "@/lib/IDict";
import { pb, type PBDownload, getPbFileUrl } from "@/lib/pb";
interface DownloadClientProps {
dict: Dictionary["download"];
lang: string;
}
export function DownloadClient({ dict, lang }: DownloadClientProps) {
const [platforms, setPlatforms] = useState<PBDownload[]>([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
async function fetchDownloads() {
try {
setLoading(true);
const records = await pb
.collection(`downloads_${lang}`)
.getFullList<PBDownload>({
sort: "order",
requestKey: null,
});
setPlatforms(records);
} catch (error) {
console.error("Ошибка загрузки данных платформы:", error);
} finally {
setLoading(false);
}
}
fetchDownloads();
}, [lang]);
// Единый конфиг стилей: Primary (Фиолетовый) для базы, Secondary (Циан) для акцентов
const getPlatformIcon = (platformId: string) => {
const props = {
className:
"size-8 text-secondary drop-shadow-[0_0_10px_rgba(0,229,242,0.8)]",
};
switch (platformId) {
case "windows":
return <Monitor {...props} />;
case "linux":
return <Terminal {...props} />;
case "android":
return <Smartphone {...props} />;
case "macos":
case "ios":
return <Apple {...props} />;
default:
return <Download {...props} />;
}
};
const containerVariants: Variants = {
hidden: { opacity: 0 },
show: {
opacity: 1,
transition: { staggerChildren: 0.1 },
},
};
const itemVariants: Variants = {
hidden: { opacity: 0, y: 40 },
show: {
opacity: 1,
y: 0,
transition: { type: "spring", stiffness: 300, damping: 24 },
},
};
return (
<div className="min-h-screen pt-32 pb-24 px-4 container mx-auto max-w-5xl relative">
{/* Мягкое фоновое свечение */}
<div className="absolute top-1/4 left-1/2 -translate-x-1/2 w-full max-w-[800px] h-[400px] bg-primary/5 blur-[120px] rounded-full pointer-events-none -z-10" />
<div className="text-center mb-20">
<h1 className="text-4xl md:text-6xl font-black tracking-tighter mb-6 text-foreground">
{dict.title}
</h1>
<p className="text-muted-foreground text-lg max-w-2xl mx-auto font-medium">
{dict.subtitle}
</p>
</div>
{loading ? (
<div className="flex flex-col items-center justify-center py-20 text-primary opacity-50">
<Loader2 className="w-10 h-10 animate-spin mb-4" />
<span className="font-mono text-xs uppercase tracking-[0.3em]">
Accessing_Build_Server...
</span>
</div>
) : (
<motion.div
variants={containerVariants}
initial="hidden"
animate="show"
className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 md:gap-8"
>
{platforms.map((platform) => (
<motion.div
key={platform.id}
variants={itemVariants}
className={`bg-card/40 backdrop-blur-xl border border-border/50 rounded-[2.5rem] p-8 md:p-10 flex flex-col transition-all duration-500 relative overflow-hidden group hover:border-primary/40 hover:shadow-[0_0_40px_rgba(139,61,255,0.15)] ${
platform.status === "dev" ? "opacity-60 grayscale-[50%]" : ""
}`}
>
{/* Статус бейдж */}
<div className="absolute top-8 right-8">
{platform.status === "active" ? (
<div className="px-3 py-1 rounded-full bg-primary/10 border border-primary/20 text-primary text-[10px] font-bold uppercase tracking-widest shadow-sm">
{dict.badges.active}
</div>
) : (
<div className="px-3 py-1 rounded-full bg-muted border border-border text-muted-foreground text-[10px] font-bold uppercase tracking-widest">
{dict.badges.dev}
</div>
)}
</div>
{/* Иконка в Циановом (Secondary) стиле */}
<div className="mb-10 p-5 rounded-[1.5rem] bg-secondary/10 inline-flex border border-secondary/20 shadow-[0_0_20px_rgba(0,229,242,0.15)] w-fit transition-transform duration-500 group-hover:scale-110">
{getPlatformIcon(platform.platformId)}
</div>
<div className="mb-10">
<h3 className="text-2xl font-bold text-foreground mb-2 tracking-tight">
{platform.name}
</h3>
<p className="text-sm text-muted-foreground font-mono">
{platform.version}
</p>
</div>
{/* Кнопки действий: Основная всегда Фиолетовая (Primary) */}
<div className="mt-auto flex flex-col gap-3">
{platform.status === "active" ? (
<>
{/* Прямое скачивание */}
{platform.file ? (
<Button
asChild
className="w-full h-14 rounded-2xl font-bold gap-3 bg-primary text-primary-foreground hover:bg-primary/90 shadow-[0_0_20px_rgba(139,61,255,0.3)] transition-all active:scale-[0.98]"
>
<a
href={getPbFileUrl(platform, platform.file)}
download
>
<Download className="size-5" />
{dict.buttons.direct}
</a>
</Button>
) : (
<Button
disabled
variant="outline"
className="w-full h-14 rounded-2xl font-bold gap-3 opacity-50 bg-background/50 border-dashed"
>
<Download className="size-5" />
Build Pending
</Button>
)}
{/* Ссылка на магазин (Вместо GitHub) */}
{platform.storeLink ? (
<Button
asChild
variant="outline"
className="w-full h-14 rounded-2xl gap-3 border-border/50 bg-background/30 text-muted-foreground hover:text-foreground hover:border-primary/30 transition-all"
>
<a
href={platform.storeLink}
target="_blank"
rel="noopener noreferrer"
>
<ExternalLink className="size-4" />
{dict.buttons.store}
</a>
</Button>
) : (
<Button
variant="outline"
disabled
className="w-full h-14 rounded-2xl gap-3 border-border/20 bg-background/20 text-muted-foreground/40 cursor-not-allowed"
>
<Clock className="size-4" />
{dict.buttons.soon}
</Button>
)}
</>
) : (
// В разработке
<Button
variant="secondary"
disabled
className="w-full h-14 rounded-2xl gap-3 opacity-40 cursor-not-allowed bg-muted"
>
<Bell className="size-4" />
{dict.buttons.notify}
</Button>
)}
</div>
</motion.div>
))}
</motion.div>
)}
</div>
);
}