240 lines
8.9 KiB
TypeScript
240 lines
8.9 KiB
TypeScript
import Layout from "@/components/Layout";
|
|
import { useAuth } from "@/contexts/AuthContext";
|
|
import { useEffect, useMemo, useState } from "react";
|
|
import { useNavigate } from "react-router-dom";
|
|
import LoadingScreen from "@/components/LoadingScreen";
|
|
import {
|
|
Card,
|
|
CardContent,
|
|
CardDescription,
|
|
CardHeader,
|
|
CardTitle,
|
|
} from "@/components/ui/card";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
|
import { Badge } from "@/components/ui/badge";
|
|
import { aethexSocialService } from "@/lib/aethex-social-service";
|
|
import { UserPlus, UserCheck } from "lucide-react";
|
|
|
|
export default function Network() {
|
|
const { user, profile, loading } = useAuth();
|
|
const navigate = useNavigate();
|
|
const [isLoading, setIsLoading] = useState(true);
|
|
const [recommended, setRecommended] = useState<any[]>([]);
|
|
const [following, setFollowing] = useState<string[]>([]);
|
|
|
|
useEffect(() => {
|
|
if (!loading && !user) {
|
|
navigate("/login", { replace: true });
|
|
return;
|
|
}
|
|
if (!user) return;
|
|
|
|
const load = async () => {
|
|
setIsLoading(true);
|
|
try {
|
|
const recs = await aethexSocialService.listRecommended(user.id, 12);
|
|
setRecommended(recs);
|
|
const flw = await aethexSocialService.getFollowing(user.id);
|
|
setFollowing(flw);
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
load();
|
|
}, [user, loading, navigate]);
|
|
|
|
const isFollowing = (id: string) => following.includes(id);
|
|
|
|
const toggleFollow = async (targetId: string) => {
|
|
if (!user) return;
|
|
if (isFollowing(targetId)) {
|
|
await aethexSocialService.unfollowUser(user.id, targetId);
|
|
setFollowing((s) => s.filter((x) => x !== targetId));
|
|
} else {
|
|
await aethexSocialService.followUser(user.id, targetId);
|
|
setFollowing((s) => Array.from(new Set([...s, targetId])));
|
|
}
|
|
};
|
|
|
|
if (loading || isLoading) {
|
|
return (
|
|
<LoadingScreen
|
|
message="Loading your network..."
|
|
showProgress
|
|
duration={1000}
|
|
/>
|
|
);
|
|
}
|
|
|
|
if (!user) return null;
|
|
|
|
return (
|
|
<Layout>
|
|
<div className="min-h-screen bg-aethex-gradient py-8">
|
|
<div className="container mx-auto px-4 max-w-7xl grid grid-cols-1 lg:grid-cols-12 gap-6">
|
|
{/* Public Profile */}
|
|
<div className="lg:col-span-4 space-y-6">
|
|
<Card className="bg-card/50 border-border/50">
|
|
<CardContent className="p-6 space-y-4">
|
|
<div className="flex items-center gap-4">
|
|
<Avatar className="h-16 w-16">
|
|
<AvatarImage src={profile?.avatar_url} />
|
|
<AvatarFallback>
|
|
{profile?.full_name?.[0] ||
|
|
user.email?.[0]?.toUpperCase()}
|
|
</AvatarFallback>
|
|
</Avatar>
|
|
<div>
|
|
<h2 className="text-xl font-semibold">
|
|
{profile?.full_name || user.email?.split("@")[0]}
|
|
</h2>
|
|
<p className="text-sm text-muted-foreground">
|
|
{profile?.role || "Member"}
|
|
</p>
|
|
<div className="mt-2 flex gap-2">
|
|
<Badge
|
|
variant="outline"
|
|
className="border-aethex-400/50 text-aethex-400"
|
|
>
|
|
Level {profile?.level || 1}
|
|
</Badge>
|
|
<Badge variant="outline">
|
|
{(profile as any)?.experience_level || "beginner"}
|
|
</Badge>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{profile?.bio && (
|
|
<p className="text-sm text-muted-foreground">{profile.bio}</p>
|
|
)}
|
|
<div className="flex gap-2">
|
|
<Button
|
|
variant="outline"
|
|
onClick={() => navigate("/dashboard")}
|
|
>
|
|
Edit Profile
|
|
</Button>
|
|
<Button
|
|
variant="outline"
|
|
onClick={() => navigate("/onboarding")}
|
|
>
|
|
Improve Profile
|
|
</Button>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card className="bg-card/50 border-border/50">
|
|
<CardHeader>
|
|
<CardTitle>Recommendations</CardTitle>
|
|
<CardDescription>
|
|
People who align with your interests
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent className="space-y-3">
|
|
{recommended.slice(0, 3).map((r) => (
|
|
<div key={r.id} className="flex items-center justify-between">
|
|
<div className="flex items-center gap-3">
|
|
<Avatar className="h-10 w-10">
|
|
<AvatarImage src={r.avatar_url} />
|
|
<AvatarFallback>
|
|
{(r.full_name || r.username || "U")[0]}
|
|
</AvatarFallback>
|
|
</Avatar>
|
|
<div>
|
|
<div className="font-medium">
|
|
{r.full_name || r.username}
|
|
</div>
|
|
<div className="text-xs text-muted-foreground">
|
|
{r.bio?.slice(0, 40) || "Member"}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<Button
|
|
size="sm"
|
|
variant={isFollowing(r.id) ? "outline" : "default"}
|
|
onClick={() => toggleFollow(r.id)}
|
|
>
|
|
{isFollowing(r.id) ? (
|
|
<span className="flex items-center gap-1">
|
|
<UserCheck className="h-4 w-4" /> Following
|
|
</span>
|
|
) : (
|
|
<span className="flex items-center gap-1">
|
|
<UserPlus className="h-4 w-4" /> Follow
|
|
</span>
|
|
)}
|
|
</Button>
|
|
</div>
|
|
))}
|
|
{recommended.length === 0 && (
|
|
<div className="text-sm text-muted-foreground">
|
|
No recommendations yet.
|
|
</div>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
|
|
{/* Discover People */}
|
|
<div className="lg:col-span-8">
|
|
<Card className="bg-card/50 border-border/50">
|
|
<CardHeader>
|
|
<CardTitle>Discover People</CardTitle>
|
|
<CardDescription>
|
|
Connect with creators, clients, and members
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
{recommended.map((r) => (
|
|
<div
|
|
key={r.id}
|
|
className="flex items-center justify-between p-3 rounded-lg border border-border/50 hover:border-aethex-400/50 transition-all"
|
|
>
|
|
<div className="flex items-center gap-3">
|
|
<Avatar className="h-12 w-12">
|
|
<AvatarImage src={r.avatar_url} />
|
|
<AvatarFallback>
|
|
{(r.full_name || r.username || "U")[0]}
|
|
</AvatarFallback>
|
|
</Avatar>
|
|
<div>
|
|
<div className="font-semibold">
|
|
{r.full_name || r.username}
|
|
</div>
|
|
<div className="text-xs text-muted-foreground">
|
|
{r.bio?.slice(0, 80) || "Member"}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<Button
|
|
size="sm"
|
|
variant={isFollowing(r.id) ? "outline" : "default"}
|
|
onClick={() => toggleFollow(r.id)}
|
|
>
|
|
{isFollowing(r.id) ? (
|
|
<span className="flex items-center gap-1">
|
|
<UserCheck className="h-4 w-4" /> Following
|
|
</span>
|
|
) : (
|
|
<span className="flex items-center gap-1">
|
|
<UserPlus className="h-4 w-4" /> Follow
|
|
</span>
|
|
)}
|
|
</Button>
|
|
</div>
|
|
))}
|
|
{recommended.length === 0 && (
|
|
<div className="text-sm text-muted-foreground">
|
|
No people found yet.
|
|
</div>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</Layout>
|
|
);
|
|
}
|