Commit workspace changes

This commit is contained in:
2026-04-05 19:42:33 +02:00
parent 148a3bbe43
commit 08ad757bcb
312 changed files with 35149 additions and 399 deletions

View File

@@ -2,9 +2,46 @@ import React from 'react'
import ArtworkBreadcrumbs from './ArtworkBreadcrumbs'
export default function ArtworkMeta({ artwork }) {
const publisher = artwork?.publisher || null
const credits = artwork?.credits || {}
const primaryAuthor = credits?.primary_author || artwork?.user || null
const contributors = Array.isArray(credits?.contributors) ? credits.contributors : []
return (
<div>
<h1 className="text-2xl font-bold tracking-tight text-white sm:text-3xl">{artwork?.title}</h1>
<div className="mt-4 flex flex-wrap gap-3 text-sm text-slate-300">
{publisher?.type === 'group' ? (
<a href={publisher.profile_url} className="inline-flex items-center gap-2 rounded-full border border-sky-300/20 bg-sky-300/10 px-3 py-1.5 text-sky-100 transition hover:border-sky-300/35 hover:bg-sky-300/15">
<span className="text-[11px] font-semibold uppercase tracking-[0.18em] text-sky-100/80">Published by</span>
<span className="font-semibold">{publisher.name}</span>
</a>
) : null}
{primaryAuthor ? (
<span className="inline-flex items-center gap-2 rounded-full border border-white/10 bg-white/[0.04] px-3 py-1.5">
<span className="text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-400">Primary author</span>
{primaryAuthor.profile_url ? <a href={primaryAuthor.profile_url} className="font-semibold text-white hover:text-sky-200">{primaryAuthor.name || primaryAuthor.username}</a> : <span className="font-semibold text-white">{primaryAuthor.name || primaryAuthor.username}</span>}
</span>
) : null}
{contributors.length > 0 ? (
<div className="flex flex-wrap gap-2">
<span className="inline-flex items-center gap-2 rounded-full border border-white/10 bg-white/[0.04] px-3 py-1.5 text-white">
<span className="text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-400">Contributors</span>
</span>
{contributors.map((item) => {
const label = item.name || item.username
return (
<span key={item.id || label} className="inline-flex items-center gap-2 rounded-full border border-white/10 bg-white/[0.04] px-3 py-1.5 text-white">
{item.profile_url ? <a href={item.profile_url} className="font-semibold text-white hover:text-sky-200">{label}</a> : <span className="font-semibold text-white">{label}</span>}
{item.credit_role ? <span className="text-slate-400">{item.credit_role}</span> : null}
{item.is_primary ? <span className="rounded-full border border-emerald-300/30 bg-emerald-400/12 px-2 py-0.5 text-[10px] font-semibold uppercase tracking-[0.16em] text-emerald-100">Lead support</span> : null}
</span>
)
})}
</div>
) : null}
</div>
<div className="mt-3">
<ArtworkBreadcrumbs artwork={artwork} />
</div>

View File

@@ -22,14 +22,16 @@ function toCard(item) {
}
export default function CreatorSpotlight({ artwork, presentSq, related = [] }) {
const [following, setFollowing] = useState(Boolean(artwork?.viewer?.is_following_author))
const [followersCount, setFollowersCount] = useState(Number(artwork?.user?.followers_count || 0))
const publisher = artwork?.publisher || null
const isGroupPublisher = publisher?.type === 'group'
const [following, setFollowing] = useState(Boolean(isGroupPublisher ? artwork?.viewer?.is_following_group : artwork?.viewer?.is_following_author))
const [followersCount, setFollowersCount] = useState(Number(isGroupPublisher ? publisher?.followers_count || 0 : artwork?.user?.followers_count || 0))
const user = artwork?.user || {}
const user = artwork?.credits?.primary_author || artwork?.user || {}
const isOwnArtwork = Number(artwork?.viewer?.id || 0) > 0 && Number(artwork?.viewer?.id) === Number(user.id || 0)
const authorName = user.name || user.username || 'Artist'
const profileUrl = user.profile_url || (user.username ? `/@${user.username}` : '#')
const avatar = user.avatar_url || presentSq?.url || AVATAR_FALLBACK
const authorName = isGroupPublisher ? (publisher?.name || 'Group') : (user.name || user.username || 'Artist')
const profileUrl = isGroupPublisher ? (publisher?.profile_url || '#') : (user.profile_url || (user.username ? `/@${user.username}` : '#'))
const avatar = (isGroupPublisher ? publisher?.avatar_url : user.avatar_url) || presentSq?.url || AVATAR_FALLBACK
const creatorItems = useMemo(() => {
const filtered = (Array.isArray(related) ? related : []).filter((item) => {
@@ -63,7 +65,8 @@ export default function CreatorSpotlight({ artwork, presentSq, related = [] }) {
<a href={profileUrl} className="mt-3 block text-base font-bold text-white transition-colors hover:text-accent">
{authorName}
</a>
{user.username && <p className="text-xs text-white/40">@{user.username}</p>}
{!isGroupPublisher && user.username && <p className="text-xs text-white/40">@{user.username}</p>}
{isGroupPublisher && artwork?.credits?.primary_author ? <p className="text-xs text-white/40">Primary author: {artwork.credits.primary_author.name || artwork.credits.primary_author.username}</p> : null}
<p className="mt-1 text-xs font-medium text-white/30">
{followersCount.toLocaleString()} Followers
</p>
@@ -80,7 +83,7 @@ export default function CreatorSpotlight({ artwork, presentSq, related = [] }) {
</svg>
Profile
</a>
{!isOwnArtwork ? (
{!isOwnArtwork && !isGroupPublisher ? (
<FollowButton
username={user.username}
initialFollowing={following}
@@ -93,6 +96,31 @@ export default function CreatorSpotlight({ artwork, presentSq, related = [] }) {
}}
/>
) : null}
{!isOwnArtwork && isGroupPublisher ? (
<button
type="button"
onClick={async () => {
const method = following ? 'DELETE' : 'POST'
const response = await fetch(following ? artwork.publisher?.unfollow_url || `${publisher.profile_url}/follow` : artwork.publisher?.follow_url || `${publisher.profile_url}/follow`, {
method,
credentials: 'same-origin',
headers: {
Accept: 'application/json',
'X-Requested-With': 'XMLHttpRequest',
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || '',
},
})
const payload = await response.json().catch(() => ({}))
if (response.ok) {
setFollowing(Boolean(payload?.following))
setFollowersCount(Number(payload?.followers_count || 0))
}
}}
className={`flex-1 rounded-xl border px-3 py-2.5 text-sm font-medium transition ${following ? 'border-white/[0.12] bg-white/[0.05] text-white' : 'border-sky-300/25 bg-sky-300/10 text-sky-100 hover:border-sky-300/35 hover:bg-sky-300/15'}`}
>
{following ? 'Following' : 'Follow group'}
</button>
) : null}
</div>
</div>
@@ -100,7 +128,7 @@ export default function CreatorSpotlight({ artwork, presentSq, related = [] }) {
{creatorItems.length > 0 && (
<div className="mt-5 border-t border-white/[0.06] pt-5">
<div className="flex items-center justify-between">
<h3 className="text-sm font-semibold text-white/80">More from {authorName}</h3>
<h3 className="text-sm font-semibold text-white/80">{isGroupPublisher ? 'More related works' : `More from ${authorName}`}</h3>
<a href={profileUrl} className="text-white/30 transition-colors hover:text-white/60">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={2} stroke="currentColor" className="h-4 w-4">
<path strokeLinecap="round" strokeLinejoin="round" d="m8.25 4.5 7.5 7.5-7.5 7.5" />