Prettier format pending files

This commit is contained in:
Builder.io 2025-10-05 00:11:30 +00:00
parent c00bdfcc2d
commit 9e8163d005
9 changed files with 773 additions and 336 deletions

View file

@ -48,10 +48,7 @@ const CORE_ACHIEVEMENTS = [
const DEFAULT_TARGET_EMAIL = "mrpiglr@gmail.com"; const DEFAULT_TARGET_EMAIL = "mrpiglr@gmail.com";
const DEFAULT_TARGET_USERNAME = "mrpiglr"; const DEFAULT_TARGET_USERNAME = "mrpiglr";
export default async function handler( export default async function handler(req: VercelRequest, res: VercelResponse) {
req: VercelRequest,
res: VercelResponse,
) {
if (req.method !== "POST") { if (req.method !== "POST") {
return res.status(405).json({ error: "Method not allowed" }); return res.status(405).json({ error: "Method not allowed" });
} }
@ -68,20 +65,18 @@ export default async function handler(
// Ensure core achievements exist // Ensure core achievements exist
const achievementResults = await Promise.all( const achievementResults = await Promise.all(
CORE_ACHIEVEMENTS.map(async (achievement) => { CORE_ACHIEVEMENTS.map(async (achievement) => {
const { error } = await admin const { error } = await admin.from("achievements").upsert(
.from("achievements") {
.upsert( id: achievement.id,
{ name: achievement.name,
id: achievement.id, description: achievement.description,
name: achievement.name, icon: achievement.icon,
description: achievement.description, badge_color: achievement.badgeColor,
icon: achievement.icon, xp_reward: achievement.xpReward,
badge_color: achievement.badgeColor, created_at: nowIso,
xp_reward: achievement.xpReward, },
created_at: nowIso, { onConflict: "id" },
}, );
{ onConflict: "id" },
);
if (error) { if (error) {
throw error; throw error;
@ -92,14 +87,8 @@ export default async function handler(
// Normalise profile progression defaults // Normalise profile progression defaults
await Promise.all([ await Promise.all([
admin admin.from("user_profiles").update({ level: 1 }).is("level", null),
.from("user_profiles") admin.from("user_profiles").update({ total_xp: 0 }).is("total_xp", null),
.update({ level: 1 })
.is("level", null),
admin
.from("user_profiles")
.update({ total_xp: 0 })
.is("total_xp", null),
admin admin
.from("user_profiles") .from("user_profiles")
.update({ user_type: "game_developer" }) .update({ user_type: "game_developer" })
@ -108,7 +97,9 @@ export default async function handler(
// Locate target user // Locate target user
const normalizedEmail = (targetEmail || DEFAULT_TARGET_EMAIL).toLowerCase(); const normalizedEmail = (targetEmail || DEFAULT_TARGET_EMAIL).toLowerCase();
const normalizedUsername = (targetUsername || DEFAULT_TARGET_USERNAME).toLowerCase(); const normalizedUsername = (
targetUsername || DEFAULT_TARGET_USERNAME
).toLowerCase();
let targetUserId: string | null = null; let targetUserId: string | null = null;
@ -171,7 +162,9 @@ export default async function handler(
throw existingError; throw existingError;
} }
const existingIds = new Set((existingRows ?? []).map((row: any) => row.achievement_id)); const existingIds = new Set(
(existingRows ?? []).map((row: any) => row.achievement_id),
);
for (const achievement of CORE_ACHIEVEMENTS) { for (const achievement of CORE_ACHIEVEMENTS) {
if (existingIds.has(achievement.id)) { if (existingIds.has(achievement.id)) {

View file

@ -67,12 +67,18 @@ const App = () => (
<Route path="/profile/me" element={<Profile />} /> <Route path="/profile/me" element={<Profile />} />
<Route path="/developers" element={<DevelopersDirectory />} /> <Route path="/developers" element={<DevelopersDirectory />} />
<Route path="/developers/me" element={<LegacyPassportRedirect />} /> <Route
path="/developers/me"
element={<LegacyPassportRedirect />}
/>
<Route <Route
path="/developers/:id" path="/developers/:id"
element={<LegacyPassportRedirect />} element={<LegacyPassportRedirect />}
/> />
<Route path="/profiles" element={<Navigate to="/developers" replace />} /> <Route
path="/profiles"
element={<Navigate to="/developers" replace />}
/>
<Route path="/profiles/me" element={<LegacyPassportRedirect />} /> <Route path="/profiles/me" element={<LegacyPassportRedirect />} />
<Route <Route
path="/profiles/:id" path="/profiles/:id"

View file

@ -150,10 +150,14 @@ const PassportSummary = ({
<div className="w-full max-w-xs space-y-3 rounded-xl bg-slate-900/60 p-4 border border-slate-800"> <div className="w-full max-w-xs space-y-3 rounded-xl bg-slate-900/60 p-4 border border-slate-800">
<div className="flex items-center justify-between text-slate-200"> <div className="flex items-center justify-between text-slate-200">
<span className="text-sm font-medium uppercase tracking-wider"> <span className="text-sm font-medium uppercase tracking-wider">
{isLegendary ? "Legendary Status" : `Progress to Level ${level + 1}`} {isLegendary
? "Legendary Status"
: `Progress to Level ${level + 1}`}
</span> </span>
<span className="text-sm text-slate-300"> <span className="text-sm text-slate-300">
{isLegendary ? "MAX" : `${(progressToNextLevel || 0).toFixed(0)}%`} {isLegendary
? "MAX"
: `${(progressToNextLevel || 0).toFixed(0)}%`}
</span> </span>
</div> </div>
<Progress value={progressToNextLevel} className="h-2" /> <Progress value={progressToNextLevel} className="h-2" />

View file

@ -34,7 +34,9 @@ const ensureSupabase = () => {
const MS_PER_DAY = 1000 * 60 * 60 * 24; const MS_PER_DAY = 1000 * 60 * 60 * 24;
const startOfUTC = (date: Date) => const startOfUTC = (date: Date) =>
new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate())); new Date(
Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate()),
);
const isoDate = (date: Date) => date.toISOString().slice(0, 10); const isoDate = (date: Date) => date.toISOString().slice(0, 10);
@ -50,14 +52,14 @@ const normalizeProfile = (
): AethexUserProfile => ({ ): AethexUserProfile => ({
...(row as AethexUserProfile), ...(row as AethexUserProfile),
email: email ?? (row as any)?.email, email: email ?? (row as any)?.email,
username: username: (row as any)?.username ?? email?.split("@")[0] ?? "user",
(row as any)?.username ?? email?.split("@")[0] ?? "user",
onboarded: true, onboarded: true,
role: (row as any)?.role ?? "developer", role: (row as any)?.role ?? "developer",
loyalty_points: (row as any)?.loyalty_points ?? 0, loyalty_points: (row as any)?.loyalty_points ?? 0,
current_streak: (row as any)?.current_streak ?? 0, current_streak: (row as any)?.current_streak ?? 0,
longest_streak: longest_streak:
(row as any)?.longest_streak ?? Math.max((row as any)?.current_streak ?? 0, 0), (row as any)?.longest_streak ??
Math.max((row as any)?.current_streak ?? 0, 0),
last_streak_at: (row as any)?.last_streak_at ?? null, last_streak_at: (row as any)?.last_streak_at ?? null,
}); });
@ -302,16 +304,15 @@ export const aethexUserService = {
return normalizeProfile(data); return normalizeProfile(data);
}, },
async getProfileByUsername(username: string): Promise<AethexUserProfile | null> { async getProfileByUsername(
username: string,
): Promise<AethexUserProfile | null> {
const normalized = username?.trim(); const normalized = username?.trim();
if (!normalized) return null; if (!normalized) return null;
ensureSupabase(); ensureSupabase();
const { const { data, error } = await supabase
data,
error,
} = await supabase
.from("user_profiles") .from("user_profiles")
.select("*") .select("*")
.eq("username", normalized) .eq("username", normalized)
@ -332,10 +333,7 @@ export const aethexUserService = {
return normalizeProfile(data); return normalizeProfile(data);
} }
const { const { data: fallback, error: fallbackError } = await supabase
data: fallback,
error: fallbackError,
} = await supabase
.from("user_profiles") .from("user_profiles")
.select("*") .select("*")
.ilike("username", normalized) .ilike("username", normalized)
@ -379,13 +377,11 @@ export const aethexUserService = {
} }
return ((data as any[]) || []).map((row) => return ((data as any[]) || []).map((row) =>
normalizeProfile( normalizeProfile({
{ ...(row as AethexUserProfile),
...(row as AethexUserProfile), user_type: (row as any).user_type || "game_developer",
user_type: (row as any).user_type || "game_developer", experience_level: (row as any).experience_level || "beginner",
experience_level: (row as any).experience_level || "beginner", }),
},
),
); );
}, },
@ -693,7 +689,10 @@ export const aethexAchievementService = {
} }
}, },
async updateUserXPAndLevel(userId: string, xpGained: number | null = null): Promise<void> { async updateUserXPAndLevel(
userId: string,
xpGained: number | null = null,
): Promise<void> {
ensureSupabase(); ensureSupabase();
const { data: profile, error } = await supabase const { data: profile, error } = await supabase
@ -719,7 +718,8 @@ export const aethexAchievementService = {
const updates: Record<string, number> = {}; const updates: Record<string, number> = {};
if ("total_xp" in currentProfile) updates.total_xp = newTotalXP; if ("total_xp" in currentProfile) updates.total_xp = newTotalXP;
if ("level" in currentProfile) updates.level = newLevel; if ("level" in currentProfile) updates.level = newLevel;
if ("loyalty_points" in currentProfile) updates.loyalty_points = newLoyaltyPoints; if ("loyalty_points" in currentProfile)
updates.loyalty_points = newLoyaltyPoints;
if (Object.keys(updates).length > 0) { if (Object.keys(updates).length > 0) {
const { error: updateError } = await supabase const { error: updateError } = await supabase
@ -748,11 +748,16 @@ export const aethexAchievementService = {
return; return;
} }
} catch (error) { } catch (error) {
console.warn("Edge function award failed, attempting direct Supabase insert", error); console.warn(
"Edge function award failed, attempting direct Supabase insert",
error,
);
} }
const achievements = await this.getAllAchievements(); const achievements = await this.getAllAchievements();
const byName = new Map(achievements.map((item) => [item.name, item.id] as const)); const byName = new Map(
achievements.map((item) => [item.name, item.id] as const),
);
const names = ["Welcome to AeThex", "AeThex Explorer"]; const names = ["Welcome to AeThex", "AeThex Explorer"];
for (const name of names) { for (const name of names) {

View file

@ -36,7 +36,8 @@ const fetchMock = fetch as unknown as Mock;
vi.mock("@/lib/supabase", () => { vi.mock("@/lib/supabase", () => {
const userProfiles = new Map<string, any>(); const userProfiles = new Map<string, any>();
const userAchievements: Array<{ user_id: string; achievement_id: string }> = []; const userAchievements: Array<{ user_id: string; achievement_id: string }> =
[];
const achievementsCatalog = [ const achievementsCatalog = [
{ {
@ -59,8 +60,12 @@ vi.mock("@/lib/supabase", () => {
}, },
]; ];
const achievementsById = new Map(achievementsCatalog.map((item) => [item.id, item] as const)); const achievementsById = new Map(
const achievementsByName = new Map(achievementsCatalog.map((item) => [item.name, item] as const)); achievementsCatalog.map((item) => [item.id, item] as const),
);
const achievementsByName = new Map(
achievementsCatalog.map((item) => [item.name, item] as const),
);
const profileDefaults = (id: string) => ({ const profileDefaults = (id: string) => ({
id, id,
@ -201,7 +206,8 @@ vi.mock("@/lib/supabase", () => {
for (const entry of entries) { for (const entry of entries) {
const exists = userAchievements.some( const exists = userAchievements.some(
(item) => (item) =>
item.user_id === entry.user_id && item.achievement_id === entry.achievement_id, item.user_id === entry.user_id &&
item.achievement_id === entry.achievement_id,
); );
if (exists) { if (exists) {
error = { code: "23505" }; error = { code: "23505" };
@ -215,10 +221,12 @@ vi.mock("@/lib/supabase", () => {
return { return {
eq(_column: string, userId: string) { eq(_column: string, userId: string) {
return { return {
data: userAchievements.map((entry) => ({ data: userAchievements
...entry, .map((entry) => ({
achievements: achievementsById.get(entry.achievement_id), ...entry,
})).filter((entry) => entry.user_id === userId), achievements: achievementsById.get(entry.achievement_id),
}))
.filter((entry) => entry.user_id === userId),
error: null, error: null,
}; };
}, },
@ -251,9 +259,11 @@ vi.mock("@/lib/supabase", () => {
}), }),
}, },
from(table: string) { from(table: string) {
return tableMap[table] ?? { return (
select: () => ({ data: [], error: null }), tableMap[table] ?? {
}; select: () => ({ data: [], error: null }),
}
);
}, },
channel: () => ({ channel: () => ({
on: () => ({}), on: () => ({}),

File diff suppressed because it is too large Load diff

View file

@ -58,7 +58,9 @@ const DeveloperCard = ({ profile }: DeveloperCardProps) => {
<CardHeader className="space-y-3"> <CardHeader className="space-y-3">
<div className="flex items-center justify-between gap-2"> <div className="flex items-center justify-between gap-2">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<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>
{isGodMode && ( {isGodMode && (
@ -124,7 +126,10 @@ const DeveloperCard = ({ profile }: DeveloperCardProps) => {
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={passportHref} className="flex items-center justify-center gap-2"> <Link
to={passportHref}
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>
@ -193,8 +198,8 @@ const DevelopersDirectory = () => {
Discover AeThex developers Discover AeThex developers
</h1> </h1>
<p className="text-slate-300"> <p className="text-slate-300">
Browse verified builders, clients, and community members across Browse verified builders, clients, and community members
every AeThex realm. across every AeThex realm.
</p> </p>
</div> </div>
<div className="flex gap-2"> <div className="flex gap-2">

View file

@ -51,7 +51,7 @@ import {
interface SiteStatus { interface SiteStatus {
name: string; name: string;
url: string; url: string;
status: 'online' | 'offline' | 'maintenance'; status: "online" | "offline" | "maintenance";
lastCheck: string; lastCheck: string;
responseTime: number; responseTime: number;
version: string; version: string;
@ -61,7 +61,7 @@ interface CrossSiteCommunication {
siteName: string; siteName: string;
lastSync: string; lastSync: string;
dataExchanged: number; dataExchanged: number;
status: 'connected' | 'disconnected' | 'syncing'; status: "connected" | "disconnected" | "syncing";
} }
export default function Profile() { export default function Profile() {
@ -70,17 +70,17 @@ export default function Profile() {
const { success: toastSuccess, error: toastError } = useAethexToast(); const { success: toastSuccess, error: toastError } = useAethexToast();
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(true);
const [isSaving, setIsSaving] = useState(false); const [isSaving, setIsSaving] = useState(false);
// Profile settings state // Profile settings state
const [profileData, setProfileData] = useState({ const [profileData, setProfileData] = useState({
displayName: '', displayName: "",
bio: '', bio: "",
company: '', company: "",
location: '', location: "",
website: '', website: "",
githubUsername: '', githubUsername: "",
twitterUsername: '', twitterUsername: "",
linkedinUrl: '', linkedinUrl: "",
}); });
// Notification settings // Notification settings
@ -94,7 +94,7 @@ export default function Profile() {
// Privacy settings // Privacy settings
const [privacy, setPrivacy] = useState({ const [privacy, setPrivacy] = useState({
profileVisibility: 'public', profileVisibility: "public",
showEmail: false, showEmail: false,
showProjects: true, showProjects: true,
allowAnalytics: true, allowAnalytics: true,
@ -114,50 +114,50 @@ export default function Profile() {
// Cross-site communication data // Cross-site communication data
const [crossSiteData, setCrossSiteData] = useState<CrossSiteCommunication[]>([ const [crossSiteData, setCrossSiteData] = useState<CrossSiteCommunication[]>([
{ {
siteName: 'AeThex Labs', siteName: "AeThex Labs",
lastSync: '2 minutes ago', lastSync: "2 minutes ago",
dataExchanged: 1.2, dataExchanged: 1.2,
status: 'connected', status: "connected",
}, },
{ {
siteName: 'Development Portal', siteName: "Development Portal",
lastSync: '5 minutes ago', lastSync: "5 minutes ago",
dataExchanged: 0.8, dataExchanged: 0.8,
status: 'syncing', status: "syncing",
}, },
{ {
siteName: 'Community Hub', siteName: "Community Hub",
lastSync: '12 minutes ago', lastSync: "12 minutes ago",
dataExchanged: 2.1, dataExchanged: 2.1,
status: 'connected', status: "connected",
}, },
]); ]);
// Site monitoring data // Site monitoring data
const [siteStatuses, setSiteStatuses] = useState<SiteStatus[]>([ const [siteStatuses, setSiteStatuses] = useState<SiteStatus[]>([
{ {
name: 'Main Site', name: "Main Site",
url: 'https://core.aethex.biz', url: "https://core.aethex.biz",
status: 'online', status: "online",
lastCheck: 'Just now', lastCheck: "Just now",
responseTime: 245, responseTime: 245,
version: '1.0.0', version: "1.0.0",
}, },
{ {
name: 'API Gateway', name: "API Gateway",
url: 'https://api.aethex.biz', url: "https://api.aethex.biz",
status: 'online', status: "online",
lastCheck: '30 seconds ago', lastCheck: "30 seconds ago",
responseTime: 123, responseTime: 123,
version: '2.1.3', version: "2.1.3",
}, },
{ {
name: 'Labs Portal', name: "Labs Portal",
url: 'https://labs.aethex.biz', url: "https://labs.aethex.biz",
status: 'maintenance', status: "maintenance",
lastCheck: '2 minutes ago', lastCheck: "2 minutes ago",
responseTime: 0, responseTime: 0,
version: '1.5.2', version: "1.5.2",
}, },
]); ]);
@ -166,20 +166,20 @@ export default function Profile() {
useEffect(() => { useEffect(() => {
if (!authLoading && !user) { if (!authLoading && !user) {
navigate('/login'); navigate("/login");
return; return;
} }
if (profile) { if (profile) {
setProfileData({ setProfileData({
displayName: profile.full_name || '', displayName: profile.full_name || "",
bio: profile.bio || '', bio: profile.bio || "",
company: (profile as any).company || '', company: (profile as any).company || "",
location: profile.location || '', location: profile.location || "",
website: (profile as any).website || '', website: (profile as any).website || "",
githubUsername: (profile as any).github_username || '', githubUsername: (profile as any).github_username || "",
twitterUsername: (profile as any).twitter_username || '', twitterUsername: (profile as any).twitter_username || "",
linkedinUrl: profile.linkedin_url || '', linkedinUrl: profile.linkedin_url || "",
}); });
} }
@ -193,7 +193,7 @@ export default function Profile() {
console.log("Starting profile save...", { console.log("Starting profile save...", {
user: user.id, user: user.id,
profile: !!profile, profile: !!profile,
profileData profileData,
}); });
try { try {
@ -204,13 +204,20 @@ export default function Profile() {
try { try {
// Try using the AuthContext updateProfile (which works with mock too) // Try using the AuthContext updateProfile (which works with mock too)
await updateProfile({ await updateProfile({
username: profileData.displayName || user.email?.split('@')[0] || `user_${Date.now()}`, username:
profileData.displayName ||
user.email?.split("@")[0] ||
`user_${Date.now()}`,
full_name: profileData.displayName, full_name: profileData.displayName,
bio: profileData.bio, bio: profileData.bio,
location: profileData.location, location: profileData.location,
linkedin_url: profileData.linkedinUrl, linkedin_url: profileData.linkedinUrl,
github_url: profileData.githubUsername ? `https://github.com/${profileData.githubUsername}` : null, github_url: profileData.githubUsername
twitter_url: profileData.twitterUsername ? `https://twitter.com/${profileData.twitterUsername}` : null, ? `https://github.com/${profileData.githubUsername}`
: null,
twitter_url: profileData.twitterUsername
? `https://twitter.com/${profileData.twitterUsername}`
: null,
user_type: "game_developer", user_type: "game_developer",
experience_level: "beginner", experience_level: "beginner",
level: 1, level: 1,
@ -219,22 +226,32 @@ export default function Profile() {
console.log("Profile created successfully via AuthContext"); console.log("Profile created successfully via AuthContext");
} catch (authError) { } catch (authError) {
console.warn("AuthContext creation failed, trying direct database:", authError); console.warn(
"AuthContext creation failed, trying direct database:",
authError,
);
// Fallback to direct database call // Fallback to direct database call
const { data, error } = await supabase const { data, error } = await supabase
.from("user_profiles") .from("user_profiles")
.insert({ .insert({
id: user.id, id: user.id,
username: profileData.displayName || user.email?.split('@')[0] || `user_${Date.now()}`, username:
profileData.displayName ||
user.email?.split("@")[0] ||
`user_${Date.now()}`,
user_type: "game_developer", user_type: "game_developer",
experience_level: "beginner", experience_level: "beginner",
full_name: profileData.displayName, full_name: profileData.displayName,
bio: profileData.bio, bio: profileData.bio,
location: profileData.location, location: profileData.location,
linkedin_url: profileData.linkedinUrl, linkedin_url: profileData.linkedinUrl,
github_url: profileData.githubUsername ? `https://github.com/${profileData.githubUsername}` : null, github_url: profileData.githubUsername
twitter_url: profileData.twitterUsername ? `https://twitter.com/${profileData.twitterUsername}` : null, ? `https://github.com/${profileData.githubUsername}`
: null,
twitter_url: profileData.twitterUsername
? `https://twitter.com/${profileData.twitterUsername}`
: null,
level: 1, level: 1,
total_xp: 0, total_xp: 0,
created_at: new Date().toISOString(), created_at: new Date().toISOString(),
@ -257,8 +274,12 @@ export default function Profile() {
bio: profileData.bio, bio: profileData.bio,
location: profileData.location, location: profileData.location,
linkedin_url: profileData.linkedinUrl, linkedin_url: profileData.linkedinUrl,
github_url: profileData.githubUsername ? `https://github.com/${profileData.githubUsername}` : null, github_url: profileData.githubUsername
twitter_url: profileData.twitterUsername ? `https://twitter.com/${profileData.twitterUsername}` : null, ? `https://github.com/${profileData.githubUsername}`
: null,
twitter_url: profileData.twitterUsername
? `https://twitter.com/${profileData.twitterUsername}`
: null,
updated_at: new Date().toISOString(), updated_at: new Date().toISOString(),
} as any); } as any);
} }
@ -271,7 +292,8 @@ export default function Profile() {
console.error("Profile save error:", error); console.error("Profile save error:", error);
toastError({ toastError({
title: "Update Failed", title: "Update Failed",
description: error.message || "Failed to update profile. Please try again.", description:
error.message || "Failed to update profile. Please try again.",
}); });
} finally { } finally {
setIsSaving(false); setIsSaving(false);
@ -280,14 +302,14 @@ export default function Profile() {
const getStatusIcon = (status: string) => { const getStatusIcon = (status: string) => {
switch (status) { switch (status) {
case 'online': case "online":
case 'connected': case "connected":
return <CheckCircle className="h-4 w-4 text-green-500" />; return <CheckCircle className="h-4 w-4 text-green-500" />;
case 'offline': case "offline":
case 'disconnected': case "disconnected":
return <XCircle className="h-4 w-4 text-red-500" />; return <XCircle className="h-4 w-4 text-red-500" />;
case 'maintenance': case "maintenance":
case 'syncing': case "syncing":
return <RefreshCw className="h-4 w-4 text-yellow-500 animate-spin" />; return <RefreshCw className="h-4 w-4 text-yellow-500 animate-spin" />;
default: default:
return <Circle className="h-4 w-4 text-gray-400" />; return <Circle className="h-4 w-4 text-gray-400" />;
@ -296,17 +318,17 @@ export default function Profile() {
const getStatusColor = (status: string) => { const getStatusColor = (status: string) => {
switch (status) { switch (status) {
case 'online': case "online":
case 'connected': case "connected":
return 'bg-green-500'; return "bg-green-500";
case 'offline': case "offline":
case 'disconnected': case "disconnected":
return 'bg-red-500'; return "bg-red-500";
case 'maintenance': case "maintenance":
case 'syncing': case "syncing":
return 'bg-yellow-500'; return "bg-yellow-500";
default: default:
return 'bg-gray-400'; return "bg-gray-400";
} }
}; };
@ -329,14 +351,20 @@ export default function Profile() {
</Avatar> </Avatar>
<div> <div>
<h1 className="text-3xl font-bold text-white"> <h1 className="text-3xl font-bold text-white">
{profile?.full_name || 'User Profile'} {profile?.full_name || "User Profile"}
</h1> </h1>
<p className="text-gray-300">{user?.email}</p> <p className="text-gray-300">{user?.email}</p>
<div className="mt-3 flex flex-wrap gap-2"> <div className="mt-3 flex flex-wrap gap-2">
<Badge variant="outline" className="border-purple-400/40 text-purple-200"> <Badge
variant="outline"
className="border-purple-400/40 text-purple-200"
>
Current streak: {currentStreak}d Current streak: {currentStreak}d
</Badge> </Badge>
<Badge variant="outline" className="border-purple-400/40 text-purple-200"> <Badge
variant="outline"
className="border-purple-400/40 text-purple-200"
>
Longest streak: {longestStreak}d Longest streak: {longestStreak}d
</Badge> </Badge>
</div> </div>
@ -346,19 +374,31 @@ export default function Profile() {
<Tabs defaultValue="profile" className="space-y-6"> <Tabs defaultValue="profile" className="space-y-6">
<TabsList className="grid w-full grid-cols-4 bg-slate-800/50 border border-slate-700"> <TabsList className="grid w-full grid-cols-4 bg-slate-800/50 border border-slate-700">
<TabsTrigger value="profile" className="text-white data-[state=active]:bg-purple-600"> <TabsTrigger
value="profile"
className="text-white data-[state=active]:bg-purple-600"
>
<User className="h-4 w-4 mr-2" /> <User className="h-4 w-4 mr-2" />
Profile Profile
</TabsTrigger> </TabsTrigger>
<TabsTrigger value="settings" className="text-white data-[state=active]:bg-purple-600"> <TabsTrigger
value="settings"
className="text-white data-[state=active]:bg-purple-600"
>
<Settings className="h-4 w-4 mr-2" /> <Settings className="h-4 w-4 mr-2" />
Settings Settings
</TabsTrigger> </TabsTrigger>
<TabsTrigger value="overseer" className="text-white data-[state=active]:bg-purple-600"> <TabsTrigger
value="overseer"
className="text-white data-[state=active]:bg-purple-600"
>
<Monitor className="h-4 w-4 mr-2" /> <Monitor className="h-4 w-4 mr-2" />
Overseer Overseer
</TabsTrigger> </TabsTrigger>
<TabsTrigger value="network" className="text-white data-[state=active]:bg-purple-600"> <TabsTrigger
value="network"
className="text-white data-[state=active]:bg-purple-600"
>
<Network className="h-4 w-4 mr-2" /> <Network className="h-4 w-4 mr-2" />
Network Network
</TabsTrigger> </TabsTrigger>
@ -379,48 +419,80 @@ export default function Profile() {
<CardContent className="space-y-4"> <CardContent className="space-y-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4"> <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div> <div>
<Label htmlFor="displayName" className="text-white">Display Name</Label> <Label htmlFor="displayName" className="text-white">
Display Name
</Label>
<Input <Input
id="displayName" id="displayName"
value={profileData.displayName} value={profileData.displayName}
onChange={(e) => setProfileData({ ...profileData, displayName: e.target.value })} onChange={(e) =>
setProfileData({
...profileData,
displayName: e.target.value,
})
}
className="bg-slate-900/50 border-slate-600 text-white" className="bg-slate-900/50 border-slate-600 text-white"
/> />
</div> </div>
<div> <div>
<Label htmlFor="company" className="text-white">Company</Label> <Label htmlFor="company" className="text-white">
Company
</Label>
<Input <Input
id="company" id="company"
value={profileData.company} value={profileData.company}
onChange={(e) => setProfileData({ ...profileData, company: e.target.value })} onChange={(e) =>
setProfileData({
...profileData,
company: e.target.value,
})
}
className="bg-slate-900/50 border-slate-600 text-white" className="bg-slate-900/50 border-slate-600 text-white"
/> />
</div> </div>
<div> <div>
<Label htmlFor="location" className="text-white">Location</Label> <Label htmlFor="location" className="text-white">
Location
</Label>
<Input <Input
id="location" id="location"
value={profileData.location} value={profileData.location}
onChange={(e) => setProfileData({ ...profileData, location: e.target.value })} onChange={(e) =>
setProfileData({
...profileData,
location: e.target.value,
})
}
className="bg-slate-900/50 border-slate-600 text-white" className="bg-slate-900/50 border-slate-600 text-white"
/> />
</div> </div>
<div> <div>
<Label htmlFor="website" className="text-white">Website</Label> <Label htmlFor="website" className="text-white">
Website
</Label>
<Input <Input
id="website" id="website"
value={profileData.website} value={profileData.website}
onChange={(e) => setProfileData({ ...profileData, website: e.target.value })} onChange={(e) =>
setProfileData({
...profileData,
website: e.target.value,
})
}
className="bg-slate-900/50 border-slate-600 text-white" className="bg-slate-900/50 border-slate-600 text-white"
/> />
</div> </div>
</div> </div>
<div> <div>
<Label htmlFor="bio" className="text-white">Bio</Label> <Label htmlFor="bio" className="text-white">
Bio
</Label>
<Textarea <Textarea
id="bio" id="bio"
value={profileData.bio} value={profileData.bio}
onChange={(e) => setProfileData({ ...profileData, bio: e.target.value })} onChange={(e) =>
setProfileData({ ...profileData, bio: e.target.value })
}
className="bg-slate-900/50 border-slate-600 text-white" className="bg-slate-900/50 border-slate-600 text-white"
rows={3} rows={3}
/> />
@ -428,35 +500,60 @@ export default function Profile() {
<Separator className="bg-slate-600" /> <Separator className="bg-slate-600" />
<div className="grid grid-cols-1 md:grid-cols-3 gap-4"> <div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div> <div>
<Label htmlFor="github" className="text-white">GitHub Username</Label> <Label htmlFor="github" className="text-white">
GitHub Username
</Label>
<Input <Input
id="github" id="github"
value={profileData.githubUsername} value={profileData.githubUsername}
onChange={(e) => setProfileData({ ...profileData, githubUsername: e.target.value })} onChange={(e) =>
setProfileData({
...profileData,
githubUsername: e.target.value,
})
}
className="bg-slate-900/50 border-slate-600 text-white" className="bg-slate-900/50 border-slate-600 text-white"
/> />
</div> </div>
<div> <div>
<Label htmlFor="twitter" className="text-white">Twitter Username</Label> <Label htmlFor="twitter" className="text-white">
Twitter Username
</Label>
<Input <Input
id="twitter" id="twitter"
value={profileData.twitterUsername} value={profileData.twitterUsername}
onChange={(e) => setProfileData({ ...profileData, twitterUsername: e.target.value })} onChange={(e) =>
setProfileData({
...profileData,
twitterUsername: e.target.value,
})
}
className="bg-slate-900/50 border-slate-600 text-white" className="bg-slate-900/50 border-slate-600 text-white"
/> />
</div> </div>
<div> <div>
<Label htmlFor="linkedin" className="text-white">LinkedIn URL</Label> <Label htmlFor="linkedin" className="text-white">
LinkedIn URL
</Label>
<Input <Input
id="linkedin" id="linkedin"
value={profileData.linkedinUrl} value={profileData.linkedinUrl}
onChange={(e) => setProfileData({ ...profileData, linkedinUrl: e.target.value })} onChange={(e) =>
setProfileData({
...profileData,
linkedinUrl: e.target.value,
})
}
className="bg-slate-900/50 border-slate-600 text-white" className="bg-slate-900/50 border-slate-600 text-white"
/> />
</div> </div>
</div> </div>
<Button onClick={handleProfileSave} disabled={isSaving} className="bg-purple-600 hover:bg-purple-700"> <Button
{isSaving ? 'Saving...' : 'Save Profile'} onClick={handleProfileSave}
disabled={isSaving}
className="bg-purple-600 hover:bg-purple-700"
>
{isSaving ? "Saving..." : "Save Profile"}
</Button> </Button>
</CardContent> </CardContent>
</Card> </Card>
@ -474,14 +571,20 @@ export default function Profile() {
</CardHeader> </CardHeader>
<CardContent className="space-y-4"> <CardContent className="space-y-4">
{Object.entries(notifications).map(([key, value]) => ( {Object.entries(notifications).map(([key, value]) => (
<div key={key} className="flex items-center justify-between"> <div
key={key}
className="flex items-center justify-between"
>
<Label className="text-white capitalize"> <Label className="text-white capitalize">
{key.replace(/([A-Z])/g, ' $1').trim()} {key.replace(/([A-Z])/g, " $1").trim()}
</Label> </Label>
<Switch <Switch
checked={value} checked={value}
onCheckedChange={(checked) => onCheckedChange={(checked) =>
setNotifications({ ...notifications, [key]: checked }) setNotifications({
...notifications,
[key]: checked,
})
} }
/> />
</div> </div>
@ -498,16 +601,28 @@ export default function Profile() {
</CardHeader> </CardHeader>
<CardContent className="space-y-4"> <CardContent className="space-y-4">
{Object.entries(privacy).map(([key, value]) => ( {Object.entries(privacy).map(([key, value]) => (
<div key={key} className="flex items-center justify-between"> <div
key={key}
className="flex items-center justify-between"
>
<Label className="text-white capitalize"> <Label className="text-white capitalize">
{key.replace(/([A-Z])/g, ' $1').trim()} {key.replace(/([A-Z])/g, " $1").trim()}
</Label> </Label>
<Switch <Switch
checked={typeof value === 'boolean' ? value : value === 'public'} checked={
onCheckedChange={(checked) => typeof value === "boolean"
setPrivacy({ ? value
...privacy, : value === "public"
[key]: typeof value === 'boolean' ? checked : (checked ? 'public' : 'private') }
onCheckedChange={(checked) =>
setPrivacy({
...privacy,
[key]:
typeof value === "boolean"
? checked
: checked
? "public"
: "private",
}) })
} }
/> />
@ -526,7 +641,9 @@ export default function Profile() {
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div> <div>
<p className="text-sm text-gray-400">System Health</p> <p className="text-sm text-gray-400">System Health</p>
<p className="text-2xl font-bold text-green-400">{overseerData.systemHealth}%</p> <p className="text-2xl font-bold text-green-400">
{overseerData.systemHealth}%
</p>
</div> </div>
<Activity className="h-8 w-8 text-green-400" /> <Activity className="h-8 w-8 text-green-400" />
</div> </div>
@ -538,7 +655,9 @@ export default function Profile() {
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div> <div>
<p className="text-sm text-gray-400">Active Users</p> <p className="text-sm text-gray-400">Active Users</p>
<p className="text-2xl font-bold text-blue-400">{overseerData.activeUsers}</p> <p className="text-2xl font-bold text-blue-400">
{overseerData.activeUsers}
</p>
</div> </div>
<Users className="h-8 w-8 text-blue-400" /> <Users className="h-8 w-8 text-blue-400" />
</div> </div>
@ -550,7 +669,9 @@ export default function Profile() {
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div> <div>
<p className="text-sm text-gray-400">Server Load</p> <p className="text-sm text-gray-400">Server Load</p>
<p className="text-2xl font-bold text-yellow-400">{overseerData.serverLoad}%</p> <p className="text-2xl font-bold text-yellow-400">
{overseerData.serverLoad}%
</p>
</div> </div>
<Server className="h-8 w-8 text-yellow-400" /> <Server className="h-8 w-8 text-yellow-400" />
</div> </div>
@ -562,7 +683,9 @@ export default function Profile() {
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div> <div>
<p className="text-sm text-gray-400">Error Rate</p> <p className="text-sm text-gray-400">Error Rate</p>
<p className="text-2xl font-bold text-red-400">{overseerData.errorRate}%</p> <p className="text-2xl font-bold text-red-400">
{overseerData.errorRate}%
</p>
</div> </div>
<AlertTriangle className="h-8 w-8 text-red-400" /> <AlertTriangle className="h-8 w-8 text-red-400" />
</div> </div>
@ -583,19 +706,29 @@ export default function Profile() {
<CardContent> <CardContent>
<div className="space-y-4"> <div className="space-y-4">
{siteStatuses.map((site, index) => ( {siteStatuses.map((site, index) => (
<div key={index} className="flex items-center justify-between p-4 bg-slate-900/50 rounded-lg border border-slate-600"> <div
key={index}
className="flex items-center justify-between p-4 bg-slate-900/50 rounded-lg border border-slate-600"
>
<div className="flex items-center space-x-4"> <div className="flex items-center space-x-4">
{getStatusIcon(site.status)} {getStatusIcon(site.status)}
<div> <div>
<h4 className="text-white font-medium">{site.name}</h4> <h4 className="text-white font-medium">
{site.name}
</h4>
<p className="text-sm text-gray-400">{site.url}</p> <p className="text-sm text-gray-400">{site.url}</p>
</div> </div>
</div> </div>
<div className="text-right"> <div className="text-right">
<Badge variant="outline" className={`${getStatusColor(site.status)} text-white border-0`}> <Badge
variant="outline"
className={`${getStatusColor(site.status)} text-white border-0`}
>
{site.status} {site.status}
</Badge> </Badge>
<p className="text-sm text-gray-400 mt-1">{site.responseTime}ms</p> <p className="text-sm text-gray-400 mt-1">
{site.responseTime}ms
</p>
</div> </div>
</div> </div>
))} ))}
@ -619,17 +752,29 @@ export default function Profile() {
<CardContent> <CardContent>
<div className="space-y-4"> <div className="space-y-4">
{crossSiteData.map((site, index) => ( {crossSiteData.map((site, index) => (
<div key={index} className="flex items-center justify-between p-4 bg-slate-900/50 rounded-lg border border-slate-600"> <div
key={index}
className="flex items-center justify-between p-4 bg-slate-900/50 rounded-lg border border-slate-600"
>
<div className="flex items-center space-x-4"> <div className="flex items-center space-x-4">
{getStatusIcon(site.status)} {getStatusIcon(site.status)}
<div> <div>
<h4 className="text-white font-medium">{site.siteName}</h4> <h4 className="text-white font-medium">
<p className="text-sm text-gray-400">Last sync: {site.lastSync}</p> {site.siteName}
</h4>
<p className="text-sm text-gray-400">
Last sync: {site.lastSync}
</p>
</div> </div>
</div> </div>
<div className="text-right"> <div className="text-right">
<p className="text-white font-medium">{site.dataExchanged} MB</p> <p className="text-white font-medium">
<Badge variant="outline" className={`${getStatusColor(site.status)} text-white border-0 mt-1`}> {site.dataExchanged} MB
</p>
<Badge
variant="outline"
className={`${getStatusColor(site.status)} text-white border-0 mt-1`}
>
{site.status} {site.status}
</Badge> </Badge>
</div> </div>
@ -653,28 +798,42 @@ export default function Profile() {
<h4 className="text-white font-medium">Auto-Sync</h4> <h4 className="text-white font-medium">Auto-Sync</h4>
<Switch defaultChecked /> <Switch defaultChecked />
</div> </div>
<p className="text-sm text-gray-400">Automatically sync data between sites</p> <p className="text-sm text-gray-400">
Automatically sync data between sites
</p>
</div> </div>
<div className="p-4 bg-slate-900/50 rounded-lg border border-slate-600"> <div className="p-4 bg-slate-900/50 rounded-lg border border-slate-600">
<div className="flex items-center justify-between mb-2"> <div className="flex items-center justify-between mb-2">
<h4 className="text-white font-medium">Real-time Updates</h4> <h4 className="text-white font-medium">
Real-time Updates
</h4>
<Switch defaultChecked /> <Switch defaultChecked />
</div> </div>
<p className="text-sm text-gray-400">Enable real-time cross-site communication</p> <p className="text-sm text-gray-400">
Enable real-time cross-site communication
</p>
</div> </div>
<div className="p-4 bg-slate-900/50 rounded-lg border border-slate-600"> <div className="p-4 bg-slate-900/50 rounded-lg border border-slate-600">
<div className="flex items-center justify-between mb-2"> <div className="flex items-center justify-between mb-2">
<h4 className="text-white font-medium">Data Encryption</h4> <h4 className="text-white font-medium">
Data Encryption
</h4>
<Switch defaultChecked /> <Switch defaultChecked />
</div> </div>
<p className="text-sm text-gray-400">Encrypt data during transmission</p> <p className="text-sm text-gray-400">
Encrypt data during transmission
</p>
</div> </div>
<div className="p-4 bg-slate-900/50 rounded-lg border border-slate-600"> <div className="p-4 bg-slate-900/50 rounded-lg border border-slate-600">
<div className="flex items-center justify-between mb-2"> <div className="flex items-center justify-between mb-2">
<h4 className="text-white font-medium">Monitoring Alerts</h4> <h4 className="text-white font-medium">
Monitoring Alerts
</h4>
<Switch defaultChecked /> <Switch defaultChecked />
</div> </div>
<p className="text-sm text-gray-400">Get notified of connection issues</p> <p className="text-sm text-gray-400">
Get notified of connection issues
</p>
</div> </div>
</div> </div>
</CardContent> </CardContent>

View file

@ -75,8 +75,9 @@ const ProfilePassport = () => {
const loadPassport = async () => { const loadPassport = async () => {
setLoading(true); setLoading(true);
try { try {
let resolvedProfile: (AethexUserProfile & { email?: string | null }) | null = let resolvedProfile:
null; | (AethexUserProfile & { email?: string | null })
| null = null;
let resolvedId: string | null = null; let resolvedId: string | null = null;
if (isSelfRoute) { if (isSelfRoute) {
@ -89,7 +90,8 @@ const ProfilePassport = () => {
} }
if ( if (
currentUser.username.toLowerCase() === requestedUsername.toLowerCase() && currentUser.username.toLowerCase() ===
requestedUsername.toLowerCase() &&
currentUser.username !== requestedUsername currentUser.username !== requestedUsername
) { ) {
navigate(`/passport/${currentUser.username}`, { replace: true }); navigate(`/passport/${currentUser.username}`, { replace: true });
@ -215,7 +217,7 @@ const ProfilePassport = () => {
...resolvedProfile, ...resolvedProfile,
email: email:
resolvedProfile.email ?? resolvedProfile.email ??
(viewingSelf ? user?.email ?? authProfile?.email ?? null : null), (viewingSelf ? (user?.email ?? authProfile?.email ?? null) : null),
}); });
setAchievements(achievementList ?? []); setAchievements(achievementList ?? []);
setInterests(interestList ?? []); setInterests(interestList ?? []);
@ -261,7 +263,8 @@ const ProfilePassport = () => {
((user?.id && profile.id && user.id === profile.id) || ((user?.id && profile.id && user.id === profile.id) ||
(authProfile?.username && (authProfile?.username &&
profile.username && profile.username &&
authProfile.username.toLowerCase() === profile.username.toLowerCase())), authProfile.username.toLowerCase() ===
profile.username.toLowerCase())),
); );
if (loading) { if (loading) {