Prettier format pending files
This commit is contained in:
parent
7bf4ce124d
commit
db87f720a1
5 changed files with 157 additions and 53 deletions
|
|
@ -115,14 +115,24 @@ export function FeedItemCard({
|
||||||
<div className="rounded-2xl border border-border/40 bg-background/80 p-4">
|
<div className="rounded-2xl border border-border/40 bg-background/80 p-4">
|
||||||
<div className="flex flex-wrap items-center justify-between gap-3 text-sm text-muted-foreground">
|
<div className="flex flex-wrap items-center justify-between gap-3 text-sm text-muted-foreground">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<Button variant="ghost" size="sm" className="gap-2 pl-2 pr-3" onClick={() => onLike(item.id)}>
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
className="gap-2 pl-2 pr-3"
|
||||||
|
onClick={() => onLike(item.id)}
|
||||||
|
>
|
||||||
<Heart className="h-4 w-4 text-aethex-400" />
|
<Heart className="h-4 w-4 text-aethex-400" />
|
||||||
<span className="font-medium text-foreground">
|
<span className="font-medium text-foreground">
|
||||||
{item.likes.toLocaleString()}
|
{item.likes.toLocaleString()}
|
||||||
</span>
|
</span>
|
||||||
<span className="hidden sm:inline">Like</span>
|
<span className="hidden sm:inline">Like</span>
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="ghost" size="sm" className="gap-2 pl-2 pr-3" onClick={() => onComment(item.id)}>
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
className="gap-2 pl-2 pr-3"
|
||||||
|
onClick={() => onComment(item.id)}
|
||||||
|
>
|
||||||
<MessageCircle className="h-4 w-4 text-aethex-400" />
|
<MessageCircle className="h-4 w-4 text-aethex-400" />
|
||||||
<span className="font-medium text-foreground">
|
<span className="font-medium text-foreground">
|
||||||
{item.comments.toLocaleString()}
|
{item.comments.toLocaleString()}
|
||||||
|
|
|
||||||
|
|
@ -255,9 +255,15 @@ export const communityService = {
|
||||||
if (!error) {
|
if (!error) {
|
||||||
return (Array.isArray(data) ? data : []) as CommunityPost[];
|
return (Array.isArray(data) ? data : []) as CommunityPost[];
|
||||||
}
|
}
|
||||||
console.warn("Supabase getPosts relational select failed:", (error as any)?.message || error);
|
console.warn(
|
||||||
|
"Supabase getPosts relational select failed:",
|
||||||
|
(error as any)?.message || error,
|
||||||
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn("Supabase getPosts relational select threw:", (e as any)?.message || e);
|
console.warn(
|
||||||
|
"Supabase getPosts relational select threw:",
|
||||||
|
(e as any)?.message || e,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2) Fallback to simple posts select, then hydrate author profiles manually
|
// 2) Fallback to simple posts select, then hydrate author profiles manually
|
||||||
|
|
@ -269,7 +275,9 @@ export const communityService = {
|
||||||
.order("created_at", { ascending: false })
|
.order("created_at", { ascending: false })
|
||||||
.limit(limit);
|
.limit(limit);
|
||||||
if (!postsErr && Array.isArray(posts) && posts.length) {
|
if (!postsErr && Array.isArray(posts) && posts.length) {
|
||||||
const authorIds = Array.from(new Set(posts.map((p: any) => p.author_id).filter(Boolean)));
|
const authorIds = Array.from(
|
||||||
|
new Set(posts.map((p: any) => p.author_id).filter(Boolean)),
|
||||||
|
);
|
||||||
let profilesById: Record<string, any> = {};
|
let profilesById: Record<string, any> = {};
|
||||||
if (authorIds.length) {
|
if (authorIds.length) {
|
||||||
const { data: profiles, error: profErr } = await supabase
|
const { data: profiles, error: profErr } = await supabase
|
||||||
|
|
@ -278,20 +286,39 @@ export const communityService = {
|
||||||
.in("id", authorIds);
|
.in("id", authorIds);
|
||||||
if (!profErr && Array.isArray(profiles)) {
|
if (!profErr && Array.isArray(profiles)) {
|
||||||
profilesById = Object.fromEntries(
|
profilesById = Object.fromEntries(
|
||||||
profiles.map((u: any) => [u.id, { username: u.username, full_name: u.full_name, avatar_url: u.avatar_url }]),
|
profiles.map((u: any) => [
|
||||||
|
u.id,
|
||||||
|
{
|
||||||
|
username: u.username,
|
||||||
|
full_name: u.full_name,
|
||||||
|
avatar_url: u.avatar_url,
|
||||||
|
},
|
||||||
|
]),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return posts.map((p: any) => ({ ...p, user_profiles: profilesById[p.author_id] || null })) as CommunityPost[];
|
return posts.map((p: any) => ({
|
||||||
|
...p,
|
||||||
|
user_profiles: profilesById[p.author_id] || null,
|
||||||
|
})) as CommunityPost[];
|
||||||
}
|
}
|
||||||
if (postsErr) console.warn("Supabase getPosts simple select failed:", (postsErr as any)?.message || postsErr);
|
if (postsErr)
|
||||||
|
console.warn(
|
||||||
|
"Supabase getPosts simple select failed:",
|
||||||
|
(postsErr as any)?.message || postsErr,
|
||||||
|
);
|
||||||
} catch (e2) {
|
} catch (e2) {
|
||||||
console.warn("Supabase getPosts simple select threw:", (e2 as any)?.message || e2);
|
console.warn(
|
||||||
|
"Supabase getPosts simple select threw:",
|
||||||
|
(e2 as any)?.message || e2,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3) Final fallback to API if available
|
// 3) Final fallback to API if available
|
||||||
try {
|
try {
|
||||||
const resp = await fetch(`/api/posts?limit=${encodeURIComponent(String(limit))}`);
|
const resp = await fetch(
|
||||||
|
`/api/posts?limit=${encodeURIComponent(String(limit))}`,
|
||||||
|
);
|
||||||
if (resp.ok) {
|
if (resp.ok) {
|
||||||
const ct = resp.headers.get("content-type") || "";
|
const ct = resp.headers.get("content-type") || "";
|
||||||
if (ct.includes("application/json") || ct.includes("json")) {
|
if (ct.includes("application/json") || ct.includes("json")) {
|
||||||
|
|
@ -299,13 +326,24 @@ export const communityService = {
|
||||||
return (Array.isArray(payload) ? payload : []) as CommunityPost[];
|
return (Array.isArray(payload) ? payload : []) as CommunityPost[];
|
||||||
} else {
|
} else {
|
||||||
const text = await resp.text();
|
const text = await resp.text();
|
||||||
console.warn("API fallback returned non-JSON content-type:", ct, text.slice(0, 120));
|
console.warn(
|
||||||
|
"API fallback returned non-JSON content-type:",
|
||||||
|
ct,
|
||||||
|
text.slice(0, 120),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.warn("API fallback /api/posts not ok:", resp.status, resp.statusText);
|
console.warn(
|
||||||
|
"API fallback /api/posts not ok:",
|
||||||
|
resp.status,
|
||||||
|
resp.statusText,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} catch (apiErr) {
|
} catch (apiErr) {
|
||||||
console.error("API fallback for getPosts failed:", (apiErr as any)?.message || apiErr);
|
console.error(
|
||||||
|
"API fallback for getPosts failed:",
|
||||||
|
(apiErr as any)?.message || apiErr,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return actual empty array (no demo/mocks)
|
// Return actual empty array (no demo/mocks)
|
||||||
|
|
@ -364,11 +402,14 @@ export const communityService = {
|
||||||
|
|
||||||
async likePost(postId: string, userId: string): Promise<number | null> {
|
async likePost(postId: string, userId: string): Promise<number | null> {
|
||||||
try {
|
try {
|
||||||
const resp = await fetch(`/api/community/posts/${encodeURIComponent(postId)}/like`, {
|
const resp = await fetch(
|
||||||
method: "POST",
|
`/api/community/posts/${encodeURIComponent(postId)}/like`,
|
||||||
headers: { "Content-Type": "application/json" },
|
{
|
||||||
body: JSON.stringify({ user_id: userId }),
|
method: "POST",
|
||||||
});
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ user_id: userId }),
|
||||||
|
},
|
||||||
|
);
|
||||||
if (resp.ok) {
|
if (resp.ok) {
|
||||||
const json = await resp.json();
|
const json = await resp.json();
|
||||||
return typeof json?.likes === "number" ? json.likes : null;
|
return typeof json?.likes === "number" ? json.likes : null;
|
||||||
|
|
@ -379,11 +420,14 @@ export const communityService = {
|
||||||
|
|
||||||
async unlikePost(postId: string, userId: string): Promise<number | null> {
|
async unlikePost(postId: string, userId: string): Promise<number | null> {
|
||||||
try {
|
try {
|
||||||
const resp = await fetch(`/api/community/posts/${encodeURIComponent(postId)}/unlike`, {
|
const resp = await fetch(
|
||||||
method: "POST",
|
`/api/community/posts/${encodeURIComponent(postId)}/unlike`,
|
||||||
headers: { "Content-Type": "application/json" },
|
{
|
||||||
body: JSON.stringify({ user_id: userId }),
|
method: "POST",
|
||||||
});
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ user_id: userId }),
|
||||||
|
},
|
||||||
|
);
|
||||||
if (resp.ok) {
|
if (resp.ok) {
|
||||||
const json = await resp.json();
|
const json = await resp.json();
|
||||||
return typeof json?.likes === "number" ? json.likes : null;
|
return typeof json?.likes === "number" ? json.likes : null;
|
||||||
|
|
@ -394,7 +438,9 @@ export const communityService = {
|
||||||
|
|
||||||
async listComments(postId: string): Promise<any[]> {
|
async listComments(postId: string): Promise<any[]> {
|
||||||
try {
|
try {
|
||||||
const resp = await fetch(`/api/community/posts/${encodeURIComponent(postId)}/comments`);
|
const resp = await fetch(
|
||||||
|
`/api/community/posts/${encodeURIComponent(postId)}/comments`,
|
||||||
|
);
|
||||||
if (!resp.ok) return [];
|
if (!resp.ok) return [];
|
||||||
return await resp.json();
|
return await resp.json();
|
||||||
} catch {
|
} catch {
|
||||||
|
|
@ -402,12 +448,19 @@ export const communityService = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async addComment(postId: string, userId: string, content: string): Promise<any | null> {
|
async addComment(
|
||||||
const resp = await fetch(`/api/community/posts/${encodeURIComponent(postId)}/comments`, {
|
postId: string,
|
||||||
method: "POST",
|
userId: string,
|
||||||
headers: { "Content-Type": "application/json" },
|
content: string,
|
||||||
body: JSON.stringify({ user_id: userId, content }),
|
): Promise<any | null> {
|
||||||
});
|
const resp = await fetch(
|
||||||
|
`/api/community/posts/${encodeURIComponent(postId)}/comments`,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ user_id: userId, content }),
|
||||||
|
},
|
||||||
|
);
|
||||||
if (!resp.ok) return null;
|
if (!resp.ok) return null;
|
||||||
return await resp.json();
|
return await resp.json();
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -133,7 +133,13 @@ export default function Feed() {
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
}, [aethexSocialService, communityService, mapPostsToFeedItems, toast, user?.id]);
|
}, [
|
||||||
|
aethexSocialService,
|
||||||
|
communityService,
|
||||||
|
mapPostsToFeedItems,
|
||||||
|
toast,
|
||||||
|
user?.id,
|
||||||
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchFeed();
|
fetchFeed();
|
||||||
|
|
@ -246,10 +252,16 @@ export default function Feed() {
|
||||||
const content = prompt("Add a comment:")?.trim();
|
const content = prompt("Add a comment:")?.trim();
|
||||||
if (!content) return;
|
if (!content) return;
|
||||||
try {
|
try {
|
||||||
const created = await communityService.addComment(postId, user.id, content);
|
const created = await communityService.addComment(
|
||||||
|
postId,
|
||||||
|
user.id,
|
||||||
|
content,
|
||||||
|
);
|
||||||
if (created) {
|
if (created) {
|
||||||
setItems((prev) =>
|
setItems((prev) =>
|
||||||
prev.map((it) => (it.id === postId ? { ...it, comments: it.comments + 1 } : it)),
|
prev.map((it) =>
|
||||||
|
it.id === postId ? { ...it, comments: it.comments + 1 } : it,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,10 @@ const ProfilePassport = () => {
|
||||||
(AethexUserProfile & { email?: string | null }) | null
|
(AethexUserProfile & { email?: string | null }) | null
|
||||||
>(null);
|
>(null);
|
||||||
const [achievements, setAchievements] = useState<AethexAchievement[]>([]);
|
const [achievements, setAchievements] = useState<AethexAchievement[]>([]);
|
||||||
const [followStats, setFollowStats] = useState<{ followers: number; following: number }>({ followers: 0, following: 0 });
|
const [followStats, setFollowStats] = useState<{
|
||||||
|
followers: number;
|
||||||
|
following: number;
|
||||||
|
}>({ followers: 0, following: 0 });
|
||||||
const [degree, setDegree] = useState<string>("");
|
const [degree, setDegree] = useState<string>("");
|
||||||
const [projects, setProjects] = useState<ProjectPreview[]>([]);
|
const [projects, setProjects] = useState<ProjectPreview[]>([]);
|
||||||
const [interests, setInterests] = useState<string[]>([]);
|
const [interests, setInterests] = useState<string[]>([]);
|
||||||
|
|
@ -266,7 +269,11 @@ const ProfilePassport = () => {
|
||||||
aethexSocialService.getFollowing(resolvedId),
|
aethexSocialService.getFollowing(resolvedId),
|
||||||
aethexSocialService.getFollowers(resolvedId),
|
aethexSocialService.getFollowers(resolvedId),
|
||||||
]);
|
]);
|
||||||
if (!cancelled) setFollowStats({ following: followingIds.length, followers: followerIds.length });
|
if (!cancelled)
|
||||||
|
setFollowStats({
|
||||||
|
following: followingIds.length,
|
||||||
|
followers: followerIds.length,
|
||||||
|
});
|
||||||
} catch {}
|
} catch {}
|
||||||
try {
|
try {
|
||||||
const me = user?.id || null;
|
const me = user?.id || null;
|
||||||
|
|
@ -276,9 +283,13 @@ const ProfilePassport = () => {
|
||||||
if (first.has(resolvedId)) setDegree("1st");
|
if (first.has(resolvedId)) setDegree("1st");
|
||||||
else {
|
else {
|
||||||
const secondLists = await Promise.all(
|
const secondLists = await Promise.all(
|
||||||
Array.from(first).slice(0, 50).map((id) => aethexSocialService.getConnections(id)),
|
Array.from(first)
|
||||||
|
.slice(0, 50)
|
||||||
|
.map((id) => aethexSocialService.getConnections(id)),
|
||||||
|
);
|
||||||
|
const second = new Set(
|
||||||
|
secondLists.flat().map((c: any) => c.connection_id),
|
||||||
);
|
);
|
||||||
const second = new Set(secondLists.flat().map((c: any) => c.connection_id));
|
|
||||||
setDegree(second.has(resolvedId) ? "2nd" : "3rd+");
|
setDegree(second.has(resolvedId) ? "2nd" : "3rd+");
|
||||||
}
|
}
|
||||||
} else if (me && resolvedId && me === resolvedId) {
|
} else if (me && resolvedId && me === resolvedId) {
|
||||||
|
|
@ -547,14 +558,22 @@ const ProfilePassport = () => {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-wrap gap-2 text-sm text-slate-300">
|
<div className="flex flex-wrap gap-2 text-sm text-slate-300">
|
||||||
<Badge variant="outline" className="border-slate-700/70 bg-slate-900/40">
|
<Badge
|
||||||
|
variant="outline"
|
||||||
|
className="border-slate-700/70 bg-slate-900/40"
|
||||||
|
>
|
||||||
Followers: {followStats.followers}
|
Followers: {followStats.followers}
|
||||||
</Badge>
|
</Badge>
|
||||||
<Badge variant="outline" className="border-slate-700/70 bg-slate-900/40">
|
<Badge
|
||||||
|
variant="outline"
|
||||||
|
className="border-slate-700/70 bg-slate-900/40"
|
||||||
|
>
|
||||||
Following: {followStats.following}
|
Following: {followStats.following}
|
||||||
</Badge>
|
</Badge>
|
||||||
{degree && (
|
{degree && (
|
||||||
<Badge className="bg-aethex-500/20 text-aethex-100">{degree} degree</Badge>
|
<Badge className="bg-aethex-500/20 text-aethex-100">
|
||||||
|
{degree} degree
|
||||||
|
</Badge>
|
||||||
)}
|
)}
|
||||||
{profile.github_url && (
|
{profile.github_url && (
|
||||||
<Button
|
<Button
|
||||||
|
|
|
||||||
|
|
@ -911,14 +911,19 @@ export function createServer() {
|
||||||
.from("community_post_likes")
|
.from("community_post_likes")
|
||||||
.select("post_id", { count: "exact", head: true })
|
.select("post_id", { count: "exact", head: true })
|
||||||
.eq("post_id", postId);
|
.eq("post_id", postId);
|
||||||
const count = (c as any)?.length ? (c as any).length : (c as any)?.count || null;
|
const count = (c as any)?.length
|
||||||
|
? (c as any).length
|
||||||
|
: (c as any)?.count || null;
|
||||||
if (typeof count === "number") {
|
if (typeof count === "number") {
|
||||||
await adminSupabase
|
await adminSupabase
|
||||||
.from("community_posts")
|
.from("community_posts")
|
||||||
.update({ likes_count: count })
|
.update({ likes_count: count })
|
||||||
.eq("id", postId);
|
.eq("id", postId);
|
||||||
}
|
}
|
||||||
return res.json({ ok: true, likes: typeof count === "number" ? count : undefined });
|
return res.json({
|
||||||
|
ok: true,
|
||||||
|
likes: typeof count === "number" ? count : undefined,
|
||||||
|
});
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
return res.status(500).json({ error: e?.message || String(e) });
|
return res.status(500).json({ error: e?.message || String(e) });
|
||||||
}
|
}
|
||||||
|
|
@ -939,14 +944,19 @@ export function createServer() {
|
||||||
.from("community_post_likes")
|
.from("community_post_likes")
|
||||||
.select("post_id", { count: "exact", head: true })
|
.select("post_id", { count: "exact", head: true })
|
||||||
.eq("post_id", postId);
|
.eq("post_id", postId);
|
||||||
const count = (c as any)?.length ? (c as any).length : (c as any)?.count || null;
|
const count = (c as any)?.length
|
||||||
|
? (c as any).length
|
||||||
|
: (c as any)?.count || null;
|
||||||
if (typeof count === "number") {
|
if (typeof count === "number") {
|
||||||
await adminSupabase
|
await adminSupabase
|
||||||
.from("community_posts")
|
.from("community_posts")
|
||||||
.update({ likes_count: count })
|
.update({ likes_count: count })
|
||||||
.eq("id", postId);
|
.eq("id", postId);
|
||||||
}
|
}
|
||||||
return res.json({ ok: true, likes: typeof count === "number" ? count : undefined });
|
return res.json({
|
||||||
|
ok: true,
|
||||||
|
likes: typeof count === "number" ? count : undefined,
|
||||||
|
});
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
return res.status(500).json({ error: e?.message || String(e) });
|
return res.status(500).json({ error: e?.message || String(e) });
|
||||||
}
|
}
|
||||||
|
|
@ -958,7 +968,9 @@ export function createServer() {
|
||||||
try {
|
try {
|
||||||
const { data, error } = await adminSupabase
|
const { data, error } = await adminSupabase
|
||||||
.from("community_comments")
|
.from("community_comments")
|
||||||
.select("*, user_profiles:user_id ( id, full_name, username, avatar_url )")
|
.select(
|
||||||
|
"*, user_profiles:user_id ( id, full_name, username, avatar_url )",
|
||||||
|
)
|
||||||
.eq("post_id", postId)
|
.eq("post_id", postId)
|
||||||
.order("created_at", { ascending: true });
|
.order("created_at", { ascending: true });
|
||||||
if (error) return res.status(500).json({ error: error.message });
|
if (error) return res.status(500).json({ error: error.message });
|
||||||
|
|
@ -1077,14 +1089,12 @@ export function createServer() {
|
||||||
title: string,
|
title: string,
|
||||||
message?: string,
|
message?: string,
|
||||||
) => {
|
) => {
|
||||||
await adminSupabase
|
await adminSupabase.from("notifications").insert({
|
||||||
.from("notifications")
|
user_id: userId,
|
||||||
.insert({
|
type: "info",
|
||||||
user_id: userId,
|
title,
|
||||||
type: "info",
|
message: message || null,
|
||||||
title,
|
});
|
||||||
message: message || null,
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Notify explicit targets
|
// Notify explicit targets
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue