From 6f6a0e0d063feddc49a7c805e1c3d1b9f02256a1 Mon Sep 17 00:00:00 2001 From: Parker Rex Date: Tue, 30 Sep 2025 17:31:55 -0400 Subject: [PATCH] feat: add developer console branding with mission statement and authentication logging (#72) Implemented a comprehensive developer console branding feature that enhances the developer experience by displaying: - Custom ASCII art branding (VAI logo) - Community mission statement with inspirational message - Link to careers/jobs page (vibewith.ai/blog) - Authentication event logging with timestamps - Session initialization and state change tracking - Environment-aware logging (detailed in dev, minimal in prod) - Privacy-focused implementation (no sensitive data in production) The console branding component is integrated into the root layout and automatically displays when the application loads. Authentication events are logged throughout the user session to help with debugging. Files added: - components/ascii-art.ts: ASCII art and mission statement definitions - hooks/use-console-branding.ts: Custom hook for console logging utilities - components/console-branding.tsx: Main console branding component Files modified: - app/layout.tsx: Integrated ConsoleBranding component Closes #72 --- app/layout.tsx | 2 + components/ascii-art.ts | 18 +++++++++ components/console-branding.tsx | 41 +++++++++++++++++++ hooks/use-console-branding.ts | 70 +++++++++++++++++++++++++++++++++ 4 files changed, 131 insertions(+) create mode 100644 components/ascii-art.ts create mode 100644 components/console-branding.tsx create mode 100644 hooks/use-console-branding.ts diff --git a/app/layout.tsx b/app/layout.tsx index 3322ad2c..d455edab 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -2,6 +2,7 @@ import type { Metadata } from "next"; import "./globals.css"; import { ClerkProvider } from "@clerk/nextjs"; import localFont from "next/font/local"; +import { ConsoleBranding } from "@/components/console-branding"; import ConvexClientProvider from "@/components/convex-client-provider"; import { NetworkStatusIndicator } from "@/components/error-display"; import { Footer } from "@/components/header/footer"; @@ -40,6 +41,7 @@ export default function RootLayout({ > +
{children}
diff --git a/components/ascii-art.ts b/components/ascii-art.ts new file mode 100644 index 00000000..b634cc93 --- /dev/null +++ b/components/ascii-art.ts @@ -0,0 +1,18 @@ +export const VAI_ASCII_ART = ` +██╗ ██╗ █████╗ ██╗ ██╗ ██╗███████╗██╗ ██╗ +██║ ██║██╔══██╗██║ ██║ ██║██╔════╝╚██╗██╔╝ +██║ ██║███████║██║ ██║ ██║█████╗ ╚███╔╝ +╚██╗ ██╔╝██╔══██║██║ ╚██╗ ██╔╝██╔══╝ ██╔██╗ + ╚████╔╝ ██║ ██║███████╗ ╚████╔╝ ███████╗██╔╝ ██╗ + ╚═══╝ ╚═╝ ╚═╝╚══════╝ ╚═══╝ ╚══════╝╚═╝ ╚═╝ +`; + +export const MISSION_STATEMENT = ` +🚀 Empowering AI Engineers to Build the Future + +Join us in creating the world's most innovative AI engineering community. +Share knowledge, collaborate on projects, and advance the field of AI together. + +💼 Explore Career Opportunities: https://vibewith.ai/blog +🌟 Be part of something extraordinary. +`; diff --git a/components/console-branding.tsx b/components/console-branding.tsx new file mode 100644 index 00000000..8bd60a6f --- /dev/null +++ b/components/console-branding.tsx @@ -0,0 +1,41 @@ +"use client"; + +import { useUser } from "@clerk/nextjs"; +import { useEffect } from "react"; +import { useConsoleLogger } from "@/hooks/use-console-branding"; + +export function ConsoleBranding() { + const { isSignedIn, user } = useUser(); + const { logBranding, logAuthEvent } = useConsoleLogger(); + + // Display branding once on mount + useEffect(() => { + logBranding(); + + // Log initial auth state + logAuthEvent("session_initialized", { + isSignedIn: !!isSignedIn, + userId: user?.id, + timestamp: new Date().toISOString(), + }); + }, [logBranding, logAuthEvent, isSignedIn, user?.id]); + + // Log auth state changes + useEffect(() => { + if (isSignedIn) { + logAuthEvent("user_signed_in", { + isSignedIn: true, + userId: user?.id, + timestamp: new Date().toISOString(), + }); + } else { + logAuthEvent("user_signed_out", { + isSignedIn: false, + timestamp: new Date().toISOString(), + }); + } + }, [isSignedIn, user?.id, logAuthEvent]); + + // This component only handles console output + return null; +} diff --git a/hooks/use-console-branding.ts b/hooks/use-console-branding.ts new file mode 100644 index 00000000..d6ca3109 --- /dev/null +++ b/hooks/use-console-branding.ts @@ -0,0 +1,70 @@ +import { useCallback } from "react"; +import { MISSION_STATEMENT, VAI_ASCII_ART } from "@/components/ascii-art"; + +interface AuthEventData { + timestamp: string; + isSignedIn?: boolean; + userId?: string; + event?: string; +} + +interface TokenRefreshData { + expiresAt: string; + refreshedAt: string; +} + +export function useConsoleLogger() { + const isDev = process.env.NODE_ENV === "development"; + + const logBranding = useCallback(() => { + if (typeof window === "undefined") return; + + console.log(`%c${VAI_ASCII_ART}`, "color: #3b82f6; font-weight: bold; font-family: monospace;"); + console.log(`%c${MISSION_STATEMENT}`, "color: #10b981; font-size: 14px; line-height: 1.5;"); + console.log( + "%c===== VAI-VEX Developer Console =====", + "color: #f59e0b; font-weight: bold; font-size: 16px;", + ); + console.log("%cVersion: 0.1.0", "color: #6b7280;"); + console.log(`%cEnvironment: ${process.env.NODE_ENV}`, "color: #6b7280;"); + }, []); + + const logAuthEvent = useCallback( + (event: string, data: AuthEventData) => { + if (typeof window === "undefined") return; + + const logLevel = isDev ? "log" : "debug"; + + // Only include sensitive data in development + const safeData = { + timestamp: data.timestamp, + event, + isSignedIn: !!data.isSignedIn, + ...(isDev && data.userId && { userId: data.userId }), + }; + + console[logLevel]( + `%c🔐 AUTH ${event.toUpperCase()}`, + "color: #8b5cf6; font-weight: bold;", + safeData, + ); + }, + [isDev], + ); + + const logTokenRefresh = useCallback((tokenData: TokenRefreshData) => { + if (typeof window === "undefined") return; + + console.log("%c🔄 TOKEN REFRESH", "color: #06b6d4; font-weight: bold;", { + timestamp: new Date().toISOString(), + expiresAt: tokenData.expiresAt, + refreshedAt: tokenData.refreshedAt, + }); + }, []); + + return { + logBranding, + logAuthEvent, + logTokenRefresh, + }; +}