mirror of
https://github.com/AderKonstantin/aderktech-chronark.com-.git
synced 2025-06-08 13:48:42 +03:00
155 lines
5.7 KiB
TypeScript
155 lines
5.7 KiB
TypeScript
'use client'; // Превращаем компонент в клиентский
|
||
|
||
import Link from "next/link";
|
||
import React, { useEffect, useState } from "react";
|
||
import { allProjects } from "contentlayer/generated";
|
||
import { Navigation } from "../components/nav";
|
||
import { Card } from "../components/card";
|
||
import { Article } from "./article";
|
||
import { Eye } from "lucide-react";
|
||
|
||
export const revalidate = 60;
|
||
|
||
type ViewsData = Record<string, number>;
|
||
|
||
export default function ProjectsPage() {
|
||
const [views, setViews] = useState<ViewsData>({});
|
||
const [loading, setLoading] = useState(true);
|
||
const [error, setError] = useState<string | null>(null);
|
||
|
||
// Загружаем данные через клиентский запрос
|
||
useEffect(() => {
|
||
const fetchViews = async () => {
|
||
try {
|
||
const response = await fetch('/api/views');
|
||
if (!response.ok) throw new Error('Failed to fetch views');
|
||
const data = await response.json();
|
||
setViews(data);
|
||
} catch (err) {
|
||
setError(err instanceof Error ? err.message : 'Unknown error');
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
};
|
||
|
||
fetchViews();
|
||
}, []);
|
||
|
||
const featured = allProjects.find((project) => project.slug === "cbg")!;
|
||
const top2 = allProjects.find((project) => project.slug === "blog")!;
|
||
const top3 = allProjects.find((project) => project.slug === "bimkaspace")!;
|
||
|
||
const sorted = allProjects
|
||
.filter((p) => p.published)
|
||
.filter(
|
||
(project) =>
|
||
project.slug !== featured.slug &&
|
||
project.slug !== top2.slug &&
|
||
project.slug !== top3.slug,
|
||
)
|
||
.sort(
|
||
(a, b) =>
|
||
new Date(b.date ?? Number.POSITIVE_INFINITY).getTime() -
|
||
new Date(a.date ?? Number.POSITIVE_INFINITY).getTime(),
|
||
);
|
||
|
||
return (
|
||
<div className="relative pb-16">
|
||
<Navigation />
|
||
<div className="px-6 pt-20 mx-auto space-y-8 max-w-7xl lg:px-8 md:space-y-16 md:pt-24 lg:pt-32">
|
||
<div className="max-w-2xl mx-auto lg:mx-0">
|
||
<h2 className="text-3xl font-bold tracking-tight text-zinc-100 sm:text-4xl">
|
||
Проекты
|
||
</h2>
|
||
<p className="mt-4 text-zinc-400">
|
||
Часть проектов делал из академического интереса, часть мои проекты для себя.
|
||
</p>
|
||
</div>
|
||
<div className="w-full h-px bg-zinc-800" />
|
||
|
||
<div className="grid grid-cols-1 gap-8 mx-auto lg:grid-cols-2 ">
|
||
<Card>
|
||
<Link href={`/projects/${featured.slug}`}>
|
||
<article className="relative w-full h-full p-4 md:p-8">
|
||
<div className="flex items-center justify-between gap-2">
|
||
<div className="text-xs text-zinc-100">
|
||
{featured.date ? (
|
||
<time dateTime={new Date(featured.date).toISOString()}>
|
||
{Intl.DateTimeFormat(undefined, {
|
||
dateStyle: "medium",
|
||
}).format(new Date(featured.date))}
|
||
</time>
|
||
) : (
|
||
<span>SOON</span>
|
||
)}
|
||
</div>
|
||
<span className="flex items-center gap-1 text-xs text-zinc-500">
|
||
<Eye className="w-4 h-4" />{" "}
|
||
{Intl.NumberFormat("en-US", { notation: "compact" }).format(
|
||
views[featured.slug] ?? 0,
|
||
)}
|
||
</span>
|
||
</div>
|
||
|
||
<h2
|
||
id="featured-post"
|
||
className="mt-4 text-3xl font-bold text-zinc-100 group-hover:text-white sm:text-4xl font-display"
|
||
>
|
||
{featured.title}
|
||
</h2>
|
||
<p className="mt-4 leading-8 duration-150 text-zinc-400 group-hover:text-zinc-300">
|
||
{featured.description}
|
||
</p>
|
||
<div className="absolute bottom-4 md:bottom-8">
|
||
<p className="hidden text-zinc-200 hover:text-zinc-50 lg:block">
|
||
Подробнее <span aria-hidden="true">→</span>
|
||
</p>
|
||
</div>
|
||
</article>
|
||
</Link>
|
||
</Card>
|
||
|
||
<div className="flex flex-col w-full gap-8 mx-auto border-t border-gray-900/10 lg:mx-0 lg:border-t-0 ">
|
||
{[top2, top3].map((project) => (
|
||
<Card key={project.slug}>
|
||
<Article project={project} views={views[project.slug] ?? 0} />
|
||
</Card>
|
||
))}
|
||
</div>
|
||
</div>
|
||
<div className="hidden w-full h-px md:block bg-zinc-800" />
|
||
|
||
<div className="grid grid-cols-1 gap-4 mx-auto lg:mx-0 md:grid-cols-3">
|
||
<div className="grid grid-cols-1 gap-4">
|
||
{sorted
|
||
.filter((_, i) => i % 3 === 0)
|
||
.map((project) => (
|
||
<Card key={project.slug}>
|
||
<Article project={project} views={views[project.slug] ?? 0} />
|
||
</Card>
|
||
))}
|
||
</div>
|
||
<div className="grid grid-cols-1 gap-4">
|
||
{sorted
|
||
.filter((_, i) => i % 3 === 1)
|
||
.map((project) => (
|
||
<Card key={project.slug}>
|
||
<Article project={project} views={views[project.slug] ?? 0} />
|
||
</Card>
|
||
))}
|
||
</div>
|
||
<div className="grid grid-cols-1 gap-4">
|
||
{sorted
|
||
.filter((_, i) => i % 3 === 2)
|
||
.map((project) => (
|
||
<Card key={project.slug}>
|
||
<Article project={project} views={views[project.slug] ?? 0} />
|
||
</Card>
|
||
))}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|