import React from 'react'
import { Link, router, usePage } from '@inertiajs/react'
import SeoHead from '../../components/seo/SeoHead'
import NovaSelect from '../../components/ui/NovaSelect'
import { trackAcademySearchResultClick, trackUpgradeClick, useAcademyPageAnalytics } from '../../lib/academyAnalytics'
function academyHref(section, slug) {
return `/academy/${section}/${encodeURIComponent(slug)}`
}
function Breadcrumbs({ items = [] }) {
if (!items.length) {
return null
}
return (
{items.map((item, index) => {
const isLast = index === items.length - 1
return (
{isLast ? (
{item.label}
) : (
{item.label}
)}
{!isLast ? / : null}
)
})}
)
}
function QueryFilters({ pageType, filters, categories }) {
if (pageType !== 'lessons' && pageType !== 'prompts') {
return null
}
const categoryOptions = [{ value: '', label: 'All categories' }, ...(categories || []).map((category) => ({ value: category.slug, label: category.name }))]
const difficultyOptions = [
{ value: '', label: 'All levels' },
{ value: 'beginner', label: 'Beginner' },
{ value: 'intermediate', label: 'Intermediate' },
{ value: 'advanced', label: 'Advanced' },
{ value: 'pro', label: 'Pro' },
]
return (
{
if (event.key !== 'Enter') return
router.get(window.location.pathname, { ...filters, q: event.currentTarget.value }, { preserveState: true, preserveScroll: true })
}}
/>
router.get(window.location.pathname, { ...filters, category: nextValue || undefined }, { preserveState: true, preserveScroll: true })}
options={categoryOptions}
searchable={false}
className="rounded-2xl bg-white/[0.04]"
placeholder="All categories"
/>
router.get(window.location.pathname, { ...filters, difficulty: nextValue || undefined }, { preserveState: true, preserveScroll: true })}
options={difficultyOptions}
searchable={false}
className="rounded-2xl bg-white/[0.04]"
placeholder="All levels"
/>
)
}
function LockBadge({ item }) {
if (!item?.locked) return {item.access_level}
return Locked · {item.access_level}
}
function itemHref(pageType, item) {
if (pageType === 'lessons') return academyHref('lessons', item.slug)
if (pageType === 'prompts') return academyHref('prompts', item.slug)
if (pageType === 'packs') return academyHref('packs', item.slug)
return academyHref('challenges', item.slug)
}
function searchResultContentType(pageType) {
if (pageType === 'prompts') return 'academy_prompt'
if (pageType === 'lessons') return 'academy_lesson'
if (pageType === 'packs') return 'academy_prompt_pack'
if (pageType === 'challenges') return 'academy_challenge'
return null
}
function promptPreviewAsset(item) {
const full = item?.preview_image || ''
const thumb = item?.preview_image_thumb || full
if (!thumb) {
return null
}
return {
src: thumb,
srcSet: item?.preview_image_srcset || '',
}
}
function lessonPreviewAsset(item) {
const src = item?.cover_image_url || item?.article_cover_image_url || item?.cover_image || item?.article_cover_image || ''
if (!src) {
return null
}
return { src }
}
function PromptSpotlightCard({ item }) {
const preview = promptPreviewAsset(item)
return (
{preview ?
: null}
{item?.spotlight?.eyebrow || 'Prompt pick'}
{item?.difficulty ? {item.difficulty} : null}
{item.title}
{item.excerpt || item.prompt_preview || item.description || 'Reusable prompt template.'}
{item?.category?.name || 'Academy'}
{item?.tags?.[0] ? {item.tags[0]} : null}
)
}
function PromptDiscoverySection({ id, title, description, items = [], href, ctaLabel }) {
if (!items.length) {
return null
}
return (
Prompt discovery
{title}
{description}
{href && ctaLabel ?
{ctaLabel} : null}
)
}
function PopularPromptPeriodTabs({ currentPeriod, periods = [] }) {
if (!periods.length) {
return null
}
return (
{periods.map((period) => (
{period.label}
{period.description}
))}
{currentPeriod?.description ?
{currentPeriod.description}
: null}
)
}
function formatAccessDate(value) {
if (!value) {
return null
}
const parsed = new Date(value)
if (Number.isNaN(parsed.getTime())) {
return null
}
return new Intl.DateTimeFormat(undefined, {
year: 'numeric',
month: 'short',
day: 'numeric',
}).format(parsed)
}
function academyAccessHeading(access) {
switch (access?.status) {
case 'staff_access':
return 'You currently have full staff access to the Academy.'
case 'grace_period':
return `${access.tierLabel} access is still active.`
case 'trialing':
return `${access.tierLabel} trial is active right now.`
case 'active':
return access?.hasPaidAccess ? `${access.tierLabel} access is active.` : 'Your Academy access is active.'
case 'free':
return 'You currently have Free access to the Academy.'
default:
return null
}
}
function academyAccessMeta(access) {
if (!access?.signedIn) {
return []
}
const items = [
{ label: 'Current tier', value: access?.tierLabel || 'Free' },
{ label: 'Status', value: access?.statusLabel || 'Free access' },
]
const formattedDate = formatAccessDate(access?.expiresAt)
if (formattedDate && access?.dateLabel) {
items.push({ label: access.dateLabel, value: formattedDate })
} else if (access?.renewsAutomatically) {
items.push({ label: 'Billing', value: 'Renews automatically' })
} else if (!access?.hasPaidAccess) {
items.push({ label: 'Upgrade', value: 'Creator and Pro unlock premium prompts' })
}
return items
}
function PromptLibraryHero({ promptView = 'library', title, description, items, pricingUrl, coursesUrl, packsUrl, promptPopularUrl, promptLibraryUrl, popularPeriod, popularPeriods = [], totalCount, analytics, hasPopularSection, academyAccess = null }) {
const isPopularView = promptView === 'popular'
const statLabel = isPopularView ? `ranked prompts ${currentPeriodStatSuffix(popularPeriod)}` : 'prompts available'
const showSignedInAccess = Boolean(academyAccess?.signedIn)
const accessHeading = academyAccessHeading(academyAccess)
const accessMeta = academyAccessMeta(academyAccess)
const useBillingAction = showSignedInAccess && academyAccess?.hasPaidAccess && academyAccess?.billingUrl
const primaryActionLabel = useBillingAction ? (academyAccess?.status === 'grace_period' ? 'Renew now' : 'Manage billing') : 'Upgrade now'
const primaryActionIcon = useBillingAction ? (academyAccess?.status === 'grace_period' ? 'fa-solid fa-rotate-right' : 'fa-solid fa-sliders') : 'fa-solid fa-arrow-up-right-from-square'
const primaryActionHref = useBillingAction ? academyAccess.billingUrl : pricingUrl
const secondaryAction = isPopularView
? { href: promptLibraryUrl, label: 'Browse full library', icon: 'fa-solid fa-grid-2' }
: (hasPopularSection
? { href: promptPopularUrl, label: 'Top prompts', icon: 'fa-solid fa-fire' }
: { href: coursesUrl, label: 'Explore courses', icon: 'fa-solid fa-graduation-cap' })
const heroHighlights = [
{
label: isPopularView ? 'Ranking window' : 'Templates',
value: isPopularView ? `${totalCount || 0} ranked prompts` : `${totalCount || 0} prompts`,
},
{
label: 'Use case',
value: isPopularView ? 'High-performing systems + trend tracking' : 'Reusable systems + premium previews',
},
]
const heroTags = isPopularView
? ['Momentum picks', 'Copy trends', 'Compare windows']
: ['Fast starts', 'Visual workflows', 'Copy + adapt']
const handlePrimaryAction = () => {
if (!useBillingAction) {
trackUpgradeClick(analytics, { source: 'prompts_library_hero_primary' })
}
}
return (
Skinbase AI Academy
{isPopularView ? 'Popular prompts' : 'Prompt Library'}
{totalCount || 0} {statLabel}
{isPopularView ?
: null}
{heroHighlights.map((item) => (
{item.label}
{item.value}
))}
{heroTags.map((tag) => (
{tag}
))}
{primaryActionLabel}
{secondaryAction.label}
See prompt packs
Quick routes
{totalCount || 0} total
{isPopularView ? 'Browse full prompt library' : 'Browse Academy courses'}
See prompt packs
{isPopularView ? (
Explore Academy courses
) : hasPopularSection ? (
Open top prompts page
) : null}
{isPopularView ? 'Use the period tabs to compare momentum windows.' : 'Jump straight into packs, courses, or ranked prompts.'}
{showSignedInAccess ? 'Your Academy access' : (isPopularView ? 'Turn rankings into results' : 'Upgrade for full access')}
{showSignedInAccess ? accessHeading : (isPopularView ? 'Open the highest-performing prompts, then unlock the full text, helper prompts, variants, and premium workflows.' : 'Unlock full prompt text, helper prompts, variants, and premium workflows.')}
{showSignedInAccess ? (
{accessMeta.map((item) => (
{item.label}
{item.value}
))}
) : null}
{primaryActionLabel}
{secondaryAction.label}
{academyAccess?.status === 'grace_period' ?
Opens billing account to restore renewal before access ends.
: null}
)
}
function LessonsLibraryHero({ title, description, items = [], totalCount, pricingUrl, coursesUrl, promptLibraryUrl, academyAccess = null, analytics }) {
const featuredLesson = items.find((item) => lessonPreviewAsset(item)) || items[0] || null
const featuredPreview = lessonPreviewAsset(featuredLesson)
const showSignedInAccess = Boolean(academyAccess?.signedIn)
const accessHeading = academyAccessHeading(academyAccess)
const accessMeta = academyAccessMeta(academyAccess)
const useBillingAction = showSignedInAccess && academyAccess?.hasPaidAccess && academyAccess?.billingUrl
const primaryActionLabel = useBillingAction ? (academyAccess?.status === 'grace_period' ? 'Renew now' : 'Manage billing') : 'See plans'
const primaryActionIcon = useBillingAction ? (academyAccess?.status === 'grace_period' ? 'fa-solid fa-rotate-right' : 'fa-solid fa-sliders') : 'fa-solid fa-arrow-up-right-from-square'
const primaryActionHref = useBillingAction ? academyAccess.billingUrl : pricingUrl
const handlePrimaryAction = () => {
if (!useBillingAction) {
trackUpgradeClick(analytics, { source: 'lessons_library_hero_primary' })
}
}
return (
Skinbase AI Academy
Lessons
{totalCount || 0} tutorials
Library
{totalCount || 0} structured lessons
Focus
Prompt craft + workflow cleanup
Short wins
Creative habits
Practical steps
Browse courses
Prompt library
{primaryActionLabel}
Latest lesson
{featuredLesson?.difficulty ?
{featuredLesson.difficulty} : null}
{featuredPreview ?
: null}
{featuredLesson?.formatted_lesson_number ? {featuredLesson.formatted_lesson_number} : null}
{featuredLesson ? : null}
{String(featuredLesson?.series_name || featuredLesson?.category?.name || 'Academy lesson').trim()}
{featuredLesson?.title || 'Explore lessons'}
{featuredLesson?.excerpt || featuredLesson?.content_preview || featuredLesson?.description || 'Open a practical Academy lesson.'}
{showSignedInAccess ? 'Your Academy access' : 'Upgrade for full access'}
{showSignedInAccess ? accessHeading : 'Unlock the full lesson library, premium workflows, and the broader Academy learning track.'}
{showSignedInAccess ? (
{accessMeta.map((item) => (
{item.label}
{item.value}
))}
) : null}
{primaryActionLabel}
Browse courses
{academyAccess?.status === 'grace_period' ?
Opens billing account to restore renewal before access ends.
: null}
)
}
function currentPeriodStatSuffix(popularPeriod) {
if (!popularPeriod?.label) {
return 'this month'
}
return popularPeriod.label === '30 days' ? 'this month' : `for ${popularPeriod.label.toLowerCase()}`
}
function AcademyCard({ pageType, item, analytics, searchContext, position }) {
const lessonSeries = String(item?.series_name || '').trim()
const promptPreviewImage = item?.preview_image_thumb || item?.preview_image || ''
const promptPreviewSrcSet = item?.preview_image_srcset || ''
const lessonPreview = lessonPreviewAsset(item)
const contentType = searchResultContentType(pageType)
const href = itemHref(pageType, item)
const trackSearchClick = () => {
if (!searchContext?.query || !contentType) {
return
}
trackAcademySearchResultClick(analytics, searchContext, {
contentType,
contentId: item?.id,
position,
})
}
if (pageType === 'prompts') {
return (
{promptPreviewImage ?
: null}
{item?.ranking?.rank ? `#${item.ranking.rank} this month` : 'Prompt template'}
{item?.difficulty ? {item.difficulty} : null}
{item?.aspect_ratio ? {item.aspect_ratio} : null}
{item?.category?.name || 'Academy'}
{Array.isArray(item?.tool_notes) && item.tool_notes.length ?
{item.tool_notes.length} comparisons : null}
{item.title}
{item.excerpt || item.description || item.prompt_preview || 'No description yet.'}
{item?.ranking ?
{item.ranking.prompt_copies > 0 ? `${item.ranking.prompt_copies} copies` : `${item.ranking.views} views`} · popularity {item.ranking.popularity_score}
: null}
{item.tags?.length ?
{item.tags.slice(0, 4).join(' · ')}
: null}
)
}
if (pageType === 'lessons') {
return (
{lessonPreview ?
: null}
{item?.formatted_lesson_number ? {item.formatted_lesson_number} : null}
{item?.difficulty ? {item.difficulty} : null}
{item?.reading_minutes ? {item.reading_minutes} min : null}
{item?.category?.name || 'Academy lesson'}
{lessonSeries ?
{lessonSeries} : null}
{item.title}
{item.excerpt || item.description || item.content_preview || 'No description yet.'}
{item.tags?.length ?
{item.tags.slice(0, 4).join(' · ')}
: null}
)
}
return (
{pageType === 'lessons' && item?.formatted_lesson_number ? (
{item.formatted_lesson_number}
{lessonSeries ? {lessonSeries} : null}
) : null}
{item.title}
{item.excerpt || item.description || item.prompt_preview || item.content_preview || 'No description yet.'}
{pageType === 'lessons' && item.tags?.length ? {item.tags.slice(0, 4).join(' · ')}
: null}
{pageType === 'prompts' && item.tags?.length ? {item.tags.slice(0, 4).join(' · ')}
: null}
{pageType === 'challenges' ? {item.status} · {item.submission_count ?? 0} submissions
: null}
)
}
async function fetchAcademyPage(url) {
const response = await fetch(url, {
headers: {
Accept: 'application/json',
'X-Requested-With': 'XMLHttpRequest',
},
credentials: 'same-origin',
})
if (!response.ok) {
throw new Error('Failed to load the next page.')
}
return response.json()
}
export default function AcademyList({ pageType, promptView = 'library', title, description, seo, breadcrumbs = [], items, filters, categories, pricingUrl, coursesUrl, packsUrl, promptPopularUrl, promptLibraryUrl, popularPeriod = null, popularPeriods = [], featuredPrompts = [], popularPrompts = [], academyAccess = null, analytics }) {
const flash = usePage().props.flash || {}
useAcademyPageAnalytics(analytics)
const searchContext = analytics?.search ? {
query: analytics.search.query,
normalizedQuery: analytics.search.normalizedQuery,
resultsCount: analytics.search.resultsCount,
filters,
} : null
const initialItems = React.useMemo(() => (Array.isArray(items?.data) ? items.data : []), [items])
const [visibleItems, setVisibleItems] = React.useState(initialItems)
const [pagination, setPagination] = React.useState({
currentPage: Number(items?.current_page || 1),
lastPage: Number(items?.last_page || 1),
prevPageUrl: items?.prev_page_url || null,
nextPageUrl: items?.next_page_url || null,
})
const [loadingMore, setLoadingMore] = React.useState(false)
const sentinelRef = React.useRef(null)
const hasActivePromptFilters = pageType === 'prompts' && promptView === 'library' && Boolean(filters?.q || filters?.category || filters?.difficulty || filters?.tag)
const showPromptDiscovery = pageType === 'prompts' && promptView === 'library' && !hasActivePromptFilters
const showPopularFeatured = pageType === 'prompts' && promptView === 'popular' && featuredPrompts.length > 0
const infiniteLoadLabel = pageType === 'lessons' ? 'lessons' : 'prompts'
const usesInfiniteLoad = (pageType === 'prompts' && promptView === 'library') || pageType === 'lessons'
React.useEffect(() => {
setVisibleItems(initialItems)
setPagination({
currentPage: Number(items?.current_page || 1),
lastPage: Number(items?.last_page || 1),
prevPageUrl: items?.prev_page_url || null,
nextPageUrl: items?.next_page_url || null,
})
setLoadingMore(false)
}, [initialItems, items?.current_page, items?.last_page, items?.next_page_url, items?.prev_page_url, pageType])
const hasMorePages = usesInfiniteLoad && pagination.currentPage < pagination.lastPage && Boolean(pagination.nextPageUrl)
const hasFallbackPagination = usesInfiniteLoad && pagination.lastPage > 1
const loadMore = React.useCallback(async () => {
if (!usesInfiniteLoad || loadingMore || !pagination.nextPageUrl) {
return
}
setLoadingMore(true)
try {
const payload = await fetchAcademyPage(pagination.nextPageUrl)
const nextItems = Array.isArray(payload?.data) ? payload.data : []
setVisibleItems((current) => [...current, ...nextItems.filter((item) => !current.some((existing) => String(existing.id) === String(item.id)))])
setPagination({
currentPage: Number(payload?.current_page || pagination.currentPage),
lastPage: Number(payload?.last_page || pagination.lastPage),
prevPageUrl: payload?.prev_page_url || pagination.prevPageUrl,
nextPageUrl: payload?.next_page_url || null,
})
} catch {
setPagination((current) => ({ ...current, nextPageUrl: null }))
} finally {
setLoadingMore(false)
}
}, [loadingMore, pagination.currentPage, pagination.lastPage, pagination.nextPageUrl, pagination.prevPageUrl, usesInfiniteLoad])
React.useEffect(() => {
const sentinel = sentinelRef.current
if (!sentinel || !hasMorePages || loadingMore || typeof window === 'undefined' || typeof window.IntersectionObserver !== 'function') {
return undefined
}
const observer = new window.IntersectionObserver((entries) => {
if (entries[0]?.isIntersecting) {
void loadMore()
}
}, { rootMargin: '360px 0px' })
observer.observe(sentinel)
return () => observer.disconnect()
}, [hasMorePages, loadMore, loadingMore])
return (
{(pageType === 'prompts' || pageType === 'lessons') ?
: null}
{pageType === 'prompts' ?
0} academyAccess={academyAccess} /> : pageType === 'lessons' ? : (
Skinbase AI Academy
{title}
{description}
trackUpgradeClick(analytics, { source: `${pageType}_list_hero` })} className="rounded-full border border-sky-300/25 bg-sky-300/12 px-5 py-3 text-sm font-semibold text-sky-100">Upgrade preview
)}
{flash.success ? {flash.success}
: null}
{flash.error ? {flash.error}
: null}
{promptView === 'library' ? : null}
{showPromptDiscovery ? (
<>
>
) : null}
{showPopularFeatured ? : null}
{visibleItems.length === 0 ? (
Nothing matched this Academy view yet.
) : (
<>
{visibleItems.map((item, index) => )}
{usesInfiniteLoad ? (
{loadingMore ?
Loading more {infiniteLoadLabel}...
: null}
{!hasMorePages && visibleItems.length > initialItems.length ?
You have reached the end of the {pageType === 'lessons' ? 'lesson library' : 'prompt library'}.
: null}
{hasFallbackPagination ? (
Auto-load is primary. Pagination is available as a backup.
{pagination.prevPageUrl ? (
Previous
) : null}
Page {pagination.currentPage || 1} of {pagination.lastPage || 1}
{pagination.nextPageUrl ? (
Next
) : null}
) : null}
) : null}
>
)}
)
}