Implement academy analytics, billing, and web stories updates
This commit is contained in:
@@ -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" />
|
||||
|
||||
Reference in New Issue
Block a user