From 5e7b273b339cd11d7b4b55dd50a1bf6c823b41d5 Mon Sep 17 00:00:00 2001 From: Thomas Hallock Date: Sun, 5 Oct 2025 06:43:34 -0500 Subject: [PATCH] refactor: split deployment info into server/client components MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refactor to use server component composition pattern where DeploymentInfoContent (server component) imports build info JSON directly and is rendered as a child of DeploymentInfoModal (client component). Eliminates unnecessary API fetch. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- apps/web/src/components/DeploymentInfo.tsx | 341 +----------------- .../src/components/DeploymentInfoContent.tsx | 197 ++++++++++ .../src/components/DeploymentInfoModal.tsx | 115 ++++++ 3 files changed, 317 insertions(+), 336 deletions(-) create mode 100644 apps/web/src/components/DeploymentInfoContent.tsx create mode 100644 apps/web/src/components/DeploymentInfoModal.tsx diff --git a/apps/web/src/components/DeploymentInfo.tsx b/apps/web/src/components/DeploymentInfo.tsx index 55465c9c..c8fa5f34 100644 --- a/apps/web/src/components/DeploymentInfo.tsx +++ b/apps/web/src/components/DeploymentInfo.tsx @@ -1,341 +1,10 @@ -'use client' - -import React, { useState, useEffect } from 'react' -import * as Dialog from '@radix-ui/react-dialog' -import { Info, X, GitBranch, GitCommit, Clock, Package, Server } from 'lucide-react' -import { css } from '../../styled-system/css' -import { vstack, hstack } from '../../styled-system/patterns' - -interface BuildInfo { - version: string - buildTime: string - buildTimestamp: number - git: { - commit: string | null - commitShort: string | null - branch: string | null - tag: string | null - isDirty: boolean - } - environment: string - buildNumber: string | null - nodeVersion: string -} +import { DeploymentInfoModal } from './DeploymentInfoModal' +import { DeploymentInfoContent } from './DeploymentInfoContent' export function DeploymentInfo() { - const [open, setOpen] = useState(false) - const [buildInfo, setBuildInfo] = useState(null) - const [loading, setLoading] = useState(true) - - useEffect(() => { - // Keyboard shortcut: Cmd/Ctrl + Shift + I - const handleKeyDown = (e: KeyboardEvent) => { - if ((e.metaKey || e.ctrlKey) && e.shiftKey && e.key === 'I') { - e.preventDefault() - setOpen(prev => !prev) - } - } - - window.addEventListener('keydown', handleKeyDown) - return () => window.removeEventListener('keydown', handleKeyDown) - }, []) - - useEffect(() => { - if (open && !buildInfo) { - fetch('/api/build-info') - .then(res => res.json()) - .then(data => { - setBuildInfo(data) - setLoading(false) - }) - .catch(err => { - console.error('Failed to load build info:', err) - setLoading(false) - }) - } - }, [open, buildInfo]) - - const formatTimestamp = (timestamp: number) => { - const date = new Date(timestamp) - return date.toLocaleString('en-US', { - year: 'numeric', - month: 'short', - day: 'numeric', - hour: '2-digit', - minute: '2-digit', - second: '2-digit', - timeZoneName: 'short' - }) - } - - const getTimeAgo = (timestamp: number) => { - const seconds = Math.floor((Date.now() - timestamp) / 1000) - - const intervals = { - year: 31536000, - month: 2592000, - week: 604800, - day: 86400, - hour: 3600, - minute: 60, - second: 1 - } - - for (const [unit, secondsInUnit] of Object.entries(intervals)) { - const interval = Math.floor(seconds / secondsInUnit) - if (interval >= 1) { - return `${interval} ${unit}${interval === 1 ? '' : 's'} ago` - } - } - return 'just now' - } - return ( - - - - - - - Deployment Information - - - {loading ? ( -
- Loading... -
- ) : buildInfo ? ( -
- } - label="Version" - value={buildInfo.version} - /> - - } - label="Build Time" - value={ -
- {formatTimestamp(buildInfo.buildTimestamp)} - - {getTimeAgo(buildInfo.buildTimestamp)} - -
- } - /> - - {buildInfo.git.branch && ( - } - label="Branch" - value={ - - {buildInfo.git.branch} - {buildInfo.git.isDirty && ( - - (dirty) - - )} - - } - /> - )} - - {buildInfo.git.commitShort && ( - } - label="Commit" - value={ -
- - {buildInfo.git.commitShort} - - {buildInfo.git.commit && ( - - {buildInfo.git.commit} - - )} -
- } - /> - )} - - {buildInfo.git.tag && ( - } - label="Tag" - value={ - - {buildInfo.git.tag} - - } - /> - )} - - } - label="Environment" - value={ - - {buildInfo.environment} - - } - /> - - {buildInfo.buildNumber && ( - - )} - - - {buildInfo.nodeVersion} - - } - /> -
- ) : ( -
- Failed to load build information -
- )} - -
- Press ⌘⇧I or Ctrl⇧I to toggle -
- - - - -
-
-
- ) -} - -function InfoRow({ - icon, - label, - value -}: { - icon?: React.ReactNode - label: string - value: React.ReactNode -}) { - return ( -
-
- {icon} - {label} -
-
- {value} -
-
+ + + ) } diff --git a/apps/web/src/components/DeploymentInfoContent.tsx b/apps/web/src/components/DeploymentInfoContent.tsx new file mode 100644 index 00000000..35fb5031 --- /dev/null +++ b/apps/web/src/components/DeploymentInfoContent.tsx @@ -0,0 +1,197 @@ +import React from 'react' +import { GitBranch, GitCommit, Clock, Package, Server } from 'lucide-react' +import { css } from '../../styled-system/css' +import { vstack, hstack } from '../../styled-system/patterns' +import buildInfo from '@/generated/build-info.json' + +function formatTimestamp(timestamp: number) { + const date = new Date(timestamp) + return date.toLocaleString('en-US', { + year: 'numeric', + month: 'short', + day: 'numeric', + hour: '2-digit', + minute: '2-digit', + second: '2-digit', + timeZoneName: 'short' + }) +} + +function getTimeAgo(timestamp: number) { + const seconds = Math.floor((Date.now() - timestamp) / 1000) + + const intervals = { + year: 31536000, + month: 2592000, + week: 604800, + day: 86400, + hour: 3600, + minute: 60, + second: 1 + } + + for (const [unit, secondsInUnit] of Object.entries(intervals)) { + const interval = Math.floor(seconds / secondsInUnit) + if (interval >= 1) { + return `${interval} ${unit}${interval === 1 ? '' : 's'} ago` + } + } + return 'just now' +} + +export function DeploymentInfoContent() { + return ( +
+ } + label="Version" + value={buildInfo.version} + /> + + } + label="Build Time" + value={ +
+ {formatTimestamp(buildInfo.buildTimestamp)} + + {getTimeAgo(buildInfo.buildTimestamp)} + +
+ } + /> + + {buildInfo.git.branch && ( + } + label="Branch" + value={ + + {buildInfo.git.branch} + {buildInfo.git.isDirty && ( + + (dirty) + + )} + + } + /> + )} + + {buildInfo.git.commitShort && ( + } + label="Commit" + value={ +
+ + {buildInfo.git.commitShort} + + {buildInfo.git.commit && ( + + {buildInfo.git.commit} + + )} +
+ } + /> + )} + + {buildInfo.git.tag && ( + } + label="Tag" + value={ + + {buildInfo.git.tag} + + } + /> + )} + + } + label="Environment" + value={ + + {buildInfo.environment} + + } + /> + + {buildInfo.buildNumber && ( + + )} + + + {buildInfo.nodeVersion} + + } + /> +
+ ) +} + +function InfoRow({ + icon, + label, + value +}: { + icon?: React.ReactNode + label: string + value: React.ReactNode +}) { + return ( +
+
+ {icon} + {label} +
+
+ {value} +
+
+ ) +} diff --git a/apps/web/src/components/DeploymentInfoModal.tsx b/apps/web/src/components/DeploymentInfoModal.tsx new file mode 100644 index 00000000..a81645eb --- /dev/null +++ b/apps/web/src/components/DeploymentInfoModal.tsx @@ -0,0 +1,115 @@ +'use client' + +import React, { useState, useEffect } from 'react' +import * as Dialog from '@radix-ui/react-dialog' +import { Info, X } from 'lucide-react' +import { css } from '../../styled-system/css' + +interface DeploymentInfoModalProps { + children: React.ReactNode +} + +export function DeploymentInfoModal({ children }: DeploymentInfoModalProps) { + const [open, setOpen] = useState(false) + + useEffect(() => { + // Keyboard shortcut: Cmd/Ctrl + Shift + I + const handleKeyDown = (e: KeyboardEvent) => { + if ((e.metaKey || e.ctrlKey) && e.shiftKey && e.key === 'I') { + e.preventDefault() + setOpen(prev => !prev) + } + } + + window.addEventListener('keydown', handleKeyDown) + return () => window.removeEventListener('keydown', handleKeyDown) + }, []) + + return ( + + + + + + + Deployment Information + + + {children} + +
+ Press ⌘⇧I or Ctrl⇧I to toggle +
+ + + + +
+
+
+ ) +}