diff --git a/client/contexts/DiscordActivityContext.tsx b/client/contexts/DiscordActivityContext.tsx index dea0f31a..e732b7ed 100644 --- a/client/contexts/DiscordActivityContext.tsx +++ b/client/contexts/DiscordActivityContext.tsx @@ -284,17 +284,17 @@ export const DiscordActivityProvider: React.FC< // Subscribe to speaking updates if in voice channel if (sdk.channelId) { try { - sdk.subscribe("SPEAKING_START", (data: any) => { + await sdk.subscribe("SPEAKING_START", (data: any) => { console.log("[Discord Activity] Speaking start:", data); if (data?.user_id) { setSpeakingUsers(prev => new Set(prev).add(data.user_id)); - setParticipants(prev => prev.map(p => + setParticipants(prev => prev.map(p => p.id === data.user_id ? { ...p, speaking: true } : p )); } }, { channel_id: sdk.channelId }); - sdk.subscribe("SPEAKING_STOP", (data: any) => { + await sdk.subscribe("SPEAKING_STOP", (data: any) => { console.log("[Discord Activity] Speaking stop:", data); if (data?.user_id) { setSpeakingUsers(prev => { @@ -302,7 +302,7 @@ export const DiscordActivityProvider: React.FC< next.delete(data.user_id); return next; }); - setParticipants(prev => prev.map(p => + setParticipants(prev => prev.map(p => p.id === data.user_id ? { ...p, speaking: false } : p )); } diff --git a/server/index.ts b/server/index.ts index 047f4f19..60285b76 100644 --- a/server/index.ts +++ b/server/index.ts @@ -2817,6 +2817,41 @@ export function createServer() { } }); + // Activity feed alias (used by Discord Activity) + app.get("/api/feed", async (req, res) => { + const limit = Math.max(1, Math.min(50, Number(req.query.limit) || 10)); + try { + const { data, error } = await adminSupabase + .from("community_posts") + .select(`id, title, content, likes_count, author_id, created_at, user_profiles ( username, full_name, avatar_url )`) + .eq("is_published", true) + .order("created_at", { ascending: false }) + .limit(limit); + if (error) return res.status(500).json({ error: error.message }); + res.json({ data: data || [] }); + } catch (e: any) { + res.status(500).json({ error: e?.message || String(e) }); + } + }); + + app.post("/api/feed/:id/like", async (req, res) => { + const postId = req.params.id; + try { + const { data: post } = await adminSupabase + .from("community_posts") + .select("likes_count") + .eq("id", postId) + .single(); + await adminSupabase + .from("community_posts") + .update({ likes_count: (post?.likes_count || 0) + 1 }) + .eq("id", postId); + res.json({ ok: true }); + } catch (e: any) { + res.status(500).json({ error: e?.message || String(e) }); + } + }); + app.get("/api/user/:id/posts", async (req, res) => { const userId = req.params.id; try { @@ -3862,6 +3897,20 @@ export function createServer() { } }); + app.delete("/api/activity/polls/:id", async (req, res) => { + const pollId = req.params.id; + try { + const { error } = await adminSupabase + .from("activity_polls") + .update({ is_active: false }) + .eq("id", pollId); + if (error) return res.status(500).json({ error: error.message }); + res.json({ ok: true }); + } catch (e: any) { + res.status(500).json({ error: e?.message || String(e) }); + } + }); + app.post("/api/activity/polls/:id/vote", async (req, res) => { const pollId = req.params.id; const { user_id, option_index } = req.body || {}; @@ -3942,6 +3991,29 @@ export function createServer() { } }); + app.post("/api/activity/challenges/:id/claim", async (req, res) => { + const challengeId = req.params.id; + const { user_id } = req.body || {}; + if (!user_id) return res.status(400).json({ error: "user_id required" }); + try { + const { data, error } = await adminSupabase + .from("activity_challenge_progress") + .upsert({ + challenge_id: challengeId, + user_id, + progress: 1, + completed: true, + completed_at: new Date().toISOString(), + }, { onConflict: "challenge_id,user_id" }) + .select() + .single(); + if (error) return res.status(500).json({ error: error.message }); + res.json(data); + } catch (e: any) { + res.status(500).json({ error: e?.message || String(e) }); + } + }); + app.get("/api/activity/challenges/:id/progress", async (req, res) => { const challengeId = req.params.id; const user_id = req.query.user_id as string; @@ -4053,6 +4125,25 @@ export function createServer() { } }); + // All badges (for Activity badges tab when no userId) + app.get("/api/activity/badges", async (req, res) => { + try { + const { data, error } = await adminSupabase + .from("badges") + .select("*") + .order("created_at", { ascending: true }); + if (error) { + if (error.code === "42P01" || error.message?.includes("does not exist")) { + return res.json([]); + } + return res.status(500).json({ error: error.message }); + } + res.json(data || []); + } catch (e: any) { + res.status(500).json({ error: e?.message || String(e) }); + } + }); + // User Badges (real data) app.get("/api/activity/badges/:userId", async (req, res) => { const userId = req.params.userId;