diff --git a/client/pages/DevLinkProfile.tsx b/client/pages/DevLinkProfile.tsx new file mode 100644 index 00000000..4ea40ec6 --- /dev/null +++ b/client/pages/DevLinkProfile.tsx @@ -0,0 +1,316 @@ +import Layout from "@/components/Layout"; +import { Button } from "@/components/ui/button"; +import { Badge } from "@/components/ui/badge"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { useParams, useNavigate } from "react-router-dom"; +import { useEffect, useState } from "react-router-dom"; +import { + Github, + Globe, + Mail, + ArrowLeft, + ExternalLink, + MessageSquare, + Share2, + MapPin, + Trophy, +} from "lucide-react"; +import { supabase } from "@/lib/supabase"; + +interface DevProfile { + id: string; + user_id: string; + full_name: string; + avatar_url?: string; + bio?: string; + skills: string[]; + experience_level: "beginner" | "intermediate" | "advanced" | "expert"; + looking_for?: string; + portfolio_url?: string; + github_url?: string; + email?: string; + city?: string; + country?: string; + created_at: string; +} + +export default function DevLinkProfile() { + const { profileId } = useParams<{ profileId: string }>(); + const navigate = useNavigate(); + const [profile, setProfile] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + const fetchProfile = async () => { + if (!profileId) { + setError("Profile not found"); + setLoading(false); + return; + } + + try { + const { data, error: fetchError } = await supabase + .from("profiles") + .select("*") + .eq("id", profileId) + .single(); + + if (fetchError) throw fetchError; + + const devProfile: DevProfile = { + id: data.id, + user_id: data.user_id, + full_name: data.full_name || "Anonymous Developer", + avatar_url: data.avatar_url, + bio: data.bio, + skills: data.interests || [], + experience_level: data.experience_level || "intermediate", + looking_for: data.looking_for, + portfolio_url: data.portfolio_url, + github_url: data.github_url, + email: data.email, + city: data.city, + country: data.country, + created_at: data.created_at, + }; + + setProfile(devProfile); + } catch (err) { + console.error("Error fetching profile:", err); + setError("Failed to load profile"); + } finally { + setLoading(false); + } + }; + + fetchProfile(); + }, [profileId]); + + const getExperienceColor = (level: string) => { + const colors: Record = { + beginner: "bg-blue-500/20 text-blue-300 border-blue-400/40", + intermediate: "bg-cyan-500/20 text-cyan-300 border-cyan-400/40", + advanced: "bg-violet-500/20 text-violet-300 border-violet-400/40", + expert: "bg-amber-500/20 text-amber-300 border-amber-400/40", + }; + return colors[level] || colors.intermediate; + }; + + if (loading) { + return ( + +
+
+

Loading profile...

+
+ + ); + } + + if (error || !profile) { + return ( + +
+
+
+

+ {error || "Profile not found"} +

+ +
+
+ + ); + } + + return ( + +
+ {/* Animated backgrounds */} +
+
+
+
+
+ +
+ {/* Header */} +
+
+ + + {/* Profile Card */} + + +
+ {profile.avatar_url && ( + {profile.full_name} + )} +
+
+
+ + {profile.full_name} + + + {profile.experience_level} Developer + +
+
+ + +
+
+ + {(profile.city || profile.country) && ( +
+ + + {profile.city && profile.country + ? `${profile.city}, ${profile.country}` + : profile.city || profile.country} + +
+ )} +
+
+
+ + + {/* Bio */} + {profile.bio && ( +
+

+ About +

+

+ {profile.bio} +

+
+ )} + + {/* Looking For */} + {profile.looking_for && ( +
+

+ Currently Looking For +

+

+ + {profile.looking_for} +

+
+ )} + + {/* Skills */} + {profile.skills && profile.skills.length > 0 && ( +
+

+ Skills +

+
+ {profile.skills.map((skill) => ( + + {skill} + + ))} +
+
+ )} + + {/* Links */} + {(profile.github_url || + profile.portfolio_url || + profile.email) && ( +
+

+ Connect +

+
+ {profile.github_url && ( + + + GitHub + + + )} + {profile.portfolio_url && ( + + + Portfolio + + + )} + {profile.email && ( + + + Email + + )} +
+
+ )} + + {/* Member Since */} +
+

+ Member since{" "} + {new Date(profile.created_at).toLocaleDateString()} +

+
+
+
+
+
+
+
+ + ); +} diff --git a/client/pages/DevLinkProfiles.tsx b/client/pages/DevLinkProfiles.tsx new file mode 100644 index 00000000..529e84ef --- /dev/null +++ b/client/pages/DevLinkProfiles.tsx @@ -0,0 +1,291 @@ +import Layout from "@/components/Layout"; +import { Button } from "@/components/ui/button"; +import { Badge } from "@/components/ui/badge"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Input } from "@/components/ui/input"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { + Users, + Search, + Github, + Briefcase, + MapPin, + Trophy, + MessageSquare, +} from "lucide-react"; +import { useEffect, useState } from "react"; +import { useNavigate } from "react-router-dom"; +import { supabase } from "@/lib/supabase"; + +interface DevProfile { + id: string; + user_id: string; + full_name: string; + avatar_url?: string; + bio?: string; + skills: string[]; + experience_level: "beginner" | "intermediate" | "advanced" | "expert"; + looking_for?: string; + portfolio_url?: string; + github_url?: string; + created_at: string; +} + +export default function DevLinkProfiles() { + const [profiles, setProfiles] = useState([]); + const [filteredProfiles, setFilteredProfiles] = useState([]); + const [loading, setLoading] = useState(true); + const [searchTerm, setSearchTerm] = useState(""); + const [experienceFilter, setExperienceFilter] = useState("all"); + const navigate = useNavigate(); + + useEffect(() => { + const fetchProfiles = async () => { + try { + const { data, error } = await supabase + .from("profiles") + .select("*") + .eq("deleted_at", null) + .order("created_at", { ascending: false }) + .limit(50); + + if (error) throw error; + + const devProfiles: DevProfile[] = data + .map((p: any) => ({ + id: p.id, + user_id: p.user_id, + full_name: p.full_name || "Anonymous Developer", + avatar_url: p.avatar_url, + bio: p.bio, + skills: p.interests || [], + experience_level: p.experience_level || "intermediate", + looking_for: p.looking_for, + portfolio_url: p.portfolio_url, + github_url: p.github_url, + created_at: p.created_at, + })) + .filter((p) => p.full_name !== "Anonymous Developer" || p.bio); + + setProfiles(devProfiles); + setFilteredProfiles(devProfiles); + } catch (error) { + console.error("Error fetching profiles:", error); + } finally { + setLoading(false); + } + }; + + fetchProfiles(); + }, []); + + useEffect(() => { + let filtered = profiles; + + if (searchTerm) { + filtered = filtered.filter( + (p) => + p.full_name.toLowerCase().includes(searchTerm.toLowerCase()) || + p.bio?.toLowerCase().includes(searchTerm.toLowerCase()) || + p.skills.some((s) => + s.toLowerCase().includes(searchTerm.toLowerCase()) + ) + ); + } + + if (experienceFilter !== "all") { + filtered = filtered.filter((p) => p.experience_level === experienceFilter); + } + + setFilteredProfiles(filtered); + }, [searchTerm, experienceFilter, profiles]); + + return ( + +
+ {/* Animated backgrounds */} +
+
+
+
+
+ +
+ {/* Header Section */} +
+
+
+
+

+ Developer Profiles +

+

+ Connect with {profiles.length} talented Roblox developers +

+
+ +
+ + {/* Search and Filter */} +
+
+ + setSearchTerm(e.target.value)} + className="pl-10 bg-cyan-950/30 border-cyan-400/30 text-white placeholder:text-cyan-200/40 focus:border-cyan-400" + /> +
+ +
+
+
+ + {/* Profiles Grid */} +
+
+ {loading ? ( +
+

Loading profiles...

+
+ ) : filteredProfiles.length === 0 ? ( +
+ +

+ No profiles found. Try adjusting your filters. +

+
+ ) : ( +
+ {filteredProfiles.map((profile) => ( + navigate(`/dev-link/profiles/${profile.id}`)} + > + + {profile.avatar_url && ( + {profile.full_name} + )} + + {profile.full_name} + + + {profile.experience_level} + + + + {profile.bio && ( +

+ {profile.bio} +

+ )} + + {profile.skills && profile.skills.length > 0 && ( +
+

+ Skills +

+
+ {profile.skills.slice(0, 3).map((skill) => ( + + {skill} + + ))} + {profile.skills.length > 3 && ( + + +{profile.skills.length - 3} + + )} +
+
+ )} + + {profile.looking_for && ( +
+ + + {profile.looking_for} + +
+ )} + +
+ {profile.github_url && ( + e.stopPropagation()} + > + + + )} + {profile.portfolio_url && ( + e.stopPropagation()} + > + + + )} + +
+
+
+ ))} +
+ )} +
+
+
+
+ + ); +}