117 lines
6.1 KiB
JavaScript
117 lines
6.1 KiB
JavaScript
import React from 'react'
|
|
import { Link, router, usePage } from '@inertiajs/react'
|
|
import SeoHead from '../../components/seo/SeoHead'
|
|
import NovaSelect from '../../components/ui/NovaSelect'
|
|
|
|
function academyHref(section, slug) {
|
|
return `/academy/${section}/${encodeURIComponent(slug)}`
|
|
}
|
|
|
|
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 (
|
|
<div className="grid gap-3 rounded-[28px] border border-white/10 bg-black/20 p-5 md:grid-cols-3">
|
|
<input
|
|
defaultValue={filters?.q || ''}
|
|
placeholder={`Search ${pageType}`}
|
|
className="rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-sm text-white outline-none placeholder:text-slate-500"
|
|
onKeyDown={(event) => {
|
|
if (event.key !== 'Enter') return
|
|
router.get(window.location.pathname, { ...filters, q: event.currentTarget.value }, { preserveState: true, preserveScroll: true })
|
|
}}
|
|
/>
|
|
<NovaSelect
|
|
value={filters?.category || ''}
|
|
onChange={(nextValue) => 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"
|
|
/>
|
|
<NovaSelect
|
|
value={filters?.difficulty || ''}
|
|
onChange={(nextValue) => 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"
|
|
/>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function LockBadge({ item }) {
|
|
if (!item?.locked) return <span className="rounded-full border border-emerald-300/25 bg-emerald-300/12 px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.2em] text-emerald-100">{item.access_level}</span>
|
|
|
|
return <span className="rounded-full border border-amber-300/25 bg-amber-300/12 px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.2em] text-amber-100">Locked · {item.access_level}</span>
|
|
}
|
|
|
|
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 AcademyCard({ pageType, item }) {
|
|
return (
|
|
<Link href={itemHref(pageType, item)} className="rounded-[28px] border border-white/10 bg-white/[0.04] p-5 transition hover:border-white/20 hover:bg-white/[0.06]">
|
|
<div className="flex items-center justify-between gap-3">
|
|
<p className="text-[11px] font-semibold uppercase tracking-[0.22em] text-sky-200/80">{pageType.slice(0, -1)}</p>
|
|
<LockBadge item={item} />
|
|
</div>
|
|
<h2 className="mt-4 text-2xl font-semibold tracking-[-0.04em] text-white">{item.title}</h2>
|
|
<p className="mt-3 text-sm leading-7 text-slate-300">{item.excerpt || item.description || item.prompt_preview || item.content_preview || 'No description yet.'}</p>
|
|
{pageType === 'prompts' && item.tags?.length ? <p className="mt-4 text-xs uppercase tracking-[0.18em] text-slate-500">{item.tags.slice(0, 4).join(' · ')}</p> : null}
|
|
{pageType === 'challenges' ? <p className="mt-4 text-xs uppercase tracking-[0.18em] text-slate-500">{item.status} · {item.submission_count ?? 0} submissions</p> : null}
|
|
</Link>
|
|
)
|
|
}
|
|
|
|
export default function AcademyList({ pageType, title, description, seo, items, filters, categories, pricingUrl }) {
|
|
const flash = usePage().props.flash || {}
|
|
|
|
return (
|
|
<main className="min-h-screen bg-[radial-gradient(circle_at_top_left,_rgba(56,189,248,0.15),_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={title} description={description} />
|
|
|
|
<div className="mx-auto max-w-[1360px] space-y-6">
|
|
<section className="rounded-[38px] border border-white/10 bg-black/20 p-8 md:p-10">
|
|
<div className="flex flex-wrap items-end justify-between gap-5">
|
|
<div>
|
|
<p className="text-[11px] font-semibold uppercase tracking-[0.24em] text-sky-200/80">Skinbase AI Academy</p>
|
|
<h1 className="mt-3 text-4xl font-semibold tracking-[-0.05em] text-white md:text-5xl">{title}</h1>
|
|
<p className="mt-4 max-w-3xl text-base leading-8 text-slate-300">{description}</p>
|
|
</div>
|
|
<Link href={pricingUrl} 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</Link>
|
|
</div>
|
|
</section>
|
|
|
|
{flash.success ? <div className="rounded-2xl border border-emerald-300/20 bg-emerald-300/10 px-4 py-3 text-sm text-emerald-100">{flash.success}</div> : null}
|
|
{flash.error ? <div className="rounded-2xl border border-rose-300/20 bg-rose-300/10 px-4 py-3 text-sm text-rose-100">{flash.error}</div> : null}
|
|
|
|
<QueryFilters pageType={pageType} filters={filters} categories={categories} />
|
|
|
|
{(items?.data || []).length === 0 ? (
|
|
<section className="rounded-[30px] border border-white/10 bg-white/[0.04] px-6 py-12 text-center text-slate-400">Nothing matched this Academy view yet.</section>
|
|
) : (
|
|
<section className="grid gap-5 md:grid-cols-2 xl:grid-cols-3">
|
|
{items.data.map((item) => <AcademyCard key={`${pageType}-${item.id}`} pageType={pageType} item={item} />)}
|
|
</section>
|
|
)}
|
|
</div>
|
|
</main>
|
|
)
|
|
} |