From 948f8c7aa7b14279397b3f0fcc89c5ea029f4e3c Mon Sep 17 00:00:00 2001
From: Bryan1029384756 <23323626+Bryan1029384756@users.noreply.github.com>
Date: Sat, 21 Feb 2026 15:48:48 -0600
Subject: [PATCH] feat: Add voice and video stage functionality with
multi-platform project setup.
---
apps/android/android/app/build.gradle | 2 +-
apps/android/package.json | 2 +-
apps/electron/package.json | 2 +-
apps/web/package.json | 2 +-
deploy/server/server.js | 31 ++++++++--
packages/shared/package.json | 2 +-
packages/shared/src/components/Sidebar.jsx | 6 +-
packages/shared/src/components/VoiceStage.jsx | 57 ++++++++++++++++++-
packages/shared/src/contexts/VoiceContext.jsx | 54 ++++++++++++++++--
9 files changed, 141 insertions(+), 17 deletions(-)
diff --git a/apps/android/android/app/build.gradle b/apps/android/android/app/build.gradle
index 0c4b20a..4133f4b 100644
--- a/apps/android/android/app/build.gradle
+++ b/apps/android/android/app/build.gradle
@@ -8,7 +8,7 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 27
- versionName "1.0.32"
+ versionName "1.0.33"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
aaptOptions {
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
diff --git a/apps/android/package.json b/apps/android/package.json
index d901ba5..ce7c905 100644
--- a/apps/android/package.json
+++ b/apps/android/package.json
@@ -1,7 +1,7 @@
{
"name": "@discord-clone/android",
"private": true,
- "version": "1.0.32",
+ "version": "1.0.33",
"type": "module",
"scripts": {
"cap:sync": "npx cap sync",
diff --git a/apps/electron/package.json b/apps/electron/package.json
index 1ce7a75..cd5197c 100644
--- a/apps/electron/package.json
+++ b/apps/electron/package.json
@@ -1,7 +1,7 @@
{
"name": "@discord-clone/electron",
"private": true,
- "version": "1.0.32",
+ "version": "1.0.33",
"description": "Discord Clone - Electron app",
"author": "Moyettes",
"type": "module",
diff --git a/apps/web/package.json b/apps/web/package.json
index df2490d..5a32341 100644
--- a/apps/web/package.json
+++ b/apps/web/package.json
@@ -1,7 +1,7 @@
{
"name": "@discord-clone/web",
"private": true,
- "version": "1.0.32",
+ "version": "1.0.33",
"type": "module",
"scripts": {
"dev": "vite",
diff --git a/deploy/server/server.js b/deploy/server/server.js
index 8aa0a04..594b6cb 100644
--- a/deploy/server/server.js
+++ b/deploy/server/server.js
@@ -16,6 +16,7 @@ const RELEASES_DIR = '/app/releases';
const LOADING_HTML = readFileSync(join(__dirname, 'loading.html'), 'utf-8');
let currentDistPath = null;
+let currentCommitHash = null;
let building = false;
let lastBuildStatus = { status: 'idle', timestamp: null, error: null };
@@ -39,8 +40,15 @@ function run(cmd, opts = {}) {
execSync(cmd, { stdio: 'inherit', timeout: 600_000, ...opts });
}
-async function triggerBuild() {
+async function triggerBuild(webhookCommit) {
if (building) return;
+
+ // Fast path: skip clone entirely if webhook provides the commit and it matches
+ if (webhookCommit && webhookCommit === currentCommitHash) {
+ log(`Commit ${webhookCommit} is already deployed, skipping build (webhook fast path).`);
+ return;
+ }
+
building = true;
lastBuildStatus = { status: 'building', timestamp: Date.now(), error: null };
@@ -54,6 +62,17 @@ async function triggerBuild() {
// Clone
run(`git clone --depth 1 --branch ${GIT_BRANCH} ${GIT_REPO_URL} ${buildDir}`);
+ // Read the cloned commit hash
+ const clonedHash = execSync('git rev-parse HEAD', { cwd: buildDir, encoding: 'utf-8' }).trim();
+ log(`Cloned commit: ${clonedHash}`);
+
+ // Skip if this commit is already deployed
+ if (clonedHash === currentCommitHash) {
+ log(`Commit ${clonedHash} is already deployed, skipping build.`);
+ lastBuildStatus = { status: 'skipped', timestamp: Date.now(), error: null };
+ return;
+ }
+
// Install deps (web workspaces only)
run(
'npm ci --workspace=apps/web --workspace=packages/shared --workspace=packages/platform-web --include-workspace-root',
@@ -73,7 +92,8 @@ async function triggerBuild() {
// Swap — instant, zero downtime
const oldDist = currentDistPath;
currentDistPath = releaseDir;
- log(`Swapped to new release: ${releaseDir}`);
+ currentCommitHash = clonedHash;
+ log(`Swapped to new release: ${releaseDir} (commit ${clonedHash})`);
// Clean up old release
if (oldDist && existsSync(oldDist) && oldDist !== releaseDir) {
@@ -105,13 +125,16 @@ app.use(compression());
app.post('/api/webhook', express.json(), (req, res) => {
if (building) return res.status(409).json({ error: 'Build already in progress' });
- triggerBuild();
+ // Extract commit SHA from Gitea/GitHub push webhook payload
+ const webhookCommit = req.body?.after || null;
+
+ triggerBuild(webhookCommit);
res.json({ message: 'Build triggered' });
});
// Status endpoint
app.get('/api/status', (_req, res) => {
- res.json({ building, ...lastBuildStatus });
+ res.json({ building, commitHash: currentCommitHash, ...lastBuildStatus });
});
// Static file serving + SPA fallback
diff --git a/packages/shared/package.json b/packages/shared/package.json
index 716c5b3..267da02 100644
--- a/packages/shared/package.json
+++ b/packages/shared/package.json
@@ -1,7 +1,7 @@
{
"name": "@discord-clone/shared",
"private": true,
- "version": "1.0.32",
+ "version": "1.0.33",
"type": "module",
"main": "src/App.jsx",
"dependencies": {
diff --git a/packages/shared/src/components/Sidebar.jsx b/packages/shared/src/components/Sidebar.jsx
index 6450c97..b5d1167 100644
--- a/packages/shared/src/components/Sidebar.jsx
+++ b/packages/shared/src/components/Sidebar.jsx
@@ -335,7 +335,7 @@ const liveBadgeStyle = {
marginRight: '4px'
};
-const ACTIVE_SPEAKER_SHADOW = '0 0 0 0px hsl(134.526, 41.485%, 44.902%), inset 0 0 0 2px hsl(134.526, 41.485%, 44.902%), inset 0 0 0 3px hsl(240, 7.143%, 10.98%)';
+const ACTIVE_SPEAKER_SHADOW = 'rgb(67, 162, 90) 0px 0px 0px 2px, rgb(67, 162, 90) 0px 0px 0px 20px inset, rgb(26, 26, 30) 0px 0px 0px 20px inset';
const VOICE_ACTIVE_COLOR = 'hsl(132.809, 34.902%, 50%)';
async function encryptKeyForUsers(convex, channelId, keyHex, crypto) {
@@ -1135,7 +1135,8 @@ const Sidebar = ({ channels, categories, activeChannel, onSelectChannel, usernam
size={24}
style={{
marginRight: 8,
- boxShadow: activeSpeakers.has(user.userId) ? ACTIVE_SPEAKER_SHADOW : 'none'
+ boxShadow: activeSpeakers.has(user.userId) ? ACTIVE_SPEAKER_SHADOW : 'none',
+ transition: 'box-shadow 0.15s ease',
}}
/>
{user.displayName || user.username}
@@ -1182,6 +1183,7 @@ const Sidebar = ({ channels, categories, activeChannel, onSelectChannel, usernam
style={{
boxShadow: activeSpeakers.has(user.userId) ? ACTIVE_SPEAKER_SHADOW : 'none',
borderRadius: '50%',
+ transition: 'box-shadow 0.15s ease',
}}
/>
diff --git a/packages/shared/src/components/VoiceStage.jsx b/packages/shared/src/components/VoiceStage.jsx
index 0c5370d..46601e0 100644
--- a/packages/shared/src/components/VoiceStage.jsx
+++ b/packages/shared/src/components/VoiceStage.jsx
@@ -32,6 +32,8 @@ const getUserColor = (username) => {
};
// Style constants
+const ACTIVE_SPEAKER_SHADOW = 'rgb(67, 162, 90) 0px 0px 0px 2px, rgb(67, 162, 90) 0px 0px 0px 20px inset, rgb(26, 26, 30) 0px 0px 0px 20px inset';
+
const LIVE_BADGE_STYLE = {
backgroundColor: '#ed4245', borderRadius: '4px', padding: '2px 6px',
color: 'white', fontSize: '11px', fontWeight: 'bold',
@@ -87,7 +89,7 @@ const ConnectionQualityIcon = ({ quality }) => {
const ParticipantTile = ({ participant, username, avatarUrl }) => {
const cameraTrack = useParticipantTrack(participant, 'camera');
- const { isPersonallyMuted, voiceStates, connectionQualities } = useVoice();
+ const { isPersonallyMuted, voiceStates, connectionQualities, activeSpeakers } = useVoice();
const isMicEnabled = participant.isMicrophoneEnabled;
const isPersonalMuted = isPersonallyMuted(participant.identity);
const displayName = username || participant.identity;
@@ -109,6 +111,8 @@ const ParticipantTile = ({ participant, username, avatarUrl }) => {
width: '100%',
height: '100%',
aspectRatio: '16/9',
+ boxShadow: activeSpeakers.has(participant.identity) ? ACTIVE_SPEAKER_SHADOW : 'none',
+ transition: 'box-shadow 0.15s ease',
}}>
{cameraTrack ? (