06b8d9f64b
- WrappedRun Prisma model + migration (aggregates only, no note names/text) - /wrapped authed route: FSA + webkitdirectory + drag-drop reader, Web Worker parser - archetype + score heuristics, ДС-2 result card - POST /api/wrapped/run (userId from session), public /share/wrapped/[token] - next/og OG image (Satori+resvg-wasm, alpine-safe) + Fira Mono ttf - middleware: /share/wrapped public Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
93 lines
4.2 KiB
TypeScript
93 lines
4.2 KiB
TypeScript
import { prisma } from "@/lib/prisma";
|
||
import { notFound } from "next/navigation";
|
||
import Link from "next/link";
|
||
import type { Metadata } from "next";
|
||
|
||
const ARCHETYPE_RU: Record<string, string> = {
|
||
architect: "Архитектор",
|
||
gardener: "Садовник",
|
||
librarian: "Библиотекарь",
|
||
pragmatist: "Прагматик",
|
||
};
|
||
|
||
export async function generateMetadata({ params }: { params: Promise<{ token: string }> }): Promise<Metadata> {
|
||
const { token } = await params;
|
||
return {
|
||
title: "Моя карта знаний в Obsidian — Second Brain",
|
||
description: "Я собрал статистику своего хранилища знаний. Сделай свою карточку.",
|
||
openGraph: {
|
||
title: "Моя карта знаний в Obsidian",
|
||
description: "Statистика хранилища в Obsidian — Second Brain Wrapped.",
|
||
images: [{ url: `/share/wrapped/${token}/opengraph-image`, width: 1080, height: 1920 }],
|
||
},
|
||
twitter: { card: "summary_large_image" },
|
||
};
|
||
}
|
||
|
||
// Публичная страница карточки (без сессии). Показывает только агрегаты — никаких имён заметок.
|
||
export default async function SharePage({ params }: { params: Promise<{ token: string }> }) {
|
||
const { token } = await params;
|
||
const run = await prisma.wrappedRun.findUnique({ where: { shareToken: token } });
|
||
if (!run) notFound();
|
||
|
||
const metric = (n: number, l: string) => (
|
||
<div
|
||
key={l}
|
||
style={{ display: "flex", flexDirection: "column", alignItems: "center", padding: "16px 12px", background: "#FFFFFF", border: "2px solid var(--border)", boxShadow: "4px 4px 0 0 var(--border)" }}
|
||
>
|
||
<span style={{ fontSize: 32, fontWeight: 700 }}>{n}</span>
|
||
<span style={{ fontSize: 12, color: "var(--muted-foreground)", textTransform: "uppercase", letterSpacing: 1 }}>{l}</span>
|
||
</div>
|
||
);
|
||
|
||
return (
|
||
<main style={{ background: "var(--background)", color: "var(--foreground)", minHeight: "100vh", display: "flex", flexDirection: "column", alignItems: "center", padding: "48px 24px" }}>
|
||
<div style={{ width: "100%", maxWidth: 480 }}>
|
||
<p style={{ fontSize: 12, fontWeight: 700, textTransform: "uppercase", letterSpacing: 3, color: "var(--muted-foreground)", marginBottom: 8 }}>
|
||
Obsidian Wrapped · 2026
|
||
</p>
|
||
<h1 style={{ fontSize: 28, fontWeight: 700, margin: "0 0 4px" }}>
|
||
{run.archetype ? ARCHETYPE_RU[run.archetype] : "Карта знаний"}
|
||
</h1>
|
||
<p style={{ fontSize: 13, textTransform: "uppercase", letterSpacing: 2, color: "var(--muted-foreground)", margin: "0 0 24px" }}>
|
||
зрелость {run.score}/100
|
||
</p>
|
||
|
||
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr", gap: 12, marginBottom: 20 }}>
|
||
{metric(run.noteCount, "заметок")}
|
||
{metric(run.linkCount, "связей")}
|
||
{metric(run.tagCount, "тегов")}
|
||
</div>
|
||
|
||
{run.topTag && (
|
||
<div style={{ padding: "16px 20px", background: "var(--accent)", border: "2px solid var(--foreground)", marginBottom: 24, fontSize: 16 }}>
|
||
Топ-тег: #{run.topTag}
|
||
</div>
|
||
)}
|
||
|
||
<div style={{ display: "flex", flexDirection: "column", gap: 12 }}>
|
||
<Link
|
||
href="/register"
|
||
className="btn-aubade btn-aubade-accent"
|
||
style={{ textAlign: "center", fontSize: 15, fontWeight: 700, padding: "14px" }}
|
||
>
|
||
Сделать свою карточку →
|
||
</Link>
|
||
<a
|
||
href={`/share/wrapped/${token}/opengraph-image`}
|
||
download={`obsidian-wrapped-${token}.png`}
|
||
className="btn-aubade"
|
||
style={{ textAlign: "center", fontSize: 14, padding: "12px" }}
|
||
>
|
||
Скачать картинку (1080×1920)
|
||
</a>
|
||
</div>
|
||
|
||
<p style={{ fontSize: 12, color: "var(--muted-foreground)", marginTop: 28, textAlign: "center" }}>
|
||
🔒 Здесь только числа. Содержимое заметок остаётся на устройстве автора.
|
||
</p>
|
||
</div>
|
||
</main>
|
||
);
|
||
}
|