feat: Introduce comprehensive user settings, voice, chat, and screen sharing features with new components, contexts, icons, and Convex backend integrations.
All checks were successful
Build and Release / build-and-release (push) Successful in 13m55s

This commit is contained in:
Bryan1029384756
2026-02-18 14:48:57 -06:00
parent a9490f7bd4
commit bdc16b9d3f
22 changed files with 755 additions and 126 deletions

View File

@@ -1,5 +1,6 @@
import { query, mutation } from "./_generated/server";
import { query, mutation, internalMutation } from "./_generated/server";
import { v } from "convex/values";
import { internal } from "./_generated/api";
import { getPublicStorageUrl } from "./storageUrl";
import { getRolesForUser } from "./roles";
@@ -33,8 +34,12 @@ export const join = mutation({
isDeafened: args.isDeafened,
isScreenSharing: false,
isServerMuted: false,
lastHeartbeat: Date.now(),
});
// Schedule stale cleanup to run in 90 seconds
await ctx.scheduler.runAfter(90000, internal.voiceState.cleanStaleStates, {});
return null;
},
});
@@ -217,6 +222,7 @@ export const afkMove = mutation({
isDeafened: currentState.isDeafened,
isScreenSharing: false,
isServerMuted: currentState.isServerMuted,
lastHeartbeat: Date.now(),
});
// Clear viewers watching the moved user's stream (screen sharing stops on AFK move)
@@ -260,6 +266,56 @@ export const disconnectUser = mutation({
},
});
export const heartbeat = mutation({
args: {
userId: v.id("userProfiles"),
},
returns: v.null(),
handler: async (ctx, args) => {
const existing = await ctx.db
.query("voiceStates")
.withIndex("by_user", (q: any) => q.eq("userId", args.userId))
.first();
if (existing) {
await ctx.db.patch(existing._id, { lastHeartbeat: Date.now() });
}
return null;
},
});
export const cleanStaleStates = internalMutation({
args: {},
returns: v.null(),
handler: async (ctx) => {
const states = await ctx.db.query("voiceStates").collect();
const staleThreshold = Date.now() - 90_000; // 90 seconds
let hasActiveStates = false;
for (const s of states) {
if (s.lastHeartbeat && s.lastHeartbeat < staleThreshold) {
// Clear viewers watching this user's stream
for (const other of states) {
if (other.watchingStream === s.userId && other._id !== s._id) {
await ctx.db.patch(other._id, { watchingStream: undefined });
}
}
await ctx.db.delete(s._id);
} else {
hasActiveStates = true;
}
}
// Re-schedule if there are still active voice states
if (hasActiveStates) {
await ctx.scheduler.runAfter(90000, internal.voiceState.cleanStaleStates, {});
}
return null;
},
});
export const moveUser = mutation({
args: {
actorUserId: v.id("userProfiles"),
@@ -303,6 +359,7 @@ export const moveUser = mutation({
isDeafened: currentState.isDeafened,
isScreenSharing: currentState.isScreenSharing,
isServerMuted: currentState.isServerMuted,
lastHeartbeat: Date.now(),
});
return null;