Prettier format pending files
This commit is contained in:
parent
18b64a2760
commit
56ea7df6a9
6 changed files with 133 additions and 36 deletions
|
|
@ -223,7 +223,10 @@ const PassportSummary = ({
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-2 text-slate-100">
|
<div className="flex items-center gap-2 text-slate-100">
|
||||||
<span className="text-xl">
|
<span className="text-xl">
|
||||||
{getAchievementEmoji(achievement.icon as any, achievement.name)}
|
{getAchievementEmoji(
|
||||||
|
achievement.icon as any,
|
||||||
|
achievement.name,
|
||||||
|
)}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-sm font-medium">
|
<span className="text-sm font-medium">
|
||||||
{achievement.name}
|
{achievement.name}
|
||||||
|
|
|
||||||
|
|
@ -331,7 +331,10 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({
|
||||||
if (inviteProcessedRef.current) return;
|
if (inviteProcessedRef.current) return;
|
||||||
if (!user) return;
|
if (!user) return;
|
||||||
try {
|
try {
|
||||||
const qs = typeof window !== "undefined" ? new URLSearchParams(window.location.search) : null;
|
const qs =
|
||||||
|
typeof window !== "undefined"
|
||||||
|
? new URLSearchParams(window.location.search)
|
||||||
|
: null;
|
||||||
const token = qs?.get("invite");
|
const token = qs?.get("invite");
|
||||||
if (!token) return;
|
if (!token) return;
|
||||||
inviteProcessedRef.current = true;
|
inviteProcessedRef.current = true;
|
||||||
|
|
@ -339,7 +342,10 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({
|
||||||
try {
|
try {
|
||||||
await mod.aethexSocialService.acceptInvite(token, user.id);
|
await mod.aethexSocialService.acceptInvite(token, user.id);
|
||||||
try {
|
try {
|
||||||
aethexToast.success({ title: "Invitation accepted", description: "You're now connected." });
|
aethexToast.success({
|
||||||
|
title: "Invitation accepted",
|
||||||
|
description: "You're now connected.",
|
||||||
|
});
|
||||||
} catch {}
|
} catch {}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn("Invite accept failed", e);
|
console.warn("Invite accept failed", e);
|
||||||
|
|
|
||||||
|
|
@ -10,13 +10,19 @@ export const aethexSocialService = {
|
||||||
.limit(limit);
|
.limit(limit);
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error("Failed to load recommended profiles:", (error as any)?.message || error);
|
console.error(
|
||||||
|
"Failed to load recommended profiles:",
|
||||||
|
(error as any)?.message || error,
|
||||||
|
);
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
return (data || []) as any[];
|
return (data || []) as any[];
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Unexpected error loading recommended profiles:", (error as any)?.message || error);
|
console.error(
|
||||||
|
"Unexpected error loading recommended profiles:",
|
||||||
|
(error as any)?.message || error,
|
||||||
|
);
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -29,13 +35,19 @@ export const aethexSocialService = {
|
||||||
.eq("follower_id", userId);
|
.eq("follower_id", userId);
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error("Failed to load following list:", (error as any)?.message || error);
|
console.error(
|
||||||
|
"Failed to load following list:",
|
||||||
|
(error as any)?.message || error,
|
||||||
|
);
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
return (data as any[]).map((r: any) => r.following_id);
|
return (data as any[]).map((r: any) => r.following_id);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Unexpected error loading following list:", (error as any)?.message || error);
|
console.error(
|
||||||
|
"Unexpected error loading following list:",
|
||||||
|
(error as any)?.message || error,
|
||||||
|
);
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -67,17 +79,27 @@ export const aethexSocialService = {
|
||||||
const resp = await fetch("/api/invites", {
|
const resp = await fetch("/api/invites", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({ inviter_id: inviterId, invitee_email: email, message }),
|
body: JSON.stringify({
|
||||||
|
inviter_id: inviterId,
|
||||||
|
invitee_email: email,
|
||||||
|
message,
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
if (!resp.ok) {
|
if (!resp.ok) {
|
||||||
const err = await resp.text();
|
const err = await resp.text();
|
||||||
throw new Error(err || "Failed to send invite");
|
throw new Error(err || "Failed to send invite");
|
||||||
}
|
}
|
||||||
return (await resp.json()) as { ok: boolean; inviteUrl: string; token: string };
|
return (await resp.json()) as {
|
||||||
|
ok: boolean;
|
||||||
|
inviteUrl: string;
|
||||||
|
token: string;
|
||||||
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
async listInvites(inviterId: string) {
|
async listInvites(inviterId: string) {
|
||||||
const resp = await fetch(`/api/invites?inviter_id=${encodeURIComponent(inviterId)}`);
|
const resp = await fetch(
|
||||||
|
`/api/invites?inviter_id=${encodeURIComponent(inviterId)}`,
|
||||||
|
);
|
||||||
if (!resp.ok) return [];
|
if (!resp.ok) return [];
|
||||||
return await resp.json();
|
return await resp.json();
|
||||||
},
|
},
|
||||||
|
|
@ -129,7 +151,9 @@ export const aethexSocialService = {
|
||||||
|
|
||||||
async endorseSkill(endorserId: string, endorsedId: string, skill: string) {
|
async endorseSkill(endorserId: string, endorsedId: string, skill: string) {
|
||||||
const payload = { endorser_id: endorserId, endorsed_id: endorsedId, skill };
|
const payload = { endorser_id: endorserId, endorsed_id: endorsedId, skill };
|
||||||
const { error } = await supabase.from("endorsements").insert(payload as any);
|
const { error } = await supabase
|
||||||
|
.from("endorsements")
|
||||||
|
.insert(payload as any);
|
||||||
if (error) throw new Error(error.message || "Unable to endorse");
|
if (error) throw new Error(error.message || "Unable to endorse");
|
||||||
await this.applyReward(endorsedId, "endorsement_received", 2);
|
await this.applyReward(endorsedId, "endorsement_received", 2);
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -82,7 +82,11 @@ export default function Network() {
|
||||||
toast({ description: "Invitation sent" });
|
toast({ description: "Invitation sent" });
|
||||||
setInviteEmail("");
|
setInviteEmail("");
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
toast({ variant: "destructive", title: "Failed to send invite", description: e?.message || "Try again later" });
|
toast({
|
||||||
|
variant: "destructive",
|
||||||
|
title: "Failed to send invite",
|
||||||
|
description: e?.message || "Try again later",
|
||||||
|
});
|
||||||
} finally {
|
} finally {
|
||||||
setInviteSending(false);
|
setInviteSending(false);
|
||||||
}
|
}
|
||||||
|
|
@ -94,7 +98,11 @@ export default function Network() {
|
||||||
await aethexSocialService.endorseSkill(user.id, targetId, skill);
|
await aethexSocialService.endorseSkill(user.id, targetId, skill);
|
||||||
toast({ description: `Endorsed for ${skill}` });
|
toast({ description: `Endorsed for ${skill}` });
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
toast({ variant: "destructive", title: "Failed to endorse", description: e?.message || "Try again later" });
|
toast({
|
||||||
|
variant: "destructive",
|
||||||
|
title: "Failed to endorse",
|
||||||
|
description: e?.message || "Try again later",
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -252,7 +260,10 @@ export default function Network() {
|
||||||
const p = (c as any).user_profiles || {};
|
const p = (c as any).user_profiles || {};
|
||||||
const display = p.full_name || p.username || c.connection_id;
|
const display = p.full_name || p.username || c.connection_id;
|
||||||
return (
|
return (
|
||||||
<div key={c.connection_id} className="flex items-center justify-between p-3 rounded-lg border border-border/50">
|
<div
|
||||||
|
key={c.connection_id}
|
||||||
|
className="flex items-center justify-between p-3 rounded-lg border border-border/50"
|
||||||
|
>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<Avatar className="h-10 w-10">
|
<Avatar className="h-10 w-10">
|
||||||
<AvatarImage src={p.avatar_url || undefined} />
|
<AvatarImage src={p.avatar_url || undefined} />
|
||||||
|
|
@ -260,15 +271,21 @@ export default function Network() {
|
||||||
</Avatar>
|
</Avatar>
|
||||||
<div>
|
<div>
|
||||||
<div className="font-medium">{display}</div>
|
<div className="font-medium">{display}</div>
|
||||||
<div className="text-xs text-muted-foreground">Connected</div>
|
<div className="text-xs text-muted-foreground">
|
||||||
|
Connected
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Button size="sm" variant="outline">Message</Button>
|
<Button size="sm" variant="outline">
|
||||||
|
Message
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
{connections.length === 0 && (
|
{connections.length === 0 && (
|
||||||
<div className="text-sm text-muted-foreground">No connections yet.</div>
|
<div className="text-sm text-muted-foreground">
|
||||||
|
No connections yet.
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
@ -330,14 +347,19 @@ export default function Network() {
|
||||||
<Card className="bg-card/50 border-border/50">
|
<Card className="bg-card/50 border-border/50">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>Endorsements</CardTitle>
|
<CardTitle>Endorsements</CardTitle>
|
||||||
<CardDescription>Recognize skills of your peers</CardDescription>
|
<CardDescription>
|
||||||
|
Recognize skills of your peers
|
||||||
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-3">
|
<CardContent className="space-y-3">
|
||||||
{connections.slice(0, 6).map((c) => {
|
{connections.slice(0, 6).map((c) => {
|
||||||
const p = (c as any).user_profiles || {};
|
const p = (c as any).user_profiles || {};
|
||||||
const display = p.full_name || p.username || c.connection_id;
|
const display = p.full_name || p.username || c.connection_id;
|
||||||
return (
|
return (
|
||||||
<div key={`endorse-${c.connection_id}`} className="flex items-center justify-between p-3 rounded-lg border border-border/50">
|
<div
|
||||||
|
key={`endorse-${c.connection_id}`}
|
||||||
|
className="flex items-center justify-between p-3 rounded-lg border border-border/50"
|
||||||
|
>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<Avatar className="h-8 w-8">
|
<Avatar className="h-8 w-8">
|
||||||
<AvatarImage src={p.avatar_url || undefined} />
|
<AvatarImage src={p.avatar_url || undefined} />
|
||||||
|
|
@ -346,8 +368,22 @@ export default function Network() {
|
||||||
<div className="text-sm">{display}</div>
|
<div className="text-sm">{display}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2 flex-wrap">
|
<div className="flex gap-2 flex-wrap">
|
||||||
{(["Leadership","Systems","Frontend","Backend"] as const).map((skill) => (
|
{(
|
||||||
<Button key={skill} size="xs" variant="outline" onClick={() => handleEndorse(c.connection_id, skill)}>
|
[
|
||||||
|
"Leadership",
|
||||||
|
"Systems",
|
||||||
|
"Frontend",
|
||||||
|
"Backend",
|
||||||
|
] as const
|
||||||
|
).map((skill) => (
|
||||||
|
<Button
|
||||||
|
key={skill}
|
||||||
|
size="xs"
|
||||||
|
variant="outline"
|
||||||
|
onClick={() =>
|
||||||
|
handleEndorse(c.connection_id, skill)
|
||||||
|
}
|
||||||
|
>
|
||||||
{skill}
|
{skill}
|
||||||
</Button>
|
</Button>
|
||||||
))}
|
))}
|
||||||
|
|
|
||||||
|
|
@ -431,13 +431,17 @@ const ProfilePassport = () => {
|
||||||
<div className="flex items-center gap-3 text-white">
|
<div className="flex items-center gap-3 text-white">
|
||||||
<span className="text-3xl">
|
<span className="text-3xl">
|
||||||
{((): string => {
|
{((): string => {
|
||||||
const key = String(achievement.icon || achievement.name || "").toLowerCase();
|
const key = String(
|
||||||
|
achievement.icon || achievement.name || "",
|
||||||
|
).toLowerCase();
|
||||||
if (/founding|founder/.test(key)) return "🎖️";
|
if (/founding|founder/.test(key)) return "🎖️";
|
||||||
if (/trophy|award|medal|badge/.test(key)) return "🏆";
|
if (/trophy|award|medal|badge/.test(key))
|
||||||
|
return "🏆";
|
||||||
if (/welcome/.test(key)) return "🎉";
|
if (/welcome/.test(key)) return "🎉";
|
||||||
if (/star/.test(key)) return "⭐";
|
if (/star/.test(key)) return "⭐";
|
||||||
if (/rocket|launch/.test(key)) return "🚀";
|
if (/rocket|launch/.test(key)) return "🚀";
|
||||||
return typeof achievement.icon === "string" && achievement.icon.length <= 3
|
return typeof achievement.icon === "string" &&
|
||||||
|
achievement.icon.length <= 3
|
||||||
? (achievement.icon as string)
|
? (achievement.icon as string)
|
||||||
: "🏅";
|
: "🏅";
|
||||||
})()}
|
})()}
|
||||||
|
|
@ -487,14 +491,26 @@ const ProfilePassport = () => {
|
||||||
</Link>
|
</Link>
|
||||||
</Button>
|
</Button>
|
||||||
) : profile.email ? (
|
) : profile.email ? (
|
||||||
<Button asChild variant="outline" className="border-slate-700/70 text-slate-100">
|
<Button
|
||||||
<a href={`mailto:${profile.email}?subject=${encodeURIComponent("Collaboration invite")}&body=${encodeURIComponent("Hi, I'd like to collaborate on a project.")}`}>
|
asChild
|
||||||
|
variant="outline"
|
||||||
|
className="border-slate-700/70 text-slate-100"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href={`mailto:${profile.email}?subject=${encodeURIComponent("Collaboration invite")}&body=${encodeURIComponent("Hi, I'd like to collaborate on a project.")}`}
|
||||||
|
>
|
||||||
Invite to collaborate
|
Invite to collaborate
|
||||||
</a>
|
</a>
|
||||||
</Button>
|
</Button>
|
||||||
) : (
|
) : (
|
||||||
<Button asChild variant="outline" className="border-slate-700/70 text-slate-100">
|
<Button
|
||||||
<Link to={`/contact?topic=collaboration&about=${encodeURIComponent(profile.username || profile.full_name || "member")}`}>
|
asChild
|
||||||
|
variant="outline"
|
||||||
|
className="border-slate-700/70 text-slate-100"
|
||||||
|
>
|
||||||
|
<Link
|
||||||
|
to={`/contact?topic=collaboration&about=${encodeURIComponent(profile.username || profile.full_name || "member")}`}
|
||||||
|
>
|
||||||
Invite to collaborate
|
Invite to collaborate
|
||||||
</Link>
|
</Link>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
||||||
|
|
@ -621,10 +621,11 @@ export function createServer() {
|
||||||
|
|
||||||
// Invites API
|
// Invites API
|
||||||
const baseUrl =
|
const baseUrl =
|
||||||
process.env.PUBLIC_BASE_URL || process.env.SITE_URL || "https://aethex.biz";
|
process.env.PUBLIC_BASE_URL ||
|
||||||
|
process.env.SITE_URL ||
|
||||||
|
"https://aethex.biz";
|
||||||
|
|
||||||
const safeEmail = (v?: string | null) =>
|
const safeEmail = (v?: string | null) => (v || "").trim().toLowerCase();
|
||||||
(v || "").trim().toLowerCase();
|
|
||||||
|
|
||||||
const accrue = async (
|
const accrue = async (
|
||||||
userId: string,
|
userId: string,
|
||||||
|
|
@ -731,7 +732,8 @@ export function createServer() {
|
||||||
|
|
||||||
app.get("/api/invites", async (req, res) => {
|
app.get("/api/invites", async (req, res) => {
|
||||||
const inviter = String(req.query.inviter_id || "");
|
const inviter = String(req.query.inviter_id || "");
|
||||||
if (!inviter) return res.status(400).json({ error: "inviter_id required" });
|
if (!inviter)
|
||||||
|
return res.status(400).json({ error: "inviter_id required" });
|
||||||
try {
|
try {
|
||||||
const { data, error } = await adminSupabase
|
const { data, error } = await adminSupabase
|
||||||
.from("invites")
|
.from("invites")
|
||||||
|
|
@ -751,7 +753,9 @@ export function createServer() {
|
||||||
acceptor_id?: string;
|
acceptor_id?: string;
|
||||||
};
|
};
|
||||||
if (!token || !acceptor_id) {
|
if (!token || !acceptor_id) {
|
||||||
return res.status(400).json({ error: "token and acceptor_id required" });
|
return res
|
||||||
|
.status(400)
|
||||||
|
.json({ error: "token and acceptor_id required" });
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const { data: invite, error } = await adminSupabase
|
const { data: invite, error } = await adminSupabase
|
||||||
|
|
@ -766,7 +770,11 @@ export function createServer() {
|
||||||
const now = new Date().toISOString();
|
const now = new Date().toISOString();
|
||||||
const { error: upErr } = await adminSupabase
|
const { error: upErr } = await adminSupabase
|
||||||
.from("invites")
|
.from("invites")
|
||||||
.update({ status: "accepted", accepted_by: acceptor_id, accepted_at: now })
|
.update({
|
||||||
|
status: "accepted",
|
||||||
|
accepted_by: acceptor_id,
|
||||||
|
accepted_at: now,
|
||||||
|
})
|
||||||
.eq("id", (invite as any).id);
|
.eq("id", (invite as any).id);
|
||||||
if (upErr) return res.status(500).json({ error: upErr.message });
|
if (upErr) return res.status(500).json({ error: upErr.message });
|
||||||
|
|
||||||
|
|
@ -785,10 +793,14 @@ export function createServer() {
|
||||||
if (inviterId) {
|
if (inviterId) {
|
||||||
await accrue(inviterId, "xp", 100, "invite_accepted", { token });
|
await accrue(inviterId, "xp", 100, "invite_accepted", { token });
|
||||||
await accrue(inviterId, "loyalty", 50, "invite_accepted", { token });
|
await accrue(inviterId, "loyalty", 50, "invite_accepted", { token });
|
||||||
await accrue(inviterId, "reputation", 2, "invite_accepted", { token });
|
await accrue(inviterId, "reputation", 2, "invite_accepted", {
|
||||||
|
token,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
await accrue(acceptor_id, "xp", 50, "invite_accepted", { token });
|
await accrue(acceptor_id, "xp", 50, "invite_accepted", { token });
|
||||||
await accrue(acceptor_id, "reputation", 1, "invite_accepted", { token });
|
await accrue(acceptor_id, "reputation", 1, "invite_accepted", {
|
||||||
|
token,
|
||||||
|
});
|
||||||
|
|
||||||
return res.json({ ok: true });
|
return res.json({ ok: true });
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue