Implement academy analytics, billing, and web stories updates

This commit is contained in:
2026-05-26 07:27:29 +02:00
parent 456c3d6bb0
commit 0b33a1b074
177 changed files with 27360 additions and 2685 deletions

View File

@@ -1,6 +1,7 @@
import React, { useEffect, useMemo, useState } from 'react'
import { Link, usePage } from '@inertiajs/react'
import { Link, router, usePage } from '@inertiajs/react'
import SeoHead from '../../components/seo/SeoHead'
import { postAcademyAction, trackUpgradeClick, useAcademyPageAnalytics } from '../../lib/academyAnalytics'
function CourseBreadcrumbs({ items = [] }) {
if (!items.length) return null
@@ -197,10 +198,15 @@ function SectionBlock({ section, isActive = false }) {
)
}
export default function AcademyCoursesShow({ seo, course, sections = [], unsectionedLessons = [], pricingUrl }) {
export default function AcademyCoursesShow({ seo, course, sections = [], unsectionedLessons = [], pricingUrl, startUrl = null, interaction = null, interactionRoutes = null, loginUrl = null, analytics = null }) {
const flash = usePage().props.flash || {}
useAcademyPageAnalytics(analytics)
const cover = course?.cover_image_url || course?.cover_image || course?.teaser_image_url || course?.teaser_image || ''
const progress = course?.progress || null
const [liked, setLiked] = useState(Boolean(interaction?.liked))
const [saved, setSaved] = useState(Boolean(interaction?.saved))
const [likesCount, setLikesCount] = useState(Number(interaction?.likes_count || 0))
const [savesCount, setSavesCount] = useState(Number(interaction?.saves_count || 0))
const sectionJumpItems = useMemo(
() => [
@@ -245,6 +251,63 @@ export default function AcademyCoursesShow({ seo, course, sections = [], unsecti
return () => observer.disconnect()
}, [sectionJumpItems])
const requireLogin = () => {
if (loginUrl && typeof window !== 'undefined') {
window.location.href = loginUrl
}
}
const startCourse = () => {
if (!startUrl) {
requireLogin()
return
}
router.post(startUrl)
}
const toggleLike = async () => {
if (!interactionRoutes?.like || !analytics?.contentType || !analytics?.contentId) {
return
}
if (analytics?.isGuest) {
requireLogin()
return
}
const payload = await postAcademyAction(interactionRoutes.like, {
content_type: analytics.contentType,
content_id: analytics.contentId,
})
if (payload?.liked !== undefined) {
setLiked(Boolean(payload.liked))
setLikesCount(Number(payload.likes_count || 0))
}
}
const toggleSave = async () => {
if (!interactionRoutes?.save || !analytics?.contentType || !analytics?.contentId) {
return
}
if (analytics?.isGuest) {
requireLogin()
return
}
const payload = await postAcademyAction(interactionRoutes.save, {
content_type: analytics.contentType,
content_id: analytics.contentId,
})
if (payload?.saved !== undefined) {
setSaved(Boolean(payload.saved))
setSavesCount(Number(payload.saves_count || 0))
}
}
return (
<main className="min-h-screen bg-[radial-gradient(circle_at_top_left,_rgba(56,189,248,0.16),_transparent_24%),radial-gradient(circle_at_bottom_right,_rgba(251,191,36,0.16),_transparent_24%),linear-gradient(180deg,_#0f172a_0%,_#111827_100%)] px-4 py-8 sm:px-6 lg:px-8">
<SeoHead seo={seo || {}} title={course?.title} description={course?.excerpt || course?.description} />
@@ -273,6 +336,13 @@ export default function AcademyCoursesShow({ seo, course, sections = [], unsecti
{course?.subtitle ? <p className="mt-4 text-sm font-semibold uppercase tracking-[0.24em] text-amber-100/90">{course.subtitle}</p> : null}
<p className="mt-5 max-w-3xl text-base leading-8 text-slate-300 md:text-lg">{course?.excerpt || course?.description}</p>
<div className="mt-6 flex flex-wrap gap-3">
<button type="button" onClick={startCourse} className="rounded-full border border-sky-300/25 bg-sky-300/12 px-5 py-3 text-sm font-semibold text-sky-100">{progress?.percent ? 'Continue course' : 'Start course'}</button>
<button type="button" onClick={toggleLike} className="rounded-full border border-white/10 bg-white/[0.06] px-5 py-3 text-sm font-semibold text-white transition hover:border-white/20 hover:bg-white/[0.08]">{liked ? `Liked · ${likesCount}` : `Like · ${likesCount}`}</button>
<button type="button" onClick={toggleSave} className="rounded-full border border-white/10 bg-white/[0.06] px-5 py-3 text-sm font-semibold text-white transition hover:border-white/20 hover:bg-white/[0.08]">{saved ? `Saved · ${savesCount}` : `Save · ${savesCount}`}</button>
<Link href={pricingUrl} onClick={() => trackUpgradeClick(analytics, { source: 'academy_course_header' })} className="rounded-full border border-amber-300/25 bg-amber-300/12 px-5 py-3 text-sm font-semibold text-amber-100">See plans</Link>
</div>
<div className="mt-7 overflow-hidden rounded-[32px] border border-white/10 bg-slate-950/80 shadow-[0_24px_60px_rgba(2,6,23,0.32)]">
{cover ? (
<img src={cover} alt="" aria-hidden="true" className="w-full object-contain" />