148 lines
5.2 KiB
TypeScript
148 lines
5.2 KiB
TypeScript
import { useEffect, useState } from 'react'
|
|
import { useParams } from 'react-router-dom'
|
|
import { Users, Headphones } from 'lucide-react'
|
|
import { supabase } from '@/lib/supabase'
|
|
import { useAuthStore } from '@/stores/auth'
|
|
import type { Profile as ProfileType, Podcast } from '@/types'
|
|
import { Avatar } from '@/components/ui/Avatar'
|
|
import { Button } from '@/components/ui/Button'
|
|
import { PodcastCard } from '@/components/podcast/PodcastCard'
|
|
|
|
export function Profile() {
|
|
const { username } = useParams<{ username: string }>()
|
|
const { user } = useAuthStore()
|
|
const [profile, setProfile] = useState<ProfileType | null>(null)
|
|
const [podcasts, setPodcasts] = useState<Podcast[]>([])
|
|
const [isFollowing, setIsFollowing] = useState(false)
|
|
const [followersCount, setFollowersCount] = useState(0)
|
|
const [followingCount, setFollowingCount] = useState(0)
|
|
const [loading, setLoading] = useState(true)
|
|
|
|
const isOwn = user && profile && user.id === profile.id
|
|
|
|
useEffect(() => {
|
|
if (!username) return
|
|
async function load() {
|
|
const { data: profileData } = await supabase
|
|
.from('profiles')
|
|
.select('*')
|
|
.eq('username', username)
|
|
.single()
|
|
|
|
if (!profileData) { setLoading(false); return }
|
|
setProfile(profileData)
|
|
|
|
const [podcastsRes, followersRes, followingRes] = await Promise.all([
|
|
supabase
|
|
.from('podcasts')
|
|
.select('*, creator:profiles(*), tags:podcast_tags(tag:tags(*))')
|
|
.eq('creator_id', profileData.id)
|
|
.order('created_at', { ascending: false }),
|
|
supabase.from('follows').select('*', { count: 'exact', head: true }).eq('following_id', profileData.id),
|
|
supabase.from('follows').select('*', { count: 'exact', head: true }).eq('follower_id', profileData.id),
|
|
])
|
|
|
|
if (podcastsRes.data) {
|
|
setPodcasts(podcastsRes.data.map((p: any) => ({
|
|
...p,
|
|
tags: p.tags?.map((t: any) => t.tag).filter(Boolean) || [],
|
|
})))
|
|
}
|
|
setFollowersCount(followersRes.count || 0)
|
|
setFollowingCount(followingRes.count || 0)
|
|
|
|
if (user) {
|
|
const { data: follow } = await supabase
|
|
.from('follows')
|
|
.select('*')
|
|
.eq('follower_id', user.id)
|
|
.eq('following_id', profileData.id)
|
|
.maybeSingle()
|
|
setIsFollowing(!!follow)
|
|
}
|
|
|
|
setLoading(false)
|
|
}
|
|
load()
|
|
}, [username, user])
|
|
|
|
async function handleFollow() {
|
|
if (!user || !profile) return
|
|
if (isFollowing) {
|
|
await supabase.from('follows').delete().eq('follower_id', user.id).eq('following_id', profile.id)
|
|
setIsFollowing(false)
|
|
setFollowersCount((c) => c - 1)
|
|
} else {
|
|
await supabase.from('follows').insert({ follower_id: user.id, following_id: profile.id })
|
|
setIsFollowing(true)
|
|
setFollowersCount((c) => c + 1)
|
|
}
|
|
}
|
|
|
|
if (loading) {
|
|
return (
|
|
<div className="max-w-3xl mx-auto space-y-6">
|
|
<div className="flex items-center gap-6">
|
|
<div className="w-20 h-20 rounded-full bg-border-light animate-pulse" />
|
|
<div className="space-y-2 flex-1">
|
|
<div className="h-6 w-40 bg-border-light rounded animate-pulse" />
|
|
<div className="h-4 w-60 bg-border-light rounded animate-pulse" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
if (!profile) {
|
|
return <div className="text-center py-16 text-text-secondary">Utilisateur introuvable.</div>
|
|
}
|
|
|
|
return (
|
|
<div className="max-w-3xl mx-auto space-y-8">
|
|
<div className="flex flex-col sm:flex-row items-start gap-6">
|
|
<Avatar src={profile.avatar_url} name={profile.username} size="lg" />
|
|
<div className="flex-1">
|
|
<div className="flex items-center gap-3 flex-wrap">
|
|
<h1 className="text-2xl font-heading font-bold">{profile.username}</h1>
|
|
{profile.is_premium && (
|
|
<span className="bg-primary/10 text-primary text-xs font-semibold px-2.5 py-0.5 rounded-full">PRO</span>
|
|
)}
|
|
</div>
|
|
{profile.bio && <p className="text-text-secondary text-sm mt-1">{profile.bio}</p>}
|
|
|
|
<div className="flex items-center gap-5 mt-3 text-sm text-text-secondary">
|
|
<span className="flex items-center gap-1"><Headphones size={14} />{podcasts.length} podcasts</span>
|
|
<span className="flex items-center gap-1"><Users size={14} />{followersCount} abonnés</span>
|
|
<span>{followingCount} abonnements</span>
|
|
</div>
|
|
|
|
{!isOwn && user && (
|
|
<div className="mt-4">
|
|
<Button
|
|
variant={isFollowing ? 'secondary' : 'primary'}
|
|
size="sm"
|
|
onClick={handleFollow}
|
|
>
|
|
{isFollowing ? 'Abonné' : 'S\'abonner'}
|
|
</Button>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<h2 className="text-lg font-heading font-bold mb-4">Podcasts</h2>
|
|
{podcasts.length === 0 ? (
|
|
<p className="text-text-secondary text-sm text-center py-8">Aucun podcast publié.</p>
|
|
) : (
|
|
<div className="grid grid-cols-2 sm:grid-cols-3 gap-4">
|
|
{podcasts.map((p) => (
|
|
<PodcastCard key={p.id} podcast={p} />
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|