From 7dc341290332bb7fd5766de5569b4dfc3403189d Mon Sep 17 00:00:00 2001 From: dmitriylaukhin Date: Sat, 6 Jun 2026 16:01:39 +0500 Subject: [PATCH] Add locked course cards (upsell) to student dashboard Show the sellable course lineup on the dashboard: courses the student doesn't own render as blurred cards with a lock and a CTA linking to the product landing page (new tab). Owned courses are unchanged. Co-Authored-By: Claude Opus 4.8 --- src/app/(student)/dashboard/page.tsx | 71 ++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/src/app/(student)/dashboard/page.tsx b/src/app/(student)/dashboard/page.tsx index 3ab11ed..659baaf 100644 --- a/src/app/(student)/dashboard/page.tsx +++ b/src/app/(student)/dashboard/page.tsx @@ -4,6 +4,17 @@ import { redirect } from "next/navigation"; import { prisma } from "@/lib/prisma"; import Link from "next/link"; +// Продаваемая линейка курсов → лендинг продукта. Курсы из этого списка, +// которых нет у студента, показываются на дашборде заблюренными карточками +// со ссылкой на лендинг (апселл). Легаси/обычные версии сюда не входят. +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/", +}; + export default async function StudentDashboard() { const session = await auth.api.getSession({ headers: await headers() }); if (!session) redirect("/login"); @@ -44,6 +55,14 @@ export default async function StudentDashboard() { : []; const completedSet = new Set(progressRecords.map((p) => p.lessonId)); + // Витрина: продаваемые курсы, которых у студента нет → заблюренные карточки + const enrolledSlugs = new Set(enrollments.map((e) => e.course.slug)); + const storeCourses = await prisma.course.findMany({ + where: { slug: { in: Object.keys(STORE_LINKS) }, published: true }, + select: { id: true, slug: true, title: true, description: true, coverImage: true }, + }); + const locked = storeCourses.filter((c) => !enrolledSlugs.has(c.slug)); + return (

Мои курсы

@@ -131,6 +150,58 @@ export default async function StudentDashboard() { )} + {locked.length > 0 && ( +
+

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

+ +
+ )} + {expired.length > 0 && (