Prettier format pending files

This commit is contained in:
Builder.io 2025-09-30 22:55:59 +00:00
parent 12127ff4ef
commit 5a7b56a792
9 changed files with 229 additions and 112 deletions

View file

@ -60,7 +60,10 @@ const App = () => (
element={<Navigate to="/feed" replace />} element={<Navigate to="/feed" replace />}
/> />
<Route path="/projects/new" element={<ProjectsNew />} /> <Route path="/projects/new" element={<ProjectsNew />} />
<Route path="/profile" element={<Navigate to="/profiles/me" replace />} /> <Route
path="/profile"
element={<Navigate to="/profiles/me" replace />}
/>
<Route path="/profiles" element={<ProfilesDirectory />} /> <Route path="/profiles" element={<ProfilesDirectory />} />
<Route path="/profiles/me" element={<ProfilePassport />} /> <Route path="/profiles/me" element={<ProfilePassport />} />
<Route path="/profiles/:id" element={<ProfilePassport />} /> <Route path="/profiles/:id" element={<ProfilePassport />} />

View file

@ -8,12 +8,7 @@ import {
CardTitle, CardTitle,
} from "@/components/ui/card"; } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { import { CheckCircle, ArrowRight, Sparkles, ShieldCheck } from "lucide-react";
CheckCircle,
ArrowRight,
Sparkles,
ShieldCheck,
} from "lucide-react";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import type { AethexAchievement } from "@/lib/aethex-database-adapter"; import type { AethexAchievement } from "@/lib/aethex-database-adapter";
@ -211,16 +206,23 @@ export default function Welcome({
</CardHeader> </CardHeader>
<CardContent className="space-y-3 text-sm text-emerald-100/90"> <CardContent className="space-y-3 text-sm text-emerald-100/90">
{achievement.description && <p>{achievement.description}</p>} {achievement.description && <p>{achievement.description}</p>}
{typeof achievement.xp_reward === "number" && achievement.xp_reward > 0 && ( {typeof achievement.xp_reward === "number" &&
<div className="text-xs uppercase tracking-wider text-emerald-200"> achievement.xp_reward > 0 && (
+{achievement.xp_reward} XP added to your passport progression <div className="text-xs uppercase tracking-wider text-emerald-200">
</div> +{achievement.xp_reward} XP added to your passport progression
)} </div>
)}
<div className="flex flex-wrap gap-2"> <div className="flex flex-wrap gap-2">
<Badge variant="outline" className="border-emerald-500/40 text-emerald-100"> <Badge
variant="outline"
className="border-emerald-500/40 text-emerald-100"
>
<ShieldCheck className="mr-1 h-3.5 w-3.5" /> Profile Verified <ShieldCheck className="mr-1 h-3.5 w-3.5" /> Profile Verified
</Badge> </Badge>
<Badge variant="outline" className="border-emerald-500/40 text-emerald-100"> <Badge
variant="outline"
className="border-emerald-500/40 text-emerald-100"
>
<Sparkles className="mr-1 h-3.5 w-3.5" /> Passport Updated <Sparkles className="mr-1 h-3.5 w-3.5" /> Passport Updated
</Badge> </Badge>
</div> </div>
@ -271,7 +273,10 @@ export default function Welcome({
variant="secondary" variant="secondary"
className="bg-aethex-500/20 text-aethex-100 border border-aethex-500/40" className="bg-aethex-500/20 text-aethex-100 border border-aethex-500/40"
> >
<Link to="/dashboard?tab=connections" className="flex items-center gap-2"> <Link
to="/dashboard?tab=connections"
className="flex items-center gap-2"
>
<ShieldCheck className="h-4 w-4" /> <ShieldCheck className="h-4 w-4" />
Link OAuth Accounts Link OAuth Accounts
</Link> </Link>

View file

@ -109,18 +109,29 @@ const PassportSummary = ({
<Badge <Badge
className={cn( className={cn(
"text-white shadow-lg", "text-white shadow-lg",
realm?.gradient ? `bg-gradient-to-r ${realm.gradient}` : "bg-aethex-500", realm?.gradient
? `bg-gradient-to-r ${realm.gradient}`
: "bg-aethex-500",
)} )}
> >
{realm?.label || "Innovation Commons"} {realm?.label || "Innovation Commons"}
</Badge> </Badge>
<Badge variant="outline" className="border-slate-600/60 text-slate-200"> <Badge
variant="outline"
className="border-slate-600/60 text-slate-200"
>
Level {level} Level {level}
</Badge> </Badge>
<Badge variant="outline" className="border-slate-600/60 text-slate-200"> <Badge
variant="outline"
className="border-slate-600/60 text-slate-200"
>
{totalXp} XP {totalXp} XP
</Badge> </Badge>
<Badge variant="outline" className="border-slate-600/60 text-slate-200"> <Badge
variant="outline"
className="border-slate-600/60 text-slate-200"
>
{loyaltyPoints} Loyalty {loyaltyPoints} Loyalty
</Badge> </Badge>
</div> </div>
@ -150,7 +161,8 @@ const PassportSummary = ({
<Sparkles className="h-4 w-4 text-aethex-300" /> <Sparkles className="h-4 w-4 text-aethex-300" />
</div> </div>
<p className="mt-2 text-sm text-slate-200"> <p className="mt-2 text-sm text-slate-200">
{(profile as any)?.user_type?.replace("_", " ") || "community member"} {(profile as any)?.user_type?.replace("_", " ") ||
"community member"}
</p> </p>
</div> </div>
<div className="rounded-xl border border-slate-800 bg-slate-900/40 p-4"> <div className="rounded-xl border border-slate-800 bg-slate-900/40 p-4">
@ -185,9 +197,7 @@ const PassportSummary = ({
className="rounded-xl border border-slate-800 bg-slate-900/40 p-4" className="rounded-xl border border-slate-800 bg-slate-900/40 p-4"
> >
<div className="flex items-center gap-2 text-slate-100"> <div className="flex items-center gap-2 text-slate-100">
<span className="text-xl"> <span className="text-xl">{achievement.icon || "✨"}</span>
{achievement.icon || "✨"}
</span>
<span className="text-sm font-medium"> <span className="text-sm font-medium">
{achievement.name} {achievement.name}
</span> </span>
@ -210,7 +220,11 @@ const PassportSummary = ({
</h3> </h3>
<div className="flex flex-wrap gap-2"> <div className="flex flex-wrap gap-2">
{interests.map((interest) => ( {interests.map((interest) => (
<Badge key={interest} variant="outline" className="border-slate-600/60 text-slate-200"> <Badge
key={interest}
variant="outline"
className="border-slate-600/60 text-slate-200"
>
{interest} {interest}
</Badge> </Badge>
))} ))}
@ -249,14 +263,20 @@ const PassportSummary = ({
<> <>
<Separator className="border-slate-800" /> <Separator className="border-slate-800" />
<div className="flex flex-wrap gap-3"> <div className="flex flex-wrap gap-3">
<Button asChild variant="outline" className="border-slate-700 text-slate-100"> <Button
asChild
variant="outline"
className="border-slate-700 text-slate-100"
>
<Link to="/dashboard?tab=profile">Edit Profile</Link> <Link to="/dashboard?tab=profile">Edit Profile</Link>
</Button> </Button>
<Button <Button
asChild asChild
className="bg-gradient-to-r from-aethex-500 to-neon-blue hover:from-aethex-600 hover:to-neon-blue/90" className="bg-gradient-to-r from-aethex-500 to-neon-blue hover:from-aethex-600 hover:to-neon-blue/90"
> >
<Link to="/dashboard?tab=connections">Manage Passport Links</Link> <Link to="/dashboard?tab=connections">
Manage Passport Links
</Link>
</Button> </Button>
</div> </div>
</> </>

View file

@ -131,7 +131,7 @@ export const aethexUserService = {
if (isTableMissing(error)) { if (isTableMissing(error)) {
throw new Error( throw new Error(
"Supabase table \"user_profiles\" is missing. Please run the required migrations.", 'Supabase table "user_profiles" is missing. Please run the required migrations.',
); );
} }
@ -172,7 +172,7 @@ export const aethexUserService = {
if (isTableMissing(error)) { if (isTableMissing(error)) {
throw new Error( throw new Error(
"Supabase table \"user_profiles\" is missing. Please run the required migrations.", 'Supabase table "user_profiles" is missing. Please run the required migrations.',
); );
} }
@ -194,18 +194,19 @@ export const aethexUserService = {
if (error) { if (error) {
if (isTableMissing(error)) { if (isTableMissing(error)) {
throw new Error( throw new Error(
"Supabase table \"user_profiles\" is missing. Please run the required migrations.", 'Supabase table "user_profiles" is missing. Please run the required migrations.',
); );
} }
throw error; throw error;
} }
return ((data as any[]) || []).map((row) => return ((data as any[]) || []).map(
({ (row) =>
...(row as AethexUserProfile), ({
user_type: (row as any).user_type || "community_member", ...(row as AethexUserProfile),
experience_level: (row as any).experience_level || "beginner", user_type: (row as any).user_type || "community_member",
}) as AethexUserProfile, experience_level: (row as any).experience_level || "beginner",
}) as AethexUserProfile,
); );
}, },
@ -238,7 +239,7 @@ export const aethexUserService = {
if (isTableMissing(error)) { if (isTableMissing(error)) {
throw new Error( throw new Error(
"Supabase table \"user_profiles\" is missing. Please run the required migrations.", 'Supabase table "user_profiles" is missing. Please run the required migrations.',
); );
} }
@ -280,7 +281,7 @@ export const aethexUserService = {
if (error) { if (error) {
if (isTableMissing(error)) { if (isTableMissing(error)) {
throw new Error( throw new Error(
"Supabase table \"user_profiles\" is missing. Please run the required migrations.", 'Supabase table "user_profiles" is missing. Please run the required migrations.',
); );
} }
throw error; throw error;
@ -315,7 +316,7 @@ export const aethexUserService = {
if (error) { if (error) {
if (isTableMissing(error)) { if (isTableMissing(error)) {
throw new Error( throw new Error(
"Supabase table \"user_interests\" is missing. Please run the required migrations.", 'Supabase table "user_interests" is missing. Please run the required migrations.',
); );
} }
throw error; throw error;
@ -333,7 +334,7 @@ export const aethexUserService = {
if (error) { if (error) {
if (isTableMissing(error)) { if (isTableMissing(error)) {
throw new Error( throw new Error(
"Supabase table \"user_interests\" is missing. Please run the required migrations.", 'Supabase table "user_interests" is missing. Please run the required migrations.',
); );
} }
console.warn("Error fetching interests:", error); console.warn("Error fetching interests:", error);
@ -610,7 +611,7 @@ export const aethexAchievementService = {
if (error) { if (error) {
if (isTableMissing(error)) { if (isTableMissing(error)) {
throw new Error( throw new Error(
"Supabase table \"user_profiles\" is missing. Please run the required migrations.", 'Supabase table "user_profiles" is missing. Please run the required migrations.',
); );
} }
console.warn("Unable to load profile for XP update:", error); console.warn("Unable to load profile for XP update:", error);

View file

@ -252,7 +252,8 @@ class MockAuthService {
// Create new profile // Create new profile
profile = { profile = {
id: userId, id: userId,
username: updates.username || this.currentUser?.email?.split("@")[0] || "user", username:
updates.username || this.currentUser?.email?.split("@")[0] || "user",
email: this.currentUser?.email || "", email: this.currentUser?.email || "",
role: "member", role: "member",
onboarded: true, onboarded: true,
@ -351,8 +352,9 @@ class MockAuthService {
return { data: { provider: removedProvider }, error: null }; return { data: { provider: removedProvider }, error: null };
} }
private authCallback: ((event: string, session: MockSession | null) => void) | private authCallback:
null = null; | ((event: string, session: MockSession | null) => void)
| null = null;
private notifyAuthChange(event: string) { private notifyAuthChange(event: string) {
if (this.authCallback) { if (this.authCallback) {

View file

@ -135,16 +135,24 @@ describe("onboarding passport flow", () => {
bio: "Building awesome experiences", bio: "Building awesome experiences",
}); });
const hydratedProfile = (await mockAuth.getUserProfile(user.id as any)) as AethexUserProfile; const hydratedProfile = (await mockAuth.getUserProfile(
user.id as any,
)) as AethexUserProfile;
expect(checkProfileComplete(hydratedProfile as any)).toBe(true); expect(checkProfileComplete(hydratedProfile as any)).toBe(true);
await mockAuth.linkIdentity({ provider: "github" }); await mockAuth.linkIdentity({ provider: "github" });
const refreshedUser = (await mockAuth.getUser()).data.user; const refreshedUser = (await mockAuth.getUser()).data.user;
expect(refreshedUser?.identities?.some((id: any) => id.provider === "github")).toBe(true); expect(
refreshedUser?.identities?.some((id: any) => id.provider === "github"),
).toBe(true);
await aethexAchievementService.checkAndAwardOnboardingAchievement(user.id); await aethexAchievementService.checkAndAwardOnboardingAchievement(user.id);
const achievements = await aethexAchievementService.getUserAchievements(user.id); const achievements = await aethexAchievementService.getUserAchievements(
const welcomeBadge = achievements.find((item) => item.name === "Welcome to AeThex"); user.id,
);
const welcomeBadge = achievements.find(
(item) => item.name === "Welcome to AeThex",
);
expect(welcomeBadge).toBeTruthy(); expect(welcomeBadge).toBeTruthy();
}); });
}); });

View file

@ -80,7 +80,10 @@ export default function Onboarding() {
useState<AethexAchievement | null>(null); useState<AethexAchievement | null>(null);
const mapProfileToOnboardingData = useCallback( const mapProfileToOnboardingData = useCallback(
(profile: AethexUserProfile | null, interests: string[]): OnboardingData => { (
profile: AethexUserProfile | null,
interests: string[],
): OnboardingData => {
const email = profile?.email || user?.email || ""; const email = profile?.email || user?.email || "";
const fullName = profile?.full_name?.trim() || ""; const fullName = profile?.full_name?.trim() || "";
const nameParts = fullName ? fullName.split(/\s+/).filter(Boolean) : []; const nameParts = fullName ? fullName.split(/\s+/).filter(Boolean) : [];
@ -120,8 +123,7 @@ export default function Onboarding() {
userType: normalizedType, userType: normalizedType,
personalInfo: { personalInfo: {
firstName: firstName:
firstName || firstName || (profile?.username ?? email.split("@")[0] ?? ""),
(profile?.username ?? email.split("@")[0] ?? ""),
lastName, lastName,
email, email,
company: (profile as any)?.company || "", company: (profile as any)?.company || "",
@ -185,10 +187,7 @@ export default function Onboarding() {
}, },
}; };
if (typeof parsed.step === "number") { if (typeof parsed.step === "number") {
nextStep = Math.max( nextStep = Math.max(0, Math.min(parsed.step, steps.length - 1));
0,
Math.min(parsed.step, steps.length - 1),
);
} }
restored = true; restored = true;
} }
@ -326,8 +325,7 @@ export default function Onboarding() {
username: normalizedFirst.replace(/\s+/g, "_"), username: normalizedFirst.replace(/\s+/g, "_"),
full_name: `${normalizedFirst} ${normalizedLast}`.trim(), full_name: `${normalizedFirst} ${normalizedLast}`.trim(),
user_type: user_type:
(userTypeMap[data.userType || "member"] as any) || (userTypeMap[data.userType || "member"] as any) || "community_member",
"community_member",
experience_level: (data.experience.level as any) || "beginner", experience_level: (data.experience.level as any) || "beginner",
bio: data.experience.previousProjects?.trim() || undefined, bio: data.experience.previousProjects?.trim() || undefined,
} as any; } as any;

View file

@ -46,7 +46,9 @@ const ProfilePassport = () => {
const navigate = useNavigate(); const navigate = useNavigate();
const { user, linkedProviders } = useAuth(); const { user, linkedProviders } = useAuth();
const [profile, setProfile] = useState<(AethexUserProfile & { email?: string | null }) | null>(null); const [profile, setProfile] = useState<
(AethexUserProfile & { email?: string | null }) | null
>(null);
const [achievements, setAchievements] = useState<AethexAchievement[]>([]); const [achievements, setAchievements] = useState<AethexAchievement[]>([]);
const [projects, setProjects] = useState<ProjectPreview[]>([]); const [projects, setProjects] = useState<ProjectPreview[]>([]);
const [interests, setInterests] = useState<string[]>([]); const [interests, setInterests] = useState<string[]>([]);
@ -74,14 +76,15 @@ const ProfilePassport = () => {
const loadProfile = async () => { const loadProfile = async () => {
try { try {
setLoading(true); setLoading(true);
const [profileData, achievementList, interestList, projectList] = await Promise.all([ const [profileData, achievementList, interestList, projectList] =
params.id === "me" && profile && profile.id === targetUserId await Promise.all([
? Promise.resolve(profile) params.id === "me" && profile && profile.id === targetUserId
: aethexUserService.getProfileById(targetUserId), ? Promise.resolve(profile)
aethexAchievementService.getUserAchievements(targetUserId), : aethexUserService.getProfileById(targetUserId),
aethexUserService.getUserInterests(targetUserId), aethexAchievementService.getUserAchievements(targetUserId),
aethexProjectService.getUserProjects(targetUserId).catch(() => []), aethexUserService.getUserInterests(targetUserId),
]); aethexProjectService.getUserProjects(targetUserId).catch(() => []),
]);
if (!profileData) { if (!profileData) {
setNotFound(true); setNotFound(true);
@ -142,20 +145,29 @@ const ProfilePassport = () => {
<section className="space-y-4"> <section className="space-y-4">
<div className="flex flex-wrap items-center justify-between gap-3"> <div className="flex flex-wrap items-center justify-between gap-3">
<div> <div>
<h2 className="text-xl font-semibold text-white">Highlighted missions</h2> <h2 className="text-xl font-semibold text-white">
Highlighted missions
</h2>
<p className="text-sm text-slate-300"> <p className="text-sm text-slate-300">
A snapshot of what this creator has shipped inside AeThex. A snapshot of what this creator has shipped inside AeThex.
</p> </p>
</div> </div>
{isSelf && ( {isSelf && (
<Button asChild variant="outline" className="border-slate-700/70 text-slate-100"> <Button
asChild
variant="outline"
className="border-slate-700/70 text-slate-100"
>
<Link to="/projects/new">Launch new project</Link> <Link to="/projects/new">Launch new project</Link>
</Button> </Button>
)} )}
</div> </div>
<div className="grid gap-4 md:grid-cols-2"> <div className="grid gap-4 md:grid-cols-2">
{projects.map((project) => ( {projects.map((project) => (
<Card key={project.id} className="border border-slate-800 bg-slate-900/70"> <Card
key={project.id}
className="border border-slate-800 bg-slate-900/70"
>
<CardHeader className="flex flex-row items-start justify-between space-y-0"> <CardHeader className="flex flex-row items-start justify-between space-y-0">
<div className="space-y-1"> <div className="space-y-1">
<CardTitle className="text-lg text-white"> <CardTitle className="text-lg text-white">
@ -165,15 +177,23 @@ const ProfilePassport = () => {
{project.description || "AeThex project"} {project.description || "AeThex project"}
</CardDescription> </CardDescription>
</div> </div>
<Badge variant="outline" className="border-slate-700/70 text-slate-200"> <Badge
variant="outline"
className="border-slate-700/70 text-slate-200"
>
{project.status?.replace("_", " ") ?? "active"} {project.status?.replace("_", " ") ?? "active"}
</Badge> </Badge>
</CardHeader> </CardHeader>
<CardContent className="flex items-center justify-between text-xs text-slate-300"> <CardContent className="flex items-center justify-between text-xs text-slate-300">
<span className="flex items-center gap-1"> <span className="flex items-center gap-1">
<Clock className="h-3.5 w-3.5" /> {formatDate(project.created_at)} <Clock className="h-3.5 w-3.5" />{" "}
{formatDate(project.created_at)}
</span> </span>
<Button asChild variant="ghost" className="h-8 px-2 text-xs text-aethex-200"> <Button
asChild
variant="ghost"
className="h-8 px-2 text-xs text-aethex-200"
>
<Link to="/projects/new"> <Link to="/projects/new">
View mission View mission
<ExternalLink className="ml-1 h-3.5 w-3.5" /> <ExternalLink className="ml-1 h-3.5 w-3.5" />
@ -189,26 +209,36 @@ const ProfilePassport = () => {
<section className="space-y-4"> <section className="space-y-4">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div> <div>
<h2 className="text-xl font-semibold text-white">Achievements</h2> <h2 className="text-xl font-semibold text-white">
Achievements
</h2>
<p className="text-sm text-slate-300"> <p className="text-sm text-slate-300">
Passport stamps earned across AeThex experiences. Passport stamps earned across AeThex experiences.
</p> </p>
</div> </div>
{isSelf && ( {isSelf && (
<Badge variant="outline" className="border-aethex-500/50 text-aethex-200"> <Badge
<Award className="mr-1 h-3 w-3" /> {achievements.length} badges variant="outline"
className="border-aethex-500/50 text-aethex-200"
>
<Award className="mr-1 h-3 w-3" /> {achievements.length}{" "}
badges
</Badge> </Badge>
)} )}
</div> </div>
{achievements.length === 0 ? ( {achievements.length === 0 ? (
<Card className="border border-slate-800 bg-slate-900/60 p-8 text-center text-slate-300"> <Card className="border border-slate-800 bg-slate-900/60 p-8 text-center text-slate-300">
<Target className="mx-auto mb-3 h-8 w-8 text-aethex-300" /> <Target className="mx-auto mb-3 h-8 w-8 text-aethex-300" />
No achievements yet. Complete onboarding and participate in missions to earn AeThex badges. No achievements yet. Complete onboarding and participate in
missions to earn AeThex badges.
</Card> </Card>
) : ( ) : (
<div className="grid gap-4 sm:grid-cols-2"> <div className="grid gap-4 sm:grid-cols-2">
{achievements.map((achievement) => ( {achievements.map((achievement) => (
<Card key={achievement.id} className="border border-slate-800 bg-slate-900/70"> <Card
key={achievement.id}
className="border border-slate-800 bg-slate-900/70"
>
<CardContent className="flex h-full flex-col justify-between gap-3 p-5"> <CardContent className="flex h-full flex-col justify-between gap-3 p-5">
<div className="flex items-center gap-3 text-white"> <div className="flex items-center gap-3 text-white">
<span className="text-3xl"> <span className="text-3xl">
@ -241,24 +271,40 @@ const ProfilePassport = () => {
<section className="space-y-3"> <section className="space-y-3">
<div className="flex flex-wrap items-center justify-between gap-3"> <div className="flex flex-wrap items-center justify-between gap-3">
<div> <div>
<h2 className="text-xl font-semibold text-white">Stay connected</h2> <h2 className="text-xl font-semibold text-white">
Stay connected
</h2>
<p className="text-sm text-slate-300"> <p className="text-sm text-slate-300">
Reach out, collaborate, and shape the next AeThex release together. Reach out, collaborate, and shape the next AeThex release
together.
</p> </p>
</div> </div>
{isSelf ? ( {isSelf ? (
<Button asChild className="bg-gradient-to-r from-aethex-500 to-neon-blue hover:from-aethex-600 hover:to-neon-blue/90"> <Button
<Link to="/dashboard?tab=connections">Manage connections</Link> asChild
className="bg-gradient-to-r from-aethex-500 to-neon-blue hover:from-aethex-600 hover:to-neon-blue/90"
>
<Link to="/dashboard?tab=connections">
Manage connections
</Link>
</Button> </Button>
) : ( ) : (
<Button asChild variant="outline" className="border-slate-700/70 text-slate-100"> <Button
asChild
variant="outline"
className="border-slate-700/70 text-slate-100"
>
<Link to="/dashboard">Invite to collaborate</Link> <Link to="/dashboard">Invite to collaborate</Link>
</Button> </Button>
)} )}
</div> </div>
<div className="flex flex-wrap gap-2 text-sm text-slate-300"> <div className="flex flex-wrap gap-2 text-sm text-slate-300">
{profile.github_url && ( {profile.github_url && (
<Button asChild variant="ghost" className="h-8 px-2 text-xs text-slate-200"> <Button
asChild
variant="ghost"
className="h-8 px-2 text-xs text-slate-200"
>
<a href={profile.github_url} target="_blank" rel="noreferrer"> <a href={profile.github_url} target="_blank" rel="noreferrer">
GitHub GitHub
<ExternalLink className="ml-1 h-3.5 w-3.5" /> <ExternalLink className="ml-1 h-3.5 w-3.5" />
@ -266,31 +312,58 @@ const ProfilePassport = () => {
</Button> </Button>
)} )}
{profile.linkedin_url && ( {profile.linkedin_url && (
<Button asChild variant="ghost" className="h-8 px-2 text-xs text-slate-200"> <Button
<a href={profile.linkedin_url} target="_blank" rel="noreferrer"> asChild
variant="ghost"
className="h-8 px-2 text-xs text-slate-200"
>
<a
href={profile.linkedin_url}
target="_blank"
rel="noreferrer"
>
LinkedIn LinkedIn
<ExternalLink className="ml-1 h-3.5 w-3.5" /> <ExternalLink className="ml-1 h-3.5 w-3.5" />
</a> </a>
</Button> </Button>
)} )}
{profile.twitter_url && ( {profile.twitter_url && (
<Button asChild variant="ghost" className="h-8 px-2 text-xs text-slate-200"> <Button
<a href={profile.twitter_url} target="_blank" rel="noreferrer"> asChild
variant="ghost"
className="h-8 px-2 text-xs text-slate-200"
>
<a
href={profile.twitter_url}
target="_blank"
rel="noreferrer"
>
X / Twitter X / Twitter
<ExternalLink className="ml-1 h-3.5 w-3.5" /> <ExternalLink className="ml-1 h-3.5 w-3.5" />
</a> </a>
</Button> </Button>
)} )}
{profile.website_url && ( {profile.website_url && (
<Button asChild variant="ghost" className="h-8 px-2 text-xs text-slate-200"> <Button
<a href={profile.website_url} target="_blank" rel="noreferrer"> asChild
variant="ghost"
className="h-8 px-2 text-xs text-slate-200"
>
<a
href={profile.website_url}
target="_blank"
rel="noreferrer"
>
Portfolio Portfolio
<ExternalLink className="ml-1 h-3.5 w-3.5" /> <ExternalLink className="ml-1 h-3.5 w-3.5" />
</a> </a>
</Button> </Button>
)} )}
{profile.bio && ( {profile.bio && (
<Badge variant="outline" className="border-slate-700/70 text-slate-200"> <Badge
variant="outline"
className="border-slate-700/70 text-slate-200"
>
{profile.bio} {profile.bio}
</Badge> </Badge>
)} )}

View file

@ -24,13 +24,7 @@ import {
type AethexUserProfile, type AethexUserProfile,
} from "@/lib/aethex-database-adapter"; } from "@/lib/aethex-database-adapter";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { import { Search, RefreshCw, UserRound, Users, Sparkles } from "lucide-react";
Search,
RefreshCw,
UserRound,
Users,
Sparkles,
} from "lucide-react";
const realmFilters: Array<{ value: string; label: string }> = [ const realmFilters: Array<{ value: string; label: string }> = [
{ value: "all", label: "All Realms" }, { value: "all", label: "All Realms" },
@ -52,7 +46,8 @@ interface ProfileCardProps {
} }
const ProfileCard = ({ profile }: ProfileCardProps) => { const ProfileCard = ({ profile }: ProfileCardProps) => {
const realmStyle = realmBadgeStyles[profile.user_type] || "bg-aethex-500 text-white"; const realmStyle =
realmBadgeStyles[profile.user_type] || "bg-aethex-500 text-white";
return ( return (
<Card className="group h-full border border-slate-800 bg-slate-900/60 transition-transform hover:-translate-y-1 hover:border-aethex-400/60"> <Card className="group h-full border border-slate-800 bg-slate-900/60 transition-transform hover:-translate-y-1 hover:border-aethex-400/60">
<CardHeader className="space-y-3"> <CardHeader className="space-y-3">
@ -60,7 +55,10 @@ const ProfileCard = ({ profile }: ProfileCardProps) => {
<Badge className={cn("text-xs uppercase tracking-wider", realmStyle)}> <Badge className={cn("text-xs uppercase tracking-wider", realmStyle)}>
{profile.user_type.replace("_", " ")} {profile.user_type.replace("_", " ")}
</Badge> </Badge>
<Badge variant="outline" className="border-slate-700/70 text-slate-300"> <Badge
variant="outline"
className="border-slate-700/70 text-slate-300"
>
Level {profile.level ?? 1} Level {profile.level ?? 1}
</Badge> </Badge>
</div> </div>
@ -92,12 +90,19 @@ const ProfileCard = ({ profile }: ProfileCardProps) => {
</div> </div>
<div className="flex flex-wrap gap-2 text-xs text-slate-300"> <div className="flex flex-wrap gap-2 text-xs text-slate-300">
{(profile as any)?.skills?.slice(0, 3)?.map((skill: string) => ( {(profile as any)?.skills?.slice(0, 3)?.map((skill: string) => (
<Badge key={skill} variant="outline" className="border-slate-700/70 text-slate-200"> <Badge
key={skill}
variant="outline"
className="border-slate-700/70 text-slate-200"
>
{skill} {skill}
</Badge> </Badge>
))} ))}
{((profile as any)?.skills?.length || 0) > 3 && ( {((profile as any)?.skills?.length || 0) > 3 && (
<Badge variant="outline" className="border-slate-700/70 text-slate-200"> <Badge
variant="outline"
className="border-slate-700/70 text-slate-200"
>
+{((profile as any)?.skills?.length || 0) - 3} +{((profile as any)?.skills?.length || 0) - 3}
</Badge> </Badge>
)} )}
@ -107,7 +112,10 @@ const ProfileCard = ({ profile }: ProfileCardProps) => {
variant="outline" variant="outline"
className="w-full border-slate-700/70 text-slate-100 transition-colors hover:border-aethex-400/60 hover:text-white" className="w-full border-slate-700/70 text-slate-100 transition-colors hover:border-aethex-400/60 hover:text-white"
> >
<Link to={`/profiles/${profile.id}`} className="flex items-center justify-center gap-2"> <Link
to={`/profiles/${profile.id}`}
className="flex items-center justify-center gap-2"
>
<UserRound className="h-4 w-4" /> <UserRound className="h-4 w-4" />
View Passport View Passport
</Link> </Link>
@ -129,15 +137,9 @@ const ProfilesDirectory = () => {
const matchesRealm = const matchesRealm =
realmFilter === "all" || profile.user_type === realmFilter; realmFilter === "all" || profile.user_type === realmFilter;
const matchesSearch = lowerSearch const matchesSearch = lowerSearch
? [ ? [profile.full_name, profile.username, (profile as any)?.company]
profile.full_name,
profile.username,
(profile as any)?.company,
]
.filter(Boolean) .filter(Boolean)
.some((value) => .some((value) => String(value).toLowerCase().includes(lowerSearch))
String(value).toLowerCase().includes(lowerSearch),
)
: true; : true;
return matchesRealm && matchesSearch; return matchesRealm && matchesSearch;
}); });
@ -178,7 +180,8 @@ const ProfilesDirectory = () => {
Discover AeThex talent Discover AeThex talent
</h1> </h1>
<p className="text-slate-300"> <p className="text-slate-300">
Browse verified creators, clients, and community members across every AeThex realm. Browse verified creators, clients, and community members
across every AeThex realm.
</p> </p>
</div> </div>
<div className="flex gap-2"> <div className="flex gap-2">
@ -190,7 +193,10 @@ const ProfilesDirectory = () => {
<RefreshCw className="mr-2 h-4 w-4" /> <RefreshCw className="mr-2 h-4 w-4" />
Refresh Refresh
</Button> </Button>
<Button asChild className="bg-gradient-to-r from-aethex-500 to-neon-blue hover:from-aethex-600 hover:to-neon-blue/90"> <Button
asChild
className="bg-gradient-to-r from-aethex-500 to-neon-blue hover:from-aethex-600 hover:to-neon-blue/90"
>
<Link to="/profiles/me">Go to my passport</Link> <Link to="/profiles/me">Go to my passport</Link>
</Button> </Button>
</div> </div>
@ -228,7 +234,8 @@ const ProfilesDirectory = () => {
No passports found No passports found
</div> </div>
<p className="mt-2 text-sm text-slate-300"> <p className="mt-2 text-sm text-slate-300">
Try adjusting your search or realm filters. New members join AeThex every day! Try adjusting your search or realm filters. New members join
AeThex every day!
</p> </p>
</Card> </Card>
) : ( ) : (