Prettier format pending files
This commit is contained in:
parent
c00bdfcc2d
commit
9e8163d005
9 changed files with 773 additions and 336 deletions
|
|
@ -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)) {
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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" />
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
@ -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">
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue