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 />}
/>
<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/me" element={<ProfilePassport />} />
<Route path="/profiles/:id" element={<ProfilePassport />} />

View file

@ -8,12 +8,7 @@ import {
CardTitle,
} from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import {
CheckCircle,
ArrowRight,
Sparkles,
ShieldCheck,
} from "lucide-react";
import { CheckCircle, ArrowRight, Sparkles, ShieldCheck } from "lucide-react";
import { Link } from "react-router-dom";
import type { AethexAchievement } from "@/lib/aethex-database-adapter";
@ -211,16 +206,23 @@ export default function Welcome({
</CardHeader>
<CardContent className="space-y-3 text-sm text-emerald-100/90">
{achievement.description && <p>{achievement.description}</p>}
{typeof achievement.xp_reward === "number" && achievement.xp_reward > 0 && (
<div className="text-xs uppercase tracking-wider text-emerald-200">
+{achievement.xp_reward} XP added to your passport progression
</div>
)}
{typeof achievement.xp_reward === "number" &&
achievement.xp_reward > 0 && (
<div className="text-xs uppercase tracking-wider text-emerald-200">
+{achievement.xp_reward} XP added to your passport progression
</div>
)}
<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
</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
</Badge>
</div>
@ -271,7 +273,10 @@ export default function Welcome({
variant="secondary"
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" />
Link OAuth Accounts
</Link>

View file

@ -109,18 +109,29 @@ const PassportSummary = ({
<Badge
className={cn(
"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"}
</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}
</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
</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
</Badge>
</div>
@ -150,7 +161,8 @@ const PassportSummary = ({
<Sparkles className="h-4 w-4 text-aethex-300" />
</div>
<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>
</div>
<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"
>
<div className="flex items-center gap-2 text-slate-100">
<span className="text-xl">
{achievement.icon || "✨"}
</span>
<span className="text-xl">{achievement.icon || "✨"}</span>
<span className="text-sm font-medium">
{achievement.name}
</span>
@ -210,7 +220,11 @@ const PassportSummary = ({
</h3>
<div className="flex flex-wrap gap-2">
{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}
</Badge>
))}
@ -249,14 +263,20 @@ const PassportSummary = ({
<>
<Separator className="border-slate-800" />
<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>
</Button>
<Button
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 Passport Links</Link>
<Link to="/dashboard?tab=connections">
Manage Passport Links
</Link>
</Button>
</div>
</>

View file

@ -131,7 +131,7 @@ export const aethexUserService = {
if (isTableMissing(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)) {
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 (isTableMissing(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;
}
return ((data as any[]) || []).map((row) =>
({
...(row as AethexUserProfile),
user_type: (row as any).user_type || "community_member",
experience_level: (row as any).experience_level || "beginner",
}) as AethexUserProfile,
return ((data as any[]) || []).map(
(row) =>
({
...(row as AethexUserProfile),
user_type: (row as any).user_type || "community_member",
experience_level: (row as any).experience_level || "beginner",
}) as AethexUserProfile,
);
},
@ -238,7 +239,7 @@ export const aethexUserService = {
if (isTableMissing(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 (isTableMissing(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;
@ -315,7 +316,7 @@ export const aethexUserService = {
if (error) {
if (isTableMissing(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;
@ -333,7 +334,7 @@ export const aethexUserService = {
if (error) {
if (isTableMissing(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);
@ -610,7 +611,7 @@ export const aethexAchievementService = {
if (error) {
if (isTableMissing(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);

View file

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

View file

@ -135,16 +135,24 @@ describe("onboarding passport flow", () => {
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);
await mockAuth.linkIdentity({ provider: "github" });
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);
const achievements = await aethexAchievementService.getUserAchievements(user.id);
const welcomeBadge = achievements.find((item) => item.name === "Welcome to AeThex");
const achievements = await aethexAchievementService.getUserAchievements(
user.id,
);
const welcomeBadge = achievements.find(
(item) => item.name === "Welcome to AeThex",
);
expect(welcomeBadge).toBeTruthy();
});
});

View file

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

View file

@ -46,7 +46,9 @@ const ProfilePassport = () => {
const navigate = useNavigate();
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 [projects, setProjects] = useState<ProjectPreview[]>([]);
const [interests, setInterests] = useState<string[]>([]);
@ -74,14 +76,15 @@ const ProfilePassport = () => {
const loadProfile = async () => {
try {
setLoading(true);
const [profileData, achievementList, interestList, projectList] = await Promise.all([
params.id === "me" && profile && profile.id === targetUserId
? Promise.resolve(profile)
: aethexUserService.getProfileById(targetUserId),
aethexAchievementService.getUserAchievements(targetUserId),
aethexUserService.getUserInterests(targetUserId),
aethexProjectService.getUserProjects(targetUserId).catch(() => []),
]);
const [profileData, achievementList, interestList, projectList] =
await Promise.all([
params.id === "me" && profile && profile.id === targetUserId
? Promise.resolve(profile)
: aethexUserService.getProfileById(targetUserId),
aethexAchievementService.getUserAchievements(targetUserId),
aethexUserService.getUserInterests(targetUserId),
aethexProjectService.getUserProjects(targetUserId).catch(() => []),
]);
if (!profileData) {
setNotFound(true);
@ -142,20 +145,29 @@ const ProfilePassport = () => {
<section className="space-y-4">
<div className="flex flex-wrap items-center justify-between gap-3">
<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">
A snapshot of what this creator has shipped inside AeThex.
</p>
</div>
{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>
</Button>
)}
</div>
<div className="grid gap-4 md:grid-cols-2">
{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">
<div className="space-y-1">
<CardTitle className="text-lg text-white">
@ -165,15 +177,23 @@ const ProfilePassport = () => {
{project.description || "AeThex project"}
</CardDescription>
</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"}
</Badge>
</CardHeader>
<CardContent className="flex items-center justify-between text-xs text-slate-300">
<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>
<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">
View mission
<ExternalLink className="ml-1 h-3.5 w-3.5" />
@ -189,26 +209,36 @@ const ProfilePassport = () => {
<section className="space-y-4">
<div className="flex items-center justify-between">
<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">
Passport stamps earned across AeThex experiences.
</p>
</div>
{isSelf && (
<Badge variant="outline" className="border-aethex-500/50 text-aethex-200">
<Award className="mr-1 h-3 w-3" /> {achievements.length} badges
<Badge
variant="outline"
className="border-aethex-500/50 text-aethex-200"
>
<Award className="mr-1 h-3 w-3" /> {achievements.length}{" "}
badges
</Badge>
)}
</div>
{achievements.length === 0 ? (
<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" />
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>
) : (
<div className="grid gap-4 sm:grid-cols-2">
{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">
<div className="flex items-center gap-3 text-white">
<span className="text-3xl">
@ -241,24 +271,40 @@ const ProfilePassport = () => {
<section className="space-y-3">
<div className="flex flex-wrap items-center justify-between gap-3">
<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">
Reach out, collaborate, and shape the next AeThex release together.
Reach out, collaborate, and shape the next AeThex release
together.
</p>
</div>
{isSelf ? (
<Button 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
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 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>
</Button>
)}
</div>
<div className="flex flex-wrap gap-2 text-sm text-slate-300">
{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">
GitHub
<ExternalLink className="ml-1 h-3.5 w-3.5" />
@ -266,31 +312,58 @@ const ProfilePassport = () => {
</Button>
)}
{profile.linkedin_url && (
<Button asChild variant="ghost" className="h-8 px-2 text-xs text-slate-200">
<a href={profile.linkedin_url} target="_blank" rel="noreferrer">
<Button
asChild
variant="ghost"
className="h-8 px-2 text-xs text-slate-200"
>
<a
href={profile.linkedin_url}
target="_blank"
rel="noreferrer"
>
LinkedIn
<ExternalLink className="ml-1 h-3.5 w-3.5" />
</a>
</Button>
)}
{profile.twitter_url && (
<Button asChild variant="ghost" className="h-8 px-2 text-xs text-slate-200">
<a href={profile.twitter_url} target="_blank" rel="noreferrer">
<Button
asChild
variant="ghost"
className="h-8 px-2 text-xs text-slate-200"
>
<a
href={profile.twitter_url}
target="_blank"
rel="noreferrer"
>
X / Twitter
<ExternalLink className="ml-1 h-3.5 w-3.5" />
</a>
</Button>
)}
{profile.website_url && (
<Button asChild variant="ghost" className="h-8 px-2 text-xs text-slate-200">
<a href={profile.website_url} target="_blank" rel="noreferrer">
<Button
asChild
variant="ghost"
className="h-8 px-2 text-xs text-slate-200"
>
<a
href={profile.website_url}
target="_blank"
rel="noreferrer"
>
Portfolio
<ExternalLink className="ml-1 h-3.5 w-3.5" />
</a>
</Button>
)}
{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}
</Badge>
)}

View file

@ -24,13 +24,7 @@ import {
type AethexUserProfile,
} from "@/lib/aethex-database-adapter";
import { cn } from "@/lib/utils";
import {
Search,
RefreshCw,
UserRound,
Users,
Sparkles,
} from "lucide-react";
import { Search, RefreshCw, UserRound, Users, Sparkles } from "lucide-react";
const realmFilters: Array<{ value: string; label: string }> = [
{ value: "all", label: "All Realms" },
@ -52,7 +46,8 @@ interface 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 (
<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">
@ -60,7 +55,10 @@ const ProfileCard = ({ profile }: ProfileCardProps) => {
<Badge className={cn("text-xs uppercase tracking-wider", realmStyle)}>
{profile.user_type.replace("_", " ")}
</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}
</Badge>
</div>
@ -92,12 +90,19 @@ const ProfileCard = ({ profile }: ProfileCardProps) => {
</div>
<div className="flex flex-wrap gap-2 text-xs text-slate-300">
{(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}
</Badge>
))}
{((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}
</Badge>
)}
@ -107,7 +112,10 @@ const ProfileCard = ({ profile }: ProfileCardProps) => {
variant="outline"
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" />
View Passport
</Link>
@ -129,15 +137,9 @@ const ProfilesDirectory = () => {
const matchesRealm =
realmFilter === "all" || profile.user_type === realmFilter;
const matchesSearch = lowerSearch
? [
profile.full_name,
profile.username,
(profile as any)?.company,
]
? [profile.full_name, profile.username, (profile as any)?.company]
.filter(Boolean)
.some((value) =>
String(value).toLowerCase().includes(lowerSearch),
)
.some((value) => String(value).toLowerCase().includes(lowerSearch))
: true;
return matchesRealm && matchesSearch;
});
@ -178,7 +180,8 @@ const ProfilesDirectory = () => {
Discover AeThex talent
</h1>
<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>
</div>
<div className="flex gap-2">
@ -190,7 +193,10 @@ const ProfilesDirectory = () => {
<RefreshCw className="mr-2 h-4 w-4" />
Refresh
</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>
</Button>
</div>
@ -228,7 +234,8 @@ const ProfilesDirectory = () => {
No passports found
</div>
<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>
</Card>
) : (