Prettier format pending files
This commit is contained in:
parent
12127ff4ef
commit
5a7b56a792
9 changed files with 229 additions and 112 deletions
|
|
@ -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 />} />
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
) : (
|
||||
|
|
|
|||
Loading…
Reference in a new issue