diff --git a/.replit b/.replit index 324ed4f9..6856b9ea 100644 --- a/.replit +++ b/.replit @@ -61,7 +61,7 @@ localPort = 40437 externalPort = 3001 [[ports]] -localPort = 44257 +localPort = 43879 externalPort = 3002 [deployment] diff --git a/client/api/creators.ts b/client/api/creators.ts index a738fbfd..6206abb7 100644 --- a/client/api/creators.ts +++ b/client/api/creators.ts @@ -51,7 +51,7 @@ export interface CreatorsResponse { }; } -const getApiBase = () => +const API_BASE = typeof window !== "undefined" ? window.location.origin : ""; export async function getCreators(filters?: { @@ -169,9 +169,7 @@ export async function endorseSkill( creatorId: string, skill: string, ): Promise { - const apiBase = getApiBase(); - if (!apiBase) throw new Error("No API base available"); - const response = await fetch(`${apiBase}/api/creators/${creatorId}/endorse`, { + const response = await fetch(`${API_BASE}/api/creators/${creatorId}/endorse`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ skill }), diff --git a/client/pages/Dashboard.tsx b/client/pages/Dashboard.tsx index 2fe45c78..db2c9176 100644 --- a/client/pages/Dashboard.tsx +++ b/client/pages/Dashboard.tsx @@ -180,6 +180,10 @@ export default function Dashboard() { const [twitter, setTwitter] = useState(""); const [savingProfile, setSavingProfile] = useState(false); const [profileLinkCopied, setProfileLinkCopied] = useState(false); + const [selectedRealm, setSelectedRealm] = useState(null); + const [selectedExperience, setSelectedExperience] = useState("intermediate"); + const [realmHasChanges, setRealmHasChanges] = useState(false); + const [savingRealm, setSavingRealm] = useState(false); useEffect(() => { if (profile) { @@ -189,9 +193,47 @@ export default function Dashboard() { setLinkedin(profile.linkedin_url || ""); setGithub(profile.github_url || ""); setTwitter(profile.twitter_url || ""); + setSelectedRealm((profile as any).primary_realm || null); + setSelectedExperience((profile as any).experience_level || "intermediate"); } }, [profile]); + const handleRealmChange = (realm: RealmKey) => { + setSelectedRealm(realm); + setRealmHasChanges(true); + }; + + const handleExperienceChange = (value: string) => { + setSelectedExperience(value); + setRealmHasChanges(true); + }; + + const handleSaveRealm = async () => { + if (!user || !selectedRealm) return; + setSavingRealm(true); + try { + const { error } = await (window as any).supabaseClient + .from("user_profiles") + .update({ + primary_realm: selectedRealm, + experience_level: selectedExperience, + }) + .eq("id", user.id); + + if (error) throw error; + aethexToast.success({ + description: "Realm preference saved!", + }); + setRealmHasChanges(false); + } catch (error: any) { + aethexToast.error({ + description: error?.message || "Failed to save realm preference", + }); + } finally { + setSavingRealm(false); + } + }; + const handleSaveProfile = async () => { if (!user) return; setSavingProfile(true); @@ -209,14 +251,12 @@ export default function Dashboard() { .eq("id", user.id); if (error) throw error; - aethexToast({ - message: "Profile updated successfully!", - type: "success", + aethexToast.success({ + description: "Profile updated successfully!", }); } catch (error: any) { - aethexToast({ - message: error?.message || "Failed to update profile", - type: "error", + aethexToast.error({ + description: error?.message || "Failed to update profile", }); } finally { setSavingProfile(false); @@ -607,35 +647,111 @@ export default function Dashboard() { {/* Settings Tab */} + {/* Realm Preference - Full Width */} + + + Realm & Experience + + Choose your primary area of focus and experience level + + + + + + +
- {/* Realm Preference */} + {/* Notifications */} - Primary Realm + + + Notifications + - Choose your primary area of focus + Manage your notification preferences - - + +
+
+

Email Notifications

+

Receive updates via email

+
+ Coming Soon +
+
+
+

Community Updates

+

New posts and mentions

+
+ Coming Soon +
+
+
+

Project Alerts

+

Updates on your projects

+
+ Coming Soon +
{/* Account Actions */} - Account Actions - Manage your account + + + Account Actions + + Manage your account settings + +
+ +
diff --git a/client/pages/creators/CreatorDirectory.tsx b/client/pages/creators/CreatorDirectory.tsx index 521e5f4d..48c4e517 100644 --- a/client/pages/creators/CreatorDirectory.tsx +++ b/client/pages/creators/CreatorDirectory.tsx @@ -1,15 +1,51 @@ import { useState, useEffect } from "react"; -import { useSearchParams } from "react-router-dom"; +import { useSearchParams, Link } from "react-router-dom"; import Layout from "@/components/Layout"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; -import { Loader2, Search, Users } from "lucide-react"; -import { getCreators } from "@/api/creators"; +import { Label } from "@/components/ui/label"; +import { Textarea } from "@/components/ui/textarea"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { Loader2, Search, Users, Plus, Sparkles } from "lucide-react"; +import { getCreators, createCreatorProfile } from "@/api/creators"; import { CreatorCard } from "@/components/creator-network/CreatorCard"; import { ArmFilter } from "@/components/creator-network/ArmFilter"; +import { useAuth } from "@/contexts/AuthContext"; +import { aethexToast } from "@/lib/aethex-toast"; import type { Creator } from "@/api/creators"; +const ARMS = [ + { value: "labs", label: "Labs - Research & Development" }, + { value: "gameforge", label: "GameForge - Game Development" }, + { value: "corp", label: "Corp - Business & Consulting" }, + { value: "foundation", label: "Foundation - Education" }, + { value: "devlink", label: "Dev-Link - Developer Network" }, + { value: "nexus", label: "Nexus - Talent Marketplace" }, +]; + +const EXPERIENCE_LEVELS = [ + { value: "beginner", label: "Beginner (0-1 years)" }, + { value: "intermediate", label: "Intermediate (1-3 years)" }, + { value: "advanced", label: "Advanced (3-5 years)" }, + { value: "expert", label: "Expert (5+ years)" }, +]; + export default function CreatorDirectory() { + const { user, profile } = useAuth(); const [searchParams, setSearchParams] = useSearchParams(); const [creators, setCreators] = useState([]); const [isLoading, setIsLoading] = useState(true); @@ -20,33 +56,52 @@ export default function CreatorDirectory() { const [page, setPage] = useState(parseInt(searchParams.get("page") || "1")); const [totalPages, setTotalPages] = useState(0); + const [isDialogOpen, setIsDialogOpen] = useState(false); + const [isSubmitting, setIsSubmitting] = useState(false); + const [formData, setFormData] = useState({ + username: "", + bio: "", + skills: "", + primary_arm: "", + experience_level: "", + }); + useEffect(() => { - const fetchCreators = async () => { - setIsLoading(true); - try { - const result = await getCreators({ - arm: selectedArm, - search: search || undefined, - page, - limit: 12, - }); - setCreators(result.data); - setTotalPages(result.pagination.pages); + if (profile) { + setFormData((prev) => ({ + ...prev, + username: profile.username || "", + bio: profile.bio || "", + })); + } + }, [profile]); - // Update URL params - const params = new URLSearchParams(); - if (selectedArm) params.set("arm", selectedArm); - if (search) params.set("search", search); - if (page > 1) params.set("page", String(page)); - setSearchParams(params); - } catch (error) { - console.error("Failed to fetch creators:", error); - setCreators([]); - } finally { - setIsLoading(false); - } - }; + const fetchCreators = async () => { + setIsLoading(true); + try { + const result = await getCreators({ + arm: selectedArm, + search: search || undefined, + page, + limit: 12, + }); + setCreators(result.data); + setTotalPages(result.pagination.pages); + const params = new URLSearchParams(); + if (selectedArm) params.set("arm", selectedArm); + if (search) params.set("search", search); + if (page > 1) params.set("page", String(page)); + setSearchParams(params); + } catch (error) { + console.error("Failed to fetch creators:", error); + setCreators([]); + } finally { + setIsLoading(false); + } + }; + + useEffect(() => { fetchCreators(); }, [selectedArm, search, page, setSearchParams]); @@ -60,10 +115,47 @@ export default function CreatorDirectory() { setPage(1); }; + const handleSubmitCreator = async (e: React.FormEvent) => { + e.preventDefault(); + if (!user) { + aethexToast.error({ description: "Please sign in to register as a creator" }); + return; + } + + if (!formData.username || !formData.bio || !formData.primary_arm) { + aethexToast.error({ description: "Please fill in all required fields" }); + return; + } + + setIsSubmitting(true); + try { + await createCreatorProfile({ + username: formData.username, + bio: formData.bio, + skills: formData.skills + .split(",") + .map((s) => s.trim()) + .filter(Boolean), + avatar_url: profile?.avatar_url || "", + experience_level: formData.experience_level || "beginner", + primary_arm: formData.primary_arm, + arm_affiliations: [formData.primary_arm], + }); + + aethexToast.success({ description: "You're now a creator! Welcome to the network." }); + setIsDialogOpen(false); + fetchCreators(); + } catch (error: any) { + console.error("Failed to create creator profile:", error); + aethexToast.error({ description: error?.message || "Failed to create creator profile" }); + } finally { + setIsSubmitting(false); + } + }; + return (
- {/* Background */}
@@ -71,7 +163,6 @@ export default function CreatorDirectory() {
- {/* Hero Section */}
@@ -81,13 +172,165 @@ export default function CreatorDirectory() { Creator Directory
-

+

Discover talented creators across all AeThex arms. Filter by specialty, skills, and arm affiliation.

+ + {user ? ( + + + + + + + + Join the Creator Network + + + Register as a creator to showcase your work and + connect with other builders. + + +
+
+ + + setFormData({ + ...formData, + username: e.target.value, + }) + } + placeholder="Your unique username" + className="bg-slate-800 border-slate-600" + required + /> +
+
+ +