import React, { useEffect, useMemo, useState } from '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 return ( ) } function ProgressMeter({ progress }) { const percent = Math.max(0, Math.min(100, Number(progress?.percent || 0))) return (

Progress

{percent}%

{progress ? 'In progress' : 'Not started'}

{progress ? `${progress.completedRequired}/${progress.totalRequired} required lessons completed` : 'Start the course to begin tracking progress through required lessons.'}

) } function LessonChip({ lesson }) { const thumbnail = lesson?.cover_image_url || lesson?.article_cover_image_url || lesson?.cover_image || lesson?.article_cover_image || '' const stepLabel = lesson?.course_step_label || null const stepNumber = Number(lesson?.course_step_number || 0) const isCompleted = Boolean(lesson?.completed) const readingMinutes = Number(lesson?.reading_minutes || 0) const ctaLabel = isCompleted ? 'Review lesson' : 'Open lesson' const difficultyLabel = lesson?.difficulty || 'lesson' const accessLabel = lesson?.access_level || 'free' const lessonTypeLabel = lesson?.lesson_type || 'article' const statusLabel = isCompleted ? 'Completed' : lesson?.is_required ? 'Required next' : 'Optional read' const supportCopy = isCompleted ? 'You already finished this lesson.' : lesson?.is_required ? 'Recommended as the next required step in this course.' : 'Optional depth you can take at your own pace.' return (
{thumbnail ? ( ) : (
)}
{lesson.is_required ? 'Required' : 'Optional'} {isCompleted ? ( Done ) : null}
{stepLabel ?

{stepLabel}

: null} {stepNumber > 0 ?

{String(stepNumber).padStart(2, '0')}

: null} {!stepNumber && lesson.formatted_lesson_number ?

{lesson.formatted_lesson_number}

: null}
{stepLabel ?

{stepLabel}

: null} {lesson.formatted_lesson_number ? {lesson.formatted_lesson_number} : null} {difficultyLabel} {accessLabel} {readingMinutes > 0 ? {readingMinutes} min : null}

{lesson.title}

{supportCopy}

{lesson.excerpt || lesson.content_preview || 'Open this lesson inside the course.'}

{lessonTypeLabel} {lesson.category_name ? {lesson.category_name} : null} Course flow

Lesson path

Status {statusLabel}
Access {accessLabel}
Read time {readingMinutes > 0 ? `${readingMinutes} min` : 'Quick read'}
Continue path {ctaLabel}
) } function SectionBlock({ section, isActive = false }) { if (!section?.is_visible) return null const lessonCount = section.lessons?.length || 0 const requiredCount = (section.lessons || []).filter((lesson) => lesson?.is_required).length return (

Course section

{section.order_num + 1} {requiredCount > 0 ? {requiredCount} required : null}

{section.title}

{section.description ?

{section.description}

: null}

{lessonCount} lessons mapped in this section

{lessonCount} lessons {isActive ? Reading now : null}
{(section.lessons || []).map((lesson) => ( ))}
) } 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 heroBackground = course?.teaser_image_url || course?.teaser_image || course?.cover_image_url || course?.cover_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 visibleSections = sections.filter((section) => section?.is_visible) const totalLessons = Number(course?.lessons_count || (unsectionedLessons.length + visibleSections.reduce((sum, section) => sum + (section.lessons || []).length, 0))) const totalSections = visibleSections.length + (unsectionedLessons.length ? 1 : 0) const estimatedMinutes = course?.estimated_minutes ? `${course.estimated_minutes} min` : 'Flexible pace' const sectionJumpItems = useMemo( () => [ ...(unsectionedLessons.length ? [{ id: 'course-outline-core', label: 'Core lessons', count: unsectionedLessons.length }] : []), ...visibleSections .map((section) => ({ id: `section-${section.id}`, label: section.title, count: (section.lessons || []).length })), ], [unsectionedLessons, visibleSections], ) const [activeJumpId, setActiveJumpId] = useState(sectionJumpItems[0]?.id || null) const breadcrumbs = [ { label: 'Academy', href: '/academy' }, { label: 'Courses', href: '/academy/courses' }, { label: course?.title || 'Course', href: course?.public_url || '#' }, ] useEffect(() => { if (!sectionJumpItems.length || typeof window === 'undefined' || typeof IntersectionObserver === 'undefined') { return undefined } const observer = new IntersectionObserver( (entries) => { const visibleEntries = entries.filter((entry) => entry.isIntersecting).sort((left, right) => right.intersectionRatio - left.intersectionRatio) if (!visibleEntries.length) return setActiveJumpId(visibleEntries[0].target.id) }, { rootMargin: '-20% 0px -55% 0px', threshold: [0.2, 0.45, 0.7], }, ) const elements = sectionJumpItems.map((item) => document.getElementById(item.id)).filter(Boolean) elements.forEach((element) => observer.observe(element)) 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 (
{flash.success ?
{flash.success}
: null} {flash.error ?
{flash.error}
: null}
{heroBackground ? : null}
Skinbase AI Academy Course path {course?.difficulty} {course?.access_level} {progress?.percent ? {progress.percent}% complete : null}
{course?.subtitle ?

{course.subtitle}

: null}

{course?.title}

{course?.excerpt || course?.description}

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

Library

{totalLessons} lessons

Structure

{totalSections} sections

Pace

{estimatedMinutes}

Status

{progress?.percent ? `${progress.percent}% complete` : 'Ready to start'}

{unsectionedLessons.length ? ( ) : null} {sections.filter((section) => section?.is_visible).map((section) => ( ))}
) }