import React, { useEffect, useRef, useState } from 'react' import { router, usePage } from '@inertiajs/react' import StudioLayout from '../../Layouts/StudioLayout' async function requestJson(url, method, body) { const response = await fetch(url, { method, credentials: 'same-origin', headers: { Accept: 'application/json', 'Content-Type': 'application/json', 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || '', 'X-Requested-With': 'XMLHttpRequest', }, body: body ? JSON.stringify(body) : undefined, }) const payload = await response.json().catch(() => ({})) if (!response.ok) { throw new Error(payload?.message || payload?.error || 'Request failed') } return payload } async function uploadFile(url, fieldName, file, extra = {}) { const formData = new FormData() formData.append(fieldName, file) Object.entries(extra).forEach(([key, value]) => { formData.append(key, String(value)) }) const response = await fetch(url, { method: 'POST', credentials: 'same-origin', headers: { Accept: 'application/json', 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || '', 'X-Requested-With': 'XMLHttpRequest', }, body: formData, }) const payload = await response.json().catch(() => ({})) if (!response.ok) { throw new Error(payload?.message || payload?.error || 'Upload failed') } return payload } function socialPlatformLabel(value) { return value .split(/[_-]/) .filter(Boolean) .map((part) => part.charAt(0).toUpperCase() + part.slice(1)) .join(' ') } export default function StudioProfile() { const { props } = usePage() const profile = props.profile || {} const endpoints = props.endpoints || {} const featuredContent = props.featuredContent || {} const featuredModules = props.featuredModules || [] const avatarInputRef = useRef(null) const coverInputRef = useRef(null) const [form, setForm] = useState({ display_name: profile.name || '', tagline: profile.tagline || '', bio: profile.bio || '', website: profile.website || '', social_links: (profile.social_links || []).length > 0 ? profile.social_links : [{ platform: '', url: '' }], }) const [coverPosition, setCoverPosition] = useState(profile.cover_position ?? 50) const [savingProfile, setSavingProfile] = useState(false) const [uploadingAvatar, setUploadingAvatar] = useState(false) const [uploadingCover, setUploadingCover] = useState(false) const [savingCoverPosition, setSavingCoverPosition] = useState(false) const [deletingCover, setDeletingCover] = useState(false) useEffect(() => { setForm({ display_name: profile.name || '', tagline: profile.tagline || '', bio: profile.bio || '', website: profile.website || '', social_links: (profile.social_links || []).length > 0 ? profile.social_links : [{ platform: '', url: '' }], }) setCoverPosition(profile.cover_position ?? 50) }, [profile.bio, profile.cover_position, profile.name, profile.social_links, profile.tagline, profile.website]) const updateSocialLink = (index, key, value) => { setForm((current) => ({ ...current, social_links: current.social_links.map((link, linkIndex) => ( linkIndex === index ? { ...link, [key]: value } : link )), })) } const addSocialLink = () => { setForm((current) => ({ ...current, social_links: [...current.social_links, { platform: '', url: '' }], })) } const removeSocialLink = (index) => { setForm((current) => ({ ...current, social_links: current.social_links.filter((_, linkIndex) => linkIndex !== index), })) } const saveProfile = async () => { setSavingProfile(true) try { await requestJson(endpoints.profile, 'PUT', { display_name: form.display_name, tagline: form.tagline || null, bio: form.bio || null, website: form.website || null, social_links: form.social_links.filter((link) => link.platform.trim() && link.url.trim()), }) router.reload({ preserveScroll: true, preserveState: true }) } catch (error) { window.alert(error?.message || 'Unable to save profile.') } finally { setSavingProfile(false) } } const handleAvatarSelected = async (event) => { const file = event.target.files?.[0] if (!file) return setUploadingAvatar(true) try { await uploadFile(endpoints.avatarUpload, 'avatar', file, { avatar_position: 'center' }) router.reload({ preserveScroll: true, preserveState: true }) } catch (error) { window.alert(error?.message || 'Unable to upload avatar.') } finally { event.target.value = '' setUploadingAvatar(false) } } const handleCoverSelected = async (event) => { const file = event.target.files?.[0] if (!file) return setUploadingCover(true) try { await uploadFile(endpoints.coverUpload, 'cover', file) router.reload({ preserveScroll: true, preserveState: true }) } catch (error) { window.alert(error?.message || 'Unable to upload cover image.') } finally { event.target.value = '' setUploadingCover(false) } } const saveCoverPosition = async () => { setSavingCoverPosition(true) try { await requestJson(endpoints.coverPosition, 'POST', { position: coverPosition }) router.reload({ preserveScroll: true, preserveState: true }) } catch (error) { window.alert(error?.message || 'Unable to update cover position.') } finally { setSavingCoverPosition(false) } } const deleteCover = async () => { if (!window.confirm('Remove your current banner image?')) { return } setDeletingCover(true) try { await requestJson(endpoints.coverDelete, 'DELETE') router.reload({ preserveScroll: true, preserveState: true }) } catch (error) { window.alert(error?.message || 'Unable to delete cover image.') } finally { setDeletingCover(false) } } return (
Creator identity
{profile.cover_url && ( )} View public profile
{profile.avatar_url ? ( {profile.username} ) : (
)}

{profile.name}

@{profile.username}

{Number(profile.followers || 0).toLocaleString()} followers {profile.location && {profile.location}}
{profile.cover_url && (
setCoverPosition(Number(event.target.value))} className="mt-3 w-full" />
)}

Public profile details

Update the creator information that supports your public presence across Nova.