Build world campaigns rewards and recaps
This commit is contained in:
@@ -1,4 +1,7 @@
|
||||
import React from 'react'
|
||||
import React, { useEffect, useRef } from 'react'
|
||||
import WorldCampaignMeta from './WorldCampaignMeta'
|
||||
import WorldStatusBadge from './WorldStatusBadge'
|
||||
import { trackWorldSourceClick, trackWorldSourceImpression, withWorldSource } from '../../lib/worldAnalytics'
|
||||
|
||||
function themeStyle(theme) {
|
||||
return {
|
||||
@@ -7,14 +10,62 @@ function themeStyle(theme) {
|
||||
}
|
||||
}
|
||||
|
||||
export default function WorldCard({ world, compact = false }) {
|
||||
export default function WorldCard({ world, compact = false, sourceSurface = '', sourceDetail = '' }) {
|
||||
const cardRef = useRef(null)
|
||||
const href = world && sourceSurface ? withWorldSource(world.public_url, sourceSurface, sourceDetail) : world?.public_url
|
||||
|
||||
useEffect(() => {
|
||||
if (!sourceSurface || !world?.id || typeof window === 'undefined') {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const node = cardRef.current
|
||||
if (!node) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
if (typeof window.IntersectionObserver !== 'function') {
|
||||
trackWorldSourceImpression({
|
||||
worldId: world.id,
|
||||
worldTitle: world.title,
|
||||
sourceSurface,
|
||||
sourceDetail,
|
||||
sectionKey: compact ? 'compact_card' : 'card',
|
||||
})
|
||||
return undefined
|
||||
}
|
||||
|
||||
const observer = new window.IntersectionObserver((entries) => {
|
||||
entries.forEach((entry) => {
|
||||
if (!entry.isIntersecting || entry.intersectionRatio < 0.4) {
|
||||
return
|
||||
}
|
||||
|
||||
trackWorldSourceImpression({
|
||||
worldId: world.id,
|
||||
worldTitle: world.title,
|
||||
sourceSurface,
|
||||
sourceDetail,
|
||||
sectionKey: compact ? 'compact_card' : 'card',
|
||||
})
|
||||
observer.disconnect()
|
||||
})
|
||||
}, { threshold: [0.4] })
|
||||
|
||||
observer.observe(node)
|
||||
|
||||
return () => observer.disconnect()
|
||||
}, [compact, sourceDetail, sourceSurface, world?.id, world?.title])
|
||||
|
||||
if (!world) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<a
|
||||
href={world.public_url}
|
||||
ref={cardRef}
|
||||
href={href}
|
||||
onClick={() => trackWorldSourceClick({ worldId: world.id, worldTitle: world.title, sourceSurface, sourceDetail })}
|
||||
className={`group relative block w-full overflow-hidden rounded-[28px] border border-white/10 bg-slate-950/70 transition duration-300 hover:-translate-y-1 hover:border-white/20 ${compact ? 'p-5' : 'p-6'}`}
|
||||
style={themeStyle(world.theme)}
|
||||
>
|
||||
@@ -24,25 +75,30 @@ export default function WorldCard({ world, compact = false }) {
|
||||
|
||||
<div className="relative flex h-full min-h-[16rem] flex-col justify-between">
|
||||
<div>
|
||||
<div className="flex flex-wrap items-center gap-2 text-[11px] font-semibold uppercase tracking-[0.22em] text-white/70">
|
||||
<span className="rounded-full border border-white/15 bg-white/10 px-3 py-1">{world.phase || world.status}</span>
|
||||
{world.badge_label ? <span className="rounded-full border border-white/15 bg-black/30 px-3 py-1">{world.badge_label}</span> : null}
|
||||
{world.is_recurring ? (
|
||||
<div className="text-[11px] font-semibold uppercase tracking-[0.2em] text-sky-100/70">
|
||||
{world.family_title || 'Recurring family'}
|
||||
{world.edition_label ? <span className="ml-2 text-slate-300/70">{world.edition_label}</span> : null}
|
||||
</div>
|
||||
) : null}
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
{(Array.isArray(world.status_badges) ? world.status_badges : []).map((badge) => (
|
||||
<WorldStatusBadge key={badge.label} badge={badge} />
|
||||
))}
|
||||
{!Array.isArray(world.status_badges) || world.status_badges.length === 0 ? <WorldStatusBadge badge={{ label: world.phase || world.status, tone: 'slate' }} /> : null}
|
||||
{world.campaign_label ? <WorldStatusBadge badge={{ label: world.campaign_label, tone: 'slate' }} /> : null}
|
||||
{world.badge_label ? <WorldStatusBadge badge={{ label: world.badge_label, tone: 'rose' }} /> : null}
|
||||
</div>
|
||||
<h3 className={`mt-4 max-w-xl font-semibold tracking-[-0.03em] text-white ${compact ? 'text-2xl' : 'text-3xl'}`}>{world.title}</h3>
|
||||
{world.teaser_title && world.teaser_title !== world.title ? <p className="mt-4 text-[11px] font-semibold uppercase tracking-[0.2em] text-sky-100/70">{world.title}</p> : null}
|
||||
<h3 className={`mt-4 max-w-xl font-semibold tracking-[-0.03em] text-white ${compact ? 'text-2xl' : 'text-3xl'}`}>{world.teaser_title || world.title}</h3>
|
||||
{world.tagline ? <p className="mt-2 text-sm uppercase tracking-[0.18em] text-white/55">{world.tagline}</p> : null}
|
||||
{world.summary ? <p className="mt-4 max-w-2xl text-sm leading-6 text-slate-200/85">{world.summary}</p> : null}
|
||||
</div>
|
||||
|
||||
<div className="mt-6 flex flex-wrap items-end justify-between gap-3">
|
||||
<div className="space-y-1 text-sm text-slate-200/80">
|
||||
{world.timeframe_label ? <div>{world.timeframe_label}</div> : null}
|
||||
<div className="flex items-center gap-2 text-xs uppercase tracking-[0.16em] text-white/55">
|
||||
<i className={world.icon_name || 'fa-solid fa-globe'} />
|
||||
<span>{world.theme?.label || world.type}</span>
|
||||
</div>
|
||||
</div>
|
||||
<WorldCampaignMeta world={world} />
|
||||
<span className="inline-flex items-center gap-2 rounded-full border border-white/15 bg-white/10 px-4 py-2 text-sm font-semibold text-white transition group-hover:bg-white/15">
|
||||
{world.cta_label || 'Open world'}
|
||||
{world.cta_label || world.challenge_cta_label || (world.is_recurring && !world.is_canonical_edition ? 'Open edition' : 'Open world')}
|
||||
<i className="fa-solid fa-arrow-right" />
|
||||
</span>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user