Wire admin studio SSR and search infrastructure

This commit is contained in:
2026-05-01 11:46:06 +02:00
parent 257b0dbef6
commit 18cea8b0f0
329 changed files with 197465 additions and 2741 deletions

View File

@@ -15,6 +15,7 @@ const baseNavGroups = [
label: 'Create',
items: [
{ label: 'New Artwork', href: '/upload', icon: 'fa-solid fa-cloud-arrow-up' },
{ label: 'Upload Queue', href: '/studio/upload-queue', icon: 'fa-solid fa-layer-group' },
{ label: 'New Card', href: '/studio/cards/create', icon: 'fa-solid fa-id-card' },
{ label: 'New Story', href: '/creator/stories/create', icon: 'fa-solid fa-feather-pointed' },
{ label: 'New Collection', href: '/settings/collections/create', icon: 'fa-solid fa-layer-group' },
@@ -34,6 +35,7 @@ const baseNavGroups = [
label: 'Library',
items: [
{ label: 'Drafts', href: '/studio/drafts', icon: 'fa-solid fa-file-pen' },
{ label: 'Upload Queue', href: '/studio/upload-queue', icon: 'fa-solid fa-list-check' },
{ label: 'Scheduled', href: '/studio/scheduled', icon: 'fa-solid fa-calendar-days' },
{ label: 'Calendar', href: '/studio/calendar', icon: 'fa-solid fa-calendar-range' },
{ label: 'Archived', href: '/studio/archived', icon: 'fa-solid fa-box-archive' },
@@ -168,25 +170,40 @@ export default function StudioLayout({ children, title, subtitle, actions }) {
const studioGroups = Array.isArray(props.studio_groups) ? props.studio_groups : []
const currentGroup = props.studioGroup || null
const canManageNews = Boolean(props.auth?.user?.is_admin || props.auth?.user?.is_moderator)
const canManageWorlds = canManageNews
const navGroups = baseNavGroups.map((group) => {
if (!canManageNews || group.label !== 'Content') {
if ((!canManageNews && !canManageWorlds) || group.label !== 'Content') {
return group
}
const extraItems = []
if (canManageNews) {
extraItems.push({ label: 'News', href: '/studio/news', icon: 'fa-solid fa-newspaper' })
}
if (canManageWorlds) {
extraItems.push({ label: 'Worlds', href: '/studio/worlds', icon: 'fa-solid fa-globe' })
}
return {
...group,
items: [
...group.items,
{ label: 'News', href: '/studio/news', icon: 'fa-solid fa-newspaper' },
],
items: [...group.items, ...extraItems],
}
})
const quickCreateItems = (canManageNews
? [...baseQuickCreateItems, { label: 'News Article', href: '/studio/news/create', icon: 'fa-solid fa-newspaper' }]
: baseQuickCreateItems
).map((item) => {
const quickCreatePool = [...baseQuickCreateItems]
if (canManageNews) {
quickCreatePool.push({ label: 'News Article', href: '/studio/news/create', icon: 'fa-solid fa-newspaper' })
}
if (canManageWorlds) {
quickCreatePool.push({ label: 'World', href: '/studio/worlds/create', icon: 'fa-solid fa-globe' })
}
const quickCreateItems = quickCreatePool.map((item) => {
if (currentGroup?.urls && item.label === 'Artwork') {
return { ...item, href: currentGroup.urls?.studio_artworks ? `/upload?group=${currentGroup.slug}` : item.href }
}

View File

@@ -1,10 +1,12 @@
import React, { useState } from 'react'
import { usePage } from '@inertiajs/react'
import ProfileCoverEditor from './ProfileCoverEditor'
import LevelBadge from '../xp/LevelBadge'
import XPProgressBar from '../xp/XPProgressBar'
import FollowButton from '../social/FollowButton'
import FollowersPreview from '../social/FollowersPreview'
import MutualFollowersBadge from '../social/MutualFollowersBadge'
import { shinyFlagUrl } from '../../utils/flagUrl'
function formatCompactNumber(value) {
const numeric = Number(value ?? 0)
@@ -12,11 +14,13 @@ function formatCompactNumber(value) {
}
export default function ProfileHero({ user, profile, isOwner, viewerIsFollowing, followerCount, recentFollowers = [], followContext = null, heroBgUrl, countryName, leaderboardRank, extraActions = null }) {
const { props } = usePage()
const [following, setFollowing] = useState(viewerIsFollowing)
const [count, setCount] = useState(followerCount)
const [editorOpen, setEditorOpen] = useState(false)
const [coverUrl, setCoverUrl] = useState(user?.cover_url || heroBgUrl || null)
const [coverPosition, setCoverPosition] = useState(Number.isFinite(user?.cover_position) ? user.cover_position : 50)
const flagUrl = shinyFlagUrl(profile?.country_code, props?.cdn?.files_url)
const uname = user.username || user.name || 'Unknown'
const displayName = user.name || uname
@@ -118,9 +122,9 @@ export default function ProfileHero({ user, profile, isOwner, viewerIsFollowing,
{!isOwner ? <MutualFollowersBadge context={followContext} /> : null}
{countryName ? (
<span className="inline-flex items-center gap-1.5 rounded-full border border-white/10 bg-white/5 px-3 py-1.5 text-xs text-slate-300">
{profile?.country_code ? (
{flagUrl ? (
<img
src={`/gfx/flags/shiny/24/${encodeURIComponent(String(profile.country_code).toUpperCase())}.png`}
src={flagUrl}
alt={countryName}
className="h-auto w-4 rounded-sm"
onError={(event) => { event.target.style.display = 'none' }}

View File

@@ -1,4 +1,6 @@
import React from 'react'
import { usePage } from '@inertiajs/react'
import { shinyFlagUrl } from '../../../utils/flagUrl'
const SOCIAL_ICONS = {
twitter: { icon: 'fa-brands fa-x-twitter', label: 'X / Twitter' },
@@ -170,10 +172,12 @@ function SectionCard({ icon, eyebrow, title, children, className = '' }) {
* Bio, social links, metadata - replaces old sidebar profile card.
*/
export default function TabAbout({ user, profile, stats, achievements, artworks, creatorStories, profileComments, socialLinks, countryName, followerCount, recentFollowers, leaderboardRank, groupContributionHistory }) {
const { props } = usePage()
const uname = user.username || user.name
const displayName = user.name || uname
const about = profile?.about
const website = profile?.website
const flagUrl = shinyFlagUrl(profile?.country_code, props?.cdn?.files_url)
const joinDate = user.created_at
? new Date(user.created_at).toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric' })
@@ -250,9 +254,9 @@ export default function TabAbout({ user, profile, stats, achievements, artworks,
{countryName ? (
<InfoRow icon="fa-earth-americas" label="Country">
<span className="flex items-center gap-2">
{profile?.country_code ? (
{flagUrl ? (
<img
src={`/gfx/flags/shiny/24/${encodeURIComponent(String(profile.country_code).toUpperCase())}.png`}
src={flagUrl}
alt={countryName}
className="h-auto w-4 rounded-sm"
onError={(e) => { e.target.style.display = 'none' }}