diff --git a/src/app/(student)/dashboard/page.tsx b/src/app/(student)/dashboard/page.tsx index e010757..528e5ed 100644 --- a/src/app/(student)/dashboard/page.tsx +++ b/src/app/(student)/dashboard/page.tsx @@ -5,17 +5,8 @@ import { prisma } from "@/lib/prisma"; import Link from "next/link"; import { ArchetypeBanner } from "@/components/student/archetype-banner"; import { ToolboxPromoCard } from "@/components/tools/ToolboxPromoCard"; - -// Продаваемая линейка курсов → лендинг продукта. Курсы из этого списка, -// которых нет у студента, показываются на дашборде заблюренными карточками -// со ссылкой на лендинг (апселл). Легаси/обычные версии сюда не входят. -const STORE_LINKS: Record = { - "second-brain-vault": "https://second-brain.ru/sb-vault", - "obsidian-full": "https://obsidian.second-brain.ru/", - "claude-obsidian": "https://second-brain.ru/claude-obsidian", - "zotero-full": "https://zotero.second-brain.ru/", - "obsidian-ai": "https://obsidianai.second-brain.ru/", -}; +import { StoreShowcase } from "@/components/student/store-showcase"; +import { STORE_CATALOG } from "@/lib/store-catalog"; export default async function StudentDashboard() { const session = await auth.api.getSession({ headers: await headers() }); @@ -70,7 +61,7 @@ export default async function StudentDashboard() { "zotero-full": "zotero", // ВВ Zotero ← обычный Zotero }; const storeCourses = await prisma.course.findMany({ - where: { slug: { in: Object.keys(STORE_LINKS) }, published: true }, + where: { slug: { in: Object.keys(STORE_CATALOG) }, published: true }, select: { id: true, slug: true, title: true, description: true, coverImage: true }, }); const locked = storeCourses.filter( @@ -175,55 +166,10 @@ export default async function StudentDashboard() { )} {locked.length > 0 && ( -
-

- Другие курсы Second Brain -

-
- {locked.map((course) => ( - - {/* Overlay: замок + призыв, поверх размытого контента */} -
- 🔒 - - Открыть курс → - -
- {/* Размытый контент карточки */} -
- {course.coverImage ? ( - // eslint-disable-next-line @next/next/no-img-element - {course.title} - ) : ( -
- 📚 -
- )} -
-

{course.title}

- {course.description && ( -

- {course.description} -

- )} -
-
-
- ))} -
-
+ )} {expired.length > 0 && ( diff --git a/src/components/student/store-showcase.tsx b/src/components/student/store-showcase.tsx new file mode 100644 index 0000000..ea985d2 --- /dev/null +++ b/src/components/student/store-showcase.tsx @@ -0,0 +1,124 @@ +"use client"; + +import { useState } from "react"; +import { STORE_CATALOG } from "@/lib/store-catalog"; +import { StoreCheckoutModal, type LockedCourse } from "./store-checkout-modal"; + +interface Props { + courses: LockedCourse[]; + buyer: { name: string; email: string }; +} + +// Blurred card body shared by both the modal-trigger button and the +// fallback landing link. +function CardBody({ course }: { course: LockedCourse }) { + return ( + <> + {/* Overlay: замок + призыв, поверх размытого контента */} +
+ 🔒 + + Подробнее → + +
+ {/* Размытый контент карточки */} +
+ {course.coverImage ? ( + // eslint-disable-next-line @next/next/no-img-element + {course.title} + ) : ( +
+ 📚 +
+ )} +
+

{course.title}

+ {course.description && ( +

+ {course.description} +

+ )} +
+
+ + ); +} + +export function StoreShowcase({ courses, buyer }: Props) { + const [selectedSlug, setSelectedSlug] = useState(null); + + const selected = courses.find((c) => c.slug === selectedSlug); + const selectedEntry = selected ? STORE_CATALOG[selected.slug] : undefined; + + const cardClass = "card-aubade p-0 overflow-hidden flex flex-col relative group"; + + return ( +
+

+ Другие курсы Second Brain +

+
+ {courses.map((course) => { + const entry = STORE_CATALOG[course.slug]; + if (!entry || entry.tariffs.length === 0) { + // Fallback: no catalog entry → keep the old landing link + return ( + + + + ); + } + return ( + + ); + })} +
+ + {selected && selectedEntry && ( + setSelectedSlug(null)} + /> + )} +
+ ); +}