CreatorProfile page - view individual creator's profile
cgen-44d16224e7e047cf8d1954b27ce93904
This commit is contained in:
parent
8c0e463419
commit
b322efa514
1 changed files with 244 additions and 0 deletions
244
client/pages/creators/CreatorProfile.tsx
Normal file
244
client/pages/creators/CreatorProfile.tsx
Normal file
|
|
@ -0,0 +1,244 @@
|
|||
import { useState, useEffect } from "react";
|
||||
import { useParams, useNavigate } from "react-router-dom";
|
||||
import Layout from "@/components/Layout";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Loader2, ArrowLeft, ExternalLink, MessageSquare } from "lucide-react";
|
||||
import { getCreatorByUsername } from "@/api/creators";
|
||||
import { ArmBadge } from "@/components/creator-network/ArmBadge";
|
||||
import type { Creator } from "@/api/creators";
|
||||
|
||||
export default function CreatorProfile() {
|
||||
const { username } = useParams<{ username: string }>();
|
||||
const navigate = useNavigate();
|
||||
const [creator, setCreator] = useState<Creator | null>(null);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchCreator = async () => {
|
||||
if (!username) return;
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const data = await getCreatorByUsername(username);
|
||||
setCreator(data);
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch creator:", error);
|
||||
setCreator(null);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchCreator();
|
||||
}, [username]);
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<Layout>
|
||||
<div className="flex items-center justify-center min-h-screen">
|
||||
<Loader2 className="h-8 w-8 animate-spin text-purple-400" />
|
||||
</div>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
|
||||
if (!creator) {
|
||||
return (
|
||||
<Layout>
|
||||
<div className="min-h-screen bg-black text-white flex items-center justify-center">
|
||||
<div className="text-center">
|
||||
<h1 className="text-3xl font-bold mb-4">Creator Not Found</h1>
|
||||
<p className="text-gray-400 mb-6">The creator you're looking for doesn't exist.</p>
|
||||
<Button onClick={() => navigate("/creators")}>
|
||||
<ArrowLeft className="h-4 w-4 mr-2" />
|
||||
Back to Creators
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<div className="relative min-h-screen bg-black text-white overflow-hidden">
|
||||
{/* Background */}
|
||||
<div className="pointer-events-none absolute inset-0 opacity-[0.12] [background-image:radial-gradient(circle_at_top,#8b5cf6_0,rgba(0,0,0,0.45)_55%,rgba(0,0,0,0.9)_100%)]" />
|
||||
<div className="pointer-events-none absolute inset-0 bg-[linear-gradient(transparent_0,transparent_calc(100%-1px),rgba(139,92,246,0.05)_calc(100%-1px))] bg-[length:100%_32px]" />
|
||||
|
||||
<main className="relative z-10">
|
||||
<div className="container mx-auto max-w-4xl px-4 py-12">
|
||||
{/* Header */}
|
||||
<div className="mb-8">
|
||||
<Button
|
||||
onClick={() => navigate("/creators")}
|
||||
variant="ghost"
|
||||
className="mb-4"
|
||||
>
|
||||
<ArrowLeft className="h-4 w-4 mr-2" />
|
||||
Back to Creators
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Profile Card */}
|
||||
<Card className="bg-slate-800/50 border-slate-700 mb-8">
|
||||
<CardContent className="p-8">
|
||||
<div className="flex flex-col md:flex-row items-start md:items-center gap-6 mb-6">
|
||||
<Avatar className="h-24 w-24">
|
||||
<AvatarImage src={creator.avatar_url} alt={creator.username} />
|
||||
<AvatarFallback>
|
||||
{creator.username.charAt(0).toUpperCase()}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center gap-3 mb-2">
|
||||
<h1 className="text-3xl font-bold">@{creator.username}</h1>
|
||||
{creator.devconnect_linked && (
|
||||
<Badge className="bg-cyan-500/10 text-cyan-300 border-cyan-500/20">
|
||||
<ExternalLink className="h-3 w-3 mr-1" />
|
||||
On DevConnect
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-gray-300 text-lg mb-4">{creator.bio}</p>
|
||||
|
||||
<div className="flex flex-wrap gap-2 mb-4">
|
||||
{creator.primary_arm && (
|
||||
<ArmBadge arm={creator.primary_arm} />
|
||||
)}
|
||||
{creator.arm_affiliations &&
|
||||
creator.arm_affiliations
|
||||
.filter((arm) => arm !== creator.primary_arm)
|
||||
.map((arm) => (
|
||||
<ArmBadge key={arm} arm={arm} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="flex gap-3">
|
||||
{creator.devconnect_link && (
|
||||
<Button asChild>
|
||||
<a
|
||||
href={creator.devconnect_link.devconnect_profile_url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<ExternalLink className="h-4 w-4 mr-2" />
|
||||
View on DevConnect
|
||||
</a>
|
||||
</Button>
|
||||
)}
|
||||
<Button variant="outline">
|
||||
<MessageSquare className="h-4 w-4 mr-2" />
|
||||
Message
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Skills Section */}
|
||||
{creator.skills && creator.skills.length > 0 && (
|
||||
<Card className="bg-slate-800/50 border-slate-700 mb-8">
|
||||
<CardHeader>
|
||||
<CardTitle>Skills & Expertise</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{creator.skills.map((skill) => (
|
||||
<Badge
|
||||
key={skill}
|
||||
className="bg-slate-700/50 text-gray-300 border-0 px-3 py-1.5 text-sm"
|
||||
>
|
||||
{skill}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* Projects Section */}
|
||||
{creator.aethex_projects && creator.aethex_projects.length > 0 && (
|
||||
<Card className="bg-slate-800/50 border-slate-700 mb-8">
|
||||
<CardHeader>
|
||||
<CardTitle>Portfolio Projects</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
{creator.aethex_projects.map((project) => (
|
||||
<Card
|
||||
key={project.id}
|
||||
className="bg-slate-700/30 border-slate-600"
|
||||
>
|
||||
<CardContent className="p-4">
|
||||
{project.image_url && (
|
||||
<img
|
||||
src={project.image_url}
|
||||
alt={project.title}
|
||||
className="w-full h-40 object-cover rounded-lg mb-3"
|
||||
/>
|
||||
)}
|
||||
<h3 className="font-bold text-white mb-1">
|
||||
{project.title}
|
||||
</h3>
|
||||
<p className="text-sm text-gray-400 mb-3">
|
||||
{project.description}
|
||||
</p>
|
||||
{project.url && (
|
||||
<Button
|
||||
asChild
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="w-full"
|
||||
>
|
||||
<a
|
||||
href={project.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
View Project
|
||||
<ExternalLink className="h-3 w-3 ml-2" />
|
||||
</a>
|
||||
</Button>
|
||||
)}
|
||||
{project.tags && project.tags.length > 0 && (
|
||||
<div className="flex flex-wrap gap-1 mt-3">
|
||||
{project.tags.map((tag) => (
|
||||
<span
|
||||
key={tag}
|
||||
className="text-xs bg-slate-600/50 text-gray-300 px-2 py-1 rounded"
|
||||
>
|
||||
{tag}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* Experience Level */}
|
||||
<Card className="bg-slate-800/50 border-slate-700">
|
||||
<CardHeader>
|
||||
<CardTitle>Experience Level</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Badge className="bg-slate-700/50 text-gray-300 border-0 px-3 py-1.5 text-sm">
|
||||
{creator.experience_level || "Not specified"}
|
||||
</Badge>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
Loading…
Reference in a new issue