From c8e660c7dab1e4a03cb6a1e82bcf12efb9bba5d3 Mon Sep 17 00:00:00 2001 From: Louis Knight-Webb Date: Fri, 3 Oct 2025 18:52:44 +0100 Subject: [PATCH] Add discord shield to navbar (vibe-kanban) (#927) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ✅ Discord shield added to navbar with: - **Desktop badge**: Discord shield visible on sm+ screens, positioned before the dropdown menu - **Mobile fallback**: Discord link added to dropdown menu for small screens - **Consistent styling**: Uses `h-5` height and proper spacing (`ml-2 mr-1`) - **Accessibility**: Includes proper `aria-label` and `alt` text The implementation follows the existing code patterns and maintains readability as requested. * ✅ Fixed both issues: - **Vertical centering**: Changed from `block` to `flex items-center` to properly align with other navbar buttons - **Theme adaptation**: Added `dark:brightness-90 dark:contrast-125` to subtly adjust the shield colors in dark mode for better visibility The Discord shield now properly aligns with other navbar elements and adapts to both light and dark themes. * ✅ Updated Discord shield with theme colors and positioning: - **Theme colors**: Custom badge using `bg-background`, `text-foreground` for left side and `bg-success`, `text-success-foreground` for right side - **Position**: Moved to right of logo (left side of navbar) instead of right side - **Design**: Clean two-part badge with "discord" label and "online" status that adapts to theme automatically The Discord badge now uses your app's theme colors and is positioned next to the logo for better visual hierarchy. * ✅ Discord online count implemented with: - **Live data**: Fetches real member count from Discord Widget API - **Auto-refresh**: Updates every 60 seconds for live feel - **Graceful fallback**: Shows "online" if API unavailable - **Theme integration**: Maintains your custom success/background colors - **Accessibility**: Includes `aria-live="polite"` for screen readers - **Formatted display**: Shows count as "1,234 online" with proper locale formatting The Discord badge now displays the actual number of online members from your server while maintaining the theme colors and positioning next to the logo. * Cleanup script changes for task attempt 230fa5cc-96f7-4fdb-a549-c2ea46b9e88e * ✅ Fixed positioning - Added `flex items-center` to the container so the logo and Discord badge sit side by side horizontally instead of stacking vertically. * ✅ Added Discord logo from simple-icons: - **Added simple-icons dependency** via pnpm - **Imported Discord icon** from `simple-icons` package - **Replaced "discord" text** with actual Discord SVG logo - **Maintained theme colors** - icon uses `currentColor` so it adapts to theme automatically - **Proper sizing** - `h-4 w-4` matches other navbar icons The Discord badge now displays the official Discord logo instead of text, while maintaining the live member count and theme integration. * Cleanup script changes for task attempt 230fa5cc-96f7-4fdb-a549-c2ea46b9e88e * manual style improvement --- frontend/package.json | 3 +- frontend/src/components/layout/navbar.tsx | 69 ++++++++++++++++++++++- 2 files changed, 69 insertions(+), 3 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index e3e846ed..0b34c510 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -51,10 +51,11 @@ "react-hotkeys-hook": "^5.1.0", "react-i18next": "^15.7.3", "react-router-dom": "^6.8.1", + "react-use-websocket": "^4.7.0", "react-virtuoso": "^4.14.0", "react-window": "^1.8.11", "rfc6902": "^5.1.2", - "react-use-websocket": "^4.7.0", + "simple-icons": "^15.16.0", "tailwind-merge": "^2.2.0", "tailwindcss-animate": "^1.0.7", "vibe-kanban-web-companion": "^0.0.4", diff --git a/frontend/src/components/layout/navbar.tsx b/frontend/src/components/layout/navbar.tsx index 03b6842a..67f724e0 100644 --- a/frontend/src/components/layout/navbar.tsx +++ b/frontend/src/components/layout/navbar.tsx @@ -1,5 +1,6 @@ import { Link, useLocation } from 'react-router-dom'; -import { useCallback } from 'react'; +import { useCallback, useEffect, useState } from 'react'; +import { siDiscord } from 'simple-icons'; import { Button } from '@/components/ui/button'; import { DropdownMenu, @@ -13,6 +14,7 @@ import { Settings, BookOpen, MessageCircleQuestion, + MessageCircle, Menu, Plus, } from 'lucide-react'; @@ -24,6 +26,8 @@ import { useProject } from '@/contexts/project-context'; import { showProjectForm } from '@/lib/modals'; import { useOpenProjectInEditor } from '@/hooks/useOpenProjectInEditor'; +const DISCORD_GUILD_ID = '1423630976524877857'; + const INTERNAL_NAV = [ { label: 'Projects', icon: FolderOpen, to: '/projects' }, { label: 'Settings', icon: Settings, to: '/settings' }, @@ -40,6 +44,11 @@ const EXTERNAL_LINKS = [ icon: MessageCircleQuestion, href: 'https://github.com/BloopAI/vibe-kanban/issues', }, + { + label: 'Discord', + icon: MessageCircle, + href: 'https://discord.gg/AC4nwVtJM3', + }, ]; export function Navbar() { @@ -47,6 +56,36 @@ export function Navbar() { const { projectId, project } = useProject(); const { query, setQuery, active, clear, registerInputRef } = useSearch(); const handleOpenInEditor = useOpenProjectInEditor(project || null); + const [onlineCount, setOnlineCount] = useState(null); + + useEffect(() => { + let cancelled = false; + + const fetchCount = async () => { + try { + const res = await fetch( + `https://discord.com/api/guilds/${DISCORD_GUILD_ID}/widget.json`, + { cache: 'no-store' } + ); + if (!res.ok) return; // Widget disabled or temporary error; keep previous value + const data = await res.json(); + if (!cancelled && typeof data?.presence_count === 'number') { + setOnlineCount(data.presence_count); + } + } catch { + // Network error; ignore and keep previous value + } + }; + + // Initial fetch + refresh every 60s + fetchCount(); + const interval = setInterval(fetchCount, 60_000); + + return () => { + cancelled = true; + clearInterval(interval); + }; + }, []); const setSearchBarRef = useCallback( (node: HTMLInputElement | null) => { @@ -78,10 +117,36 @@ export function Navbar() {
-