feat: forum rich-text editor, emoji picker, mentions, discover nav, feed, uploads, profile
Forum: - TipTap WYSIWYG editor with full toolbar - @emoji-mart/react emoji picker (consistent with tweets) - @mention autocomplete with user search API - Fix PHP 8.4 parse errors in Blade templates - Fix thread data display (paginator items) - Align forum page widths to max-w-5xl Discover: - Extract shared _nav.blade.php partial - Add missing nav links to for-you page - Add Following link for authenticated users Feed/Posts: - Post model, controllers, policies, migrations - Feed page components (PostComposer, FeedCard, etc) - Post reactions, comments, saves, reports, sharing - Scheduled publishing support - Link preview controller Profile: - Profile page components (ProfileHero, ProfileTabs) - Profile API controller Uploads: - Upload wizard enhancements - Scheduled publish picker - Studio status bar and readiness checklist
This commit is contained in:
177
resources/js/Pages/Profile/ProfileShow.jsx
Normal file
177
resources/js/Pages/Profile/ProfileShow.jsx
Normal file
@@ -0,0 +1,177 @@
|
||||
import React, { useState, useEffect, useCallback } from 'react'
|
||||
import { usePage } from '@inertiajs/react'
|
||||
import ProfileHero from '../../Components/Profile/ProfileHero'
|
||||
import ProfileStatsRow from '../../Components/Profile/ProfileStatsRow'
|
||||
import ProfileTabs from '../../Components/Profile/ProfileTabs'
|
||||
import TabArtworks from '../../Components/Profile/tabs/TabArtworks'
|
||||
import TabAbout from '../../Components/Profile/tabs/TabAbout'
|
||||
import TabStats from '../../Components/Profile/tabs/TabStats'
|
||||
import TabFavourites from '../../Components/Profile/tabs/TabFavourites'
|
||||
import TabCollections from '../../Components/Profile/tabs/TabCollections'
|
||||
import TabActivity from '../../Components/Profile/tabs/TabActivity'
|
||||
import TabPosts from '../../Components/Profile/tabs/TabPosts'
|
||||
|
||||
const VALID_TABS = ['artworks', 'posts', 'collections', 'about', 'stats', 'favourites', 'activity']
|
||||
|
||||
function getInitialTab() {
|
||||
try {
|
||||
const sp = new URLSearchParams(window.location.search)
|
||||
const t = sp.get('tab')
|
||||
return VALID_TABS.includes(t) ? t : 'artworks'
|
||||
} catch {
|
||||
return 'artworks'
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ProfileShow – Inertia page for /@username
|
||||
*
|
||||
* Props injected by ProfileController::renderUserProfile()
|
||||
*/
|
||||
export default function ProfileShow() {
|
||||
const { props } = usePage()
|
||||
|
||||
const {
|
||||
user,
|
||||
profile,
|
||||
artworks,
|
||||
featuredArtworks,
|
||||
favourites,
|
||||
stats,
|
||||
socialLinks,
|
||||
followerCount,
|
||||
recentFollowers,
|
||||
viewerIsFollowing,
|
||||
heroBgUrl,
|
||||
profileComments,
|
||||
countryName,
|
||||
isOwner,
|
||||
auth,
|
||||
} = props
|
||||
|
||||
const [activeTab, setActiveTab] = useState(getInitialTab)
|
||||
|
||||
const handleTabChange = useCallback((tab) => {
|
||||
if (!VALID_TABS.includes(tab)) return
|
||||
setActiveTab(tab)
|
||||
|
||||
// Update URL query param without full navigation
|
||||
try {
|
||||
const url = new URL(window.location.href)
|
||||
if (tab === 'artworks') {
|
||||
url.searchParams.delete('tab')
|
||||
} else {
|
||||
url.searchParams.set('tab', tab)
|
||||
}
|
||||
window.history.pushState({}, '', url.toString())
|
||||
} catch (_) {}
|
||||
}, [])
|
||||
|
||||
// Handle browser back/forward
|
||||
useEffect(() => {
|
||||
const onPop = () => setActiveTab(getInitialTab())
|
||||
window.addEventListener('popstate', onPop)
|
||||
return () => window.removeEventListener('popstate', onPop)
|
||||
}, [])
|
||||
|
||||
const isLoggedIn = !!(auth?.user)
|
||||
|
||||
// Normalise artwork list (SSR may send cursor-paginated object)
|
||||
const artworkList = Array.isArray(artworks)
|
||||
? artworks
|
||||
: (artworks?.data ?? [])
|
||||
const artworkNextCursor = artworks?.next_cursor ?? null
|
||||
|
||||
// Normalise social links (may be object keyed by platform, or array)
|
||||
const socialLinksObj = Array.isArray(socialLinks)
|
||||
? socialLinks.reduce((acc, l) => { acc[l.platform] = l; return acc }, {})
|
||||
: (socialLinks ?? {})
|
||||
|
||||
return (
|
||||
<div className="min-h-screen pb-16">
|
||||
{/* Hero section */}
|
||||
<ProfileHero
|
||||
user={user}
|
||||
profile={profile}
|
||||
isOwner={isOwner}
|
||||
viewerIsFollowing={viewerIsFollowing}
|
||||
followerCount={followerCount}
|
||||
heroBgUrl={heroBgUrl}
|
||||
countryName={countryName}
|
||||
/>
|
||||
|
||||
{/* Stats pills row */}
|
||||
<ProfileStatsRow
|
||||
stats={stats}
|
||||
followerCount={followerCount}
|
||||
onTabChange={handleTabChange}
|
||||
/>
|
||||
|
||||
{/* Sticky tabs */}
|
||||
<ProfileTabs
|
||||
activeTab={activeTab}
|
||||
onTabChange={handleTabChange}
|
||||
/>
|
||||
|
||||
{/* Tab content area */}
|
||||
<div className="max-w-6xl mx-auto px-4">
|
||||
{activeTab === 'artworks' && (
|
||||
<TabArtworks
|
||||
artworks={{ data: artworkList, next_cursor: artworkNextCursor }}
|
||||
featuredArtworks={featuredArtworks}
|
||||
username={user.username || user.name}
|
||||
isActive
|
||||
/>
|
||||
)}
|
||||
{activeTab === 'posts' && (
|
||||
<TabPosts
|
||||
username={user.username || user.name}
|
||||
isOwner={isOwner}
|
||||
authUser={auth?.user ?? null}
|
||||
user={user}
|
||||
profile={profile}
|
||||
stats={stats}
|
||||
followerCount={followerCount}
|
||||
recentFollowers={recentFollowers}
|
||||
socialLinks={socialLinksObj}
|
||||
countryName={countryName}
|
||||
onTabChange={handleTabChange}
|
||||
/>
|
||||
)}
|
||||
{activeTab === 'collections' && (
|
||||
<TabCollections collections={[]} />
|
||||
)}
|
||||
{activeTab === 'about' && (
|
||||
<TabAbout
|
||||
user={user}
|
||||
profile={profile}
|
||||
socialLinks={socialLinksObj}
|
||||
countryName={countryName}
|
||||
followerCount={followerCount}
|
||||
/>
|
||||
)}
|
||||
{activeTab === 'stats' && (
|
||||
<TabStats
|
||||
stats={stats}
|
||||
followerCount={followerCount}
|
||||
/>
|
||||
)}
|
||||
{activeTab === 'favourites' && (
|
||||
<TabFavourites
|
||||
favourites={favourites}
|
||||
isOwner={isOwner}
|
||||
username={user.username || user.name}
|
||||
/>
|
||||
)}
|
||||
{activeTab === 'activity' && (
|
||||
<TabActivity
|
||||
profileComments={profileComments}
|
||||
user={user}
|
||||
isOwner={isOwner}
|
||||
isLoggedIn={isLoggedIn}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user