From 80527af5e409a5b1bf29be4af594dd23bc08bff4 Mon Sep 17 00:00:00 2001 From: "Builder.io" Date: Tue, 11 Nov 2025 23:10:35 +0000 Subject: [PATCH] Ethos artist settings - Manage profile, skills, pricing cgen-b300efe22cb949c896a3daac62c47064 --- client/pages/ethos/ArtistSettings.tsx | 405 ++++++++++++++++++++++++++ 1 file changed, 405 insertions(+) create mode 100644 client/pages/ethos/ArtistSettings.tsx diff --git a/client/pages/ethos/ArtistSettings.tsx b/client/pages/ethos/ArtistSettings.tsx new file mode 100644 index 00000000..1dc95f53 --- /dev/null +++ b/client/pages/ethos/ArtistSettings.tsx @@ -0,0 +1,405 @@ +import { useEffect, useState } from "react"; +import { useNavigate } from "react-router-dom"; +import Layout from "@/components/Layout"; +import SEO from "@/components/SEO"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Textarea } from "@/components/ui/textarea"; +import { Checkbox } from "@/components/ui/checkbox"; +import { useAuth } from "@/contexts/AuthContext"; +import TrackUploadModal from "@/components/ethos/TrackUploadModal"; +import TrackMetadataForm from "@/components/ethos/TrackMetadataForm"; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +import { Badge } from "@/components/ui/badge"; +import { useAethexToast } from "@/hooks/use-aethex-toast"; +import { Upload, Music, Settings } from "lucide-react"; + +const SKILLS = [ + "Synthwave", + "Orchestral", + "SFX Design", + "Game Audio", + "Ambient", + "Electronic", + "Cinematic", + "Jazz", + "Hip-Hop", + "Folk", +]; + +interface ArtistProfile { + skills: string[]; + for_hire: boolean; + bio?: string; + portfolio_url?: string; + sample_price_track?: number; + sample_price_sfx?: number; + sample_price_score?: number; + turnaround_days?: number; +} + +export default function ArtistSettings() { + const { user } = useAuth(); + const navigate = useNavigate(); + const toast = useAethexToast(); + + const [profile, setProfile] = useState({ + skills: [], + for_hire: true, + }); + const [loading, setLoading] = useState(true); + const [saving, setSaving] = useState(false); + const [uploadModalOpen, setUploadModalOpen] = useState(false); + const [currentFile, setCurrentFile] = useState(null); + const [showMetadataForm, setShowMetadataForm] = useState(false); + + useEffect(() => { + if (!user) { + navigate("/login"); + return; + } + + const fetchProfile = async () => { + try { + const res = await fetch(`/api/ethos/artists?id=${user.id}`, { + headers: { "x-user-id": user.id }, + }); + + if (res.ok) { + const data = await res.json(); + setProfile({ + skills: data.skills || [], + for_hire: data.for_hire ?? true, + bio: data.bio, + portfolio_url: data.portfolio_url, + sample_price_track: data.sample_price_track, + sample_price_sfx: data.sample_price_sfx, + sample_price_score: data.sample_price_score, + turnaround_days: data.turnaround_days, + }); + } + } catch (error) { + console.error("Failed to fetch profile:", error); + } finally { + setLoading(false); + } + }; + + fetchProfile(); + }, [user, navigate]); + + const toggleSkill = (skill: string) => { + setProfile((prev) => ({ + ...prev, + skills: prev.skills.includes(skill) + ? prev.skills.filter((s) => s !== skill) + : [...prev.skills, skill], + })); + }; + + const handleSave = async () => { + if (!user) return; + + setSaving(true); + try { + const res = await fetch(`/api/ethos/artists`, { + method: "PUT", + headers: { + "x-user-id": user.id, + "Content-Type": "application/json", + }, + body: JSON.stringify(profile), + }); + + if (res.ok) { + toast.success({ + title: "Profile updated", + description: "Your Ethos artist profile has been saved", + }); + } else { + throw new Error("Failed to save profile"); + } + } catch (error) { + toast.error({ + title: "Error", + description: String(error), + }); + } finally { + setSaving(false); + } + }; + + const handleFileSelected = (file: File) => { + setCurrentFile(file); + setShowMetadataForm(true); + }; + + const handleMetadataSubmit = async (metadata: any) => { + if (!user || !currentFile) return; + + try { + // TODO: Upload file to Supabase Storage + // For now, just create track record with placeholder file_url + const res = await fetch(`/api/ethos/tracks`, { + method: "POST", + headers: { + "x-user-id": user.id, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + ...metadata, + file_url: `ethos-tracks/${user.id}/${currentFile.name}`, + duration_seconds: Math.floor(currentFile.size / 16000), // Rough estimate + }), + }); + + if (res.ok) { + toast.success({ + title: "Track uploaded", + description: "Your track has been added to your portfolio", + }); + setShowMetadataForm(false); + setCurrentFile(null); + } else { + throw new Error("Failed to upload track"); + } + } catch (error) { + toast.error({ + title: "Error", + description: String(error), + }); + } + }; + + if (loading) { + return
Loading settings...
; + } + + return ( + <> + + +
+
+
+ {/* Header */} +
+

+ + Artist Settings +

+

+ Manage your Ethos Guild profile, portfolio, and services +

+
+ + {/* Profile Section */} + + + Profile Information + + +
+ +