Implement academy analytics, billing, and web stories updates
This commit is contained in:
@@ -74,13 +74,27 @@ function searchResultContentType(pageType) {
|
||||
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 PromptLibraryHero({ title, description, items, pricingUrl, totalCount }) {
|
||||
const featuredImages = (items || [])
|
||||
.map((item) => item?.preview_image)
|
||||
.map((item) => promptPreviewAsset(item))
|
||||
.filter(Boolean)
|
||||
.slice(0, 3)
|
||||
|
||||
const primaryImage = featuredImages[0] || ''
|
||||
const primaryImage = featuredImages[0] || null
|
||||
const supportingImages = featuredImages.slice(1, 3)
|
||||
|
||||
return (
|
||||
@@ -119,14 +133,14 @@ function PromptLibraryHero({ title, description, items, pricingUrl, totalCount }
|
||||
{primaryImage ? (
|
||||
<>
|
||||
<div className="overflow-hidden rounded-[28px] border border-white/10 bg-black/20 shadow-[0_18px_45px_rgba(2,6,23,0.18)] aspect-[16/10]">
|
||||
<img src={primaryImage} alt="" aria-hidden="true" className="h-full w-full object-cover" />
|
||||
<img src={primaryImage.src} srcSet={primaryImage.srcSet || undefined} sizes="(max-width: 1279px) calc(100vw - 4rem), 420px" alt="" aria-hidden="true" className="h-full w-full object-cover" />
|
||||
</div>
|
||||
|
||||
{supportingImages.length ? (
|
||||
<div className={`grid gap-3 ${supportingImages.length === 1 ? 'grid-cols-1' : 'grid-cols-2'}`}>
|
||||
{supportingImages.map((image, index) => (
|
||||
<div key={`${image}-${index}`} className="overflow-hidden rounded-[28px] border border-white/10 bg-black/20 shadow-[0_18px_45px_rgba(2,6,23,0.18)] aspect-square">
|
||||
<img src={image} alt="" aria-hidden="true" className="h-full w-full object-cover" />
|
||||
<div key={`${image.src}-${index}`} className="overflow-hidden rounded-[28px] border border-white/10 bg-black/20 shadow-[0_18px_45px_rgba(2,6,23,0.18)] aspect-square">
|
||||
<img src={image.src} srcSet={image.srcSet || undefined} sizes="(max-width: 1279px) calc(50vw - 2rem), 200px" alt="" aria-hidden="true" className="h-full w-full object-cover" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@@ -145,7 +159,8 @@ function PromptLibraryHero({ title, description, items, pricingUrl, totalCount }
|
||||
|
||||
function AcademyCard({ pageType, item, analytics, searchContext, position }) {
|
||||
const lessonSeries = String(item?.series_name || '').trim()
|
||||
const promptPreviewImage = item?.preview_image || ''
|
||||
const promptPreviewImage = item?.preview_image_thumb || item?.preview_image || ''
|
||||
const promptPreviewSrcSet = item?.preview_image_srcset || ''
|
||||
const contentType = searchResultContentType(pageType)
|
||||
const href = itemHref(pageType, item)
|
||||
const trackSearchClick = () => {
|
||||
@@ -173,7 +188,7 @@ function AcademyCard({ pageType, item, analytics, searchContext, position }) {
|
||||
className="group overflow-hidden rounded-[30px] border border-white/10 bg-[linear-gradient(180deg,rgba(15,23,42,0.92),rgba(7,11,18,0.96))] shadow-[0_20px_50px_rgba(2,6,23,0.18)] transition hover:border-sky-300/25 hover:bg-[linear-gradient(180deg,rgba(15,23,42,0.96),rgba(10,15,26,0.98))]"
|
||||
>
|
||||
<div className="relative aspect-[16/11] overflow-hidden bg-[linear-gradient(135deg,rgba(56,189,248,0.18),rgba(17,24,39,0.94))]">
|
||||
{promptPreviewImage ? <img src={promptPreviewImage} alt="" aria-hidden="true" className="h-full w-full object-cover transition duration-500 group-hover:scale-[1.04]" /> : null}
|
||||
{promptPreviewImage ? <img src={promptPreviewImage} srcSet={promptPreviewSrcSet || undefined} sizes="(max-width: 767px) calc(100vw - 2rem), (max-width: 1279px) calc(50vw - 2rem), 420px" alt="" aria-hidden="true" className="h-full w-full object-cover transition duration-500 group-hover:scale-[1.04]" /> : null}
|
||||
<div className="absolute inset-0 bg-[linear-gradient(180deg,transparent,rgba(2,6,23,0.72))]" />
|
||||
<div className="absolute left-4 top-4 flex flex-wrap gap-2">
|
||||
<span className="rounded-full border border-white/10 bg-black/30 px-3 py-1 text-[10px] font-semibold uppercase tracking-[0.18em] text-[#fff0ea]">Prompt template</span>
|
||||
@@ -260,6 +275,7 @@ export default function AcademyList({ pageType, title, description, seo, items,
|
||||
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)
|
||||
@@ -270,12 +286,14 @@ export default function AcademyList({ pageType, title, description, seo, items,
|
||||
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, pageType])
|
||||
}, [initialItems, items?.current_page, items?.last_page, items?.next_page_url, items?.prev_page_url, pageType])
|
||||
|
||||
const hasMorePages = pageType === 'prompts' && pagination.currentPage < pagination.lastPage && Boolean(pagination.nextPageUrl)
|
||||
const hasFallbackPagination = pageType === 'prompts' && pagination.lastPage > 1
|
||||
|
||||
const loadMore = React.useCallback(async () => {
|
||||
if (pageType !== 'prompts' || loadingMore || !pagination.nextPageUrl) {
|
||||
@@ -292,6 +310,7 @@ export default function AcademyList({ pageType, title, description, seo, items,
|
||||
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 {
|
||||
@@ -299,7 +318,7 @@ export default function AcademyList({ pageType, title, description, seo, items,
|
||||
} finally {
|
||||
setLoadingMore(false)
|
||||
}
|
||||
}, [loadingMore, pageType, pagination.currentPage, pagination.lastPage, pagination.nextPageUrl])
|
||||
}, [loadingMore, pageType, pagination.currentPage, pagination.lastPage, pagination.nextPageUrl, pagination.prevPageUrl])
|
||||
|
||||
React.useEffect(() => {
|
||||
const sentinel = sentinelRef.current
|
||||
@@ -355,6 +374,26 @@ export default function AcademyList({ pageType, title, description, seo, items,
|
||||
<div ref={sentinelRef} className="h-10 w-full" aria-hidden="true" />
|
||||
{loadingMore ? <div className="rounded-[22px] border border-white/10 bg-black/20 px-5 py-4 text-center text-sm text-slate-300">Loading more prompts...</div> : null}
|
||||
{!hasMorePages && visibleItems.length > initialItems.length ? <div className="rounded-[22px] border border-white/10 bg-black/20 px-5 py-4 text-center text-sm text-slate-400">You have reached the end of the prompt library.</div> : null}
|
||||
{hasFallbackPagination ? (
|
||||
<div className="mt-4 flex flex-wrap items-center justify-between gap-3 rounded-[22px] border border-white/10 bg-black/20 px-5 py-4">
|
||||
<div className="text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-400">Auto-load is primary. Pagination is available as a backup.</div>
|
||||
<div className="flex flex-wrap items-center gap-3">
|
||||
{pagination.prevPageUrl ? (
|
||||
<Link href={pagination.prevPageUrl} preserveScroll className="inline-flex items-center gap-2 rounded-full border border-white/10 bg-white/[0.05] px-4 py-2 text-xs font-semibold uppercase tracking-[0.14em] text-white transition hover:bg-white/[0.09]">
|
||||
<i className="fa-solid fa-arrow-left text-[10px]" />
|
||||
Previous
|
||||
</Link>
|
||||
) : null}
|
||||
<span className="rounded-full border border-white/10 bg-white/[0.04] px-4 py-2 text-xs font-semibold uppercase tracking-[0.14em] text-slate-300">Page {pagination.currentPage || 1} of {pagination.lastPage || 1}</span>
|
||||
{pagination.nextPageUrl ? (
|
||||
<Link href={pagination.nextPageUrl} preserveScroll className="inline-flex items-center gap-2 rounded-full border border-white/10 bg-white/[0.05] px-4 py-2 text-xs font-semibold uppercase tracking-[0.14em] text-white transition hover:bg-white/[0.09]">
|
||||
Next
|
||||
<i className="fa-solid fa-arrow-right text-[10px]" />
|
||||
</Link>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
</>
|
||||
|
||||
Reference in New Issue
Block a user