From 8832a36f68dd79e7410f16c79013c176ceca166d Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Thu, 22 Jan 2026 22:10:14 -0600 Subject: [PATCH 1/6] shared header --- app/repos/new/page.tsx | 42 +--- app/tasks/[taskId]/loading.tsx | 33 +--- components/home-page-content.tsx | 233 +++++++++++++++++++++- components/home-page-header.tsx | 324 ------------------------------- components/page-header.tsx | 41 ---- components/repo-layout.tsx | 54 +----- components/repo-page-client.tsx | 55 +----- components/shared-header.tsx | 70 +++++++ components/task-page-client.tsx | 108 ++++------- components/task-page-header.tsx | 51 ----- components/tasks-list-client.tsx | 35 +--- 11 files changed, 358 insertions(+), 688 deletions(-) delete mode 100644 components/home-page-header.tsx delete mode 100644 components/page-header.tsx create mode 100644 components/shared-header.tsx delete mode 100644 components/task-page-header.tsx diff --git a/app/repos/new/page.tsx b/app/repos/new/page.tsx index 2e4800b6..dc6e4928 100644 --- a/app/repos/new/page.tsx +++ b/app/repos/new/page.tsx @@ -6,18 +6,13 @@ import { Label } from '@/components/ui/label' import { Textarea } from '@/components/ui/textarea' import { Switch } from '@/components/ui/switch' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' -import { RefreshCw } from 'lucide-react' +import { RefreshCw, File } from 'lucide-react' import { useState, useEffect } from 'react' import { toast } from 'sonner' import { useRouter, useSearchParams } from 'next/navigation' -import { PageHeader } from '@/components/page-header' -import { useTasks } from '@/components/app-layout' -import { User } from '@/components/auth/user' -import { GitHubStarsButton } from '@/components/github-stars-button' -import { VERCEL_DEPLOY_URL } from '@/lib/constants' +import { SharedHeader } from '@/components/shared-header' import { useAtomValue } from 'jotai' import { sessionAtom } from '@/lib/atoms/session' -import { File } from 'lucide-react' // Template configuration const REPO_TEMPLATES = [ @@ -73,7 +68,6 @@ export default function NewRepoPage() { const router = useRouter() const searchParams = useSearchParams() const ownerParam = searchParams.get('owner') || '' - const { toggleSidebar } = useTasks() const session = useAtomValue(sessionAtom) const [isCreatingRepo, setIsCreatingRepo] = useState(false) @@ -177,37 +171,7 @@ export default function NewRepoPage() { return (
- - - {/* Deploy to Vercel Button */} - - - {/* User Authentication */} - -
- } - /> +
diff --git a/app/tasks/[taskId]/loading.tsx b/app/tasks/[taskId]/loading.tsx index dc67fc7b..0d31bb47 100644 --- a/app/tasks/[taskId]/loading.tsx +++ b/app/tasks/[taskId]/loading.tsx @@ -1,42 +1,13 @@ 'use client' -import { PageHeader } from '@/components/page-header' -import { useTasks } from '@/components/app-layout' -import { Button } from '@/components/ui/button' +import { SharedHeader } from '@/components/shared-header' import { Loader2 } from 'lucide-react' -import { VERCEL_DEPLOY_URL } from '@/lib/constants' -import { GitHubStarsButton } from '@/components/github-stars-button' export default function TaskLoading() { - const { toggleSidebar } = useTasks() - - // Placeholder actions for loading state - no user avatar to prevent flash - const loadingActions = ( -
- - {/* Deploy to Vercel Button */} - - {/* Empty spacer to reserve space for user avatar */} -
-
- ) - return (
- +
diff --git a/components/home-page-content.tsx b/components/home-page-content.tsx index 07f87cc9..a0f63eb3 100644 --- a/components/home-page-content.tsx +++ b/components/home-page-content.tsx @@ -2,7 +2,8 @@ import { useState, useEffect } from 'react' import { TaskForm } from '@/components/task-form' -import { HomePageHeader } from '@/components/home-page-header' +import { SharedHeader } from '@/components/shared-header' +import { RepoSelector } from '@/components/repo-selector' import { toast } from 'sonner' import { useRouter, useSearchParams } from 'next/navigation' import { useTasks } from '@/components/app-layout' @@ -10,6 +11,14 @@ import { setSelectedOwner, setSelectedRepo } from '@/lib/utils/cookies' import type { Session } from '@/lib/session/types' import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog' import { Button } from '@/components/ui/button' +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, + DropdownMenuSeparator, +} from '@/components/ui/dropdown-menu' +import { MoreHorizontal, RefreshCw, Unlink, Settings, Plus, ExternalLink } from 'lucide-react' import { redirectToSignIn } from '@/lib/session/redirect-to-sign-in' import { GitHubIcon } from '@/components/icons/github-icon' import { getEnabledAuthProviders } from '@/lib/auth/providers' @@ -17,6 +26,10 @@ import { useSetAtom, useAtom, useAtomValue } from 'jotai' import { taskPromptAtom } from '@/lib/atoms/task' import { HomePageMobileFooter } from '@/components/home-page-mobile-footer' import { multiRepoModeAtom, selectedReposAtom } from '@/lib/atoms/multi-repo' +import { sessionAtom } from '@/lib/atoms/session' +import { githubConnectionAtom, githubConnectionInitializedAtom } from '@/lib/atoms/github-connection' +import { OpenRepoUrlDialog } from '@/components/open-repo-url-dialog' +import { MultiRepoDialog } from '@/components/multi-repo-dialog' interface HomePageContentProps { initialSelectedOwner?: string @@ -47,6 +60,9 @@ export function HomePageContent({ const [showSignInDialog, setShowSignInDialog] = useState(false) const [loadingVercel, setLoadingVercel] = useState(false) const [loadingGitHub, setLoadingGitHub] = useState(false) + const [isRefreshing, setIsRefreshing] = useState(false) + const [showOpenRepoDialog, setShowOpenRepoDialog] = useState(false) + const [showMultiRepoDialog, setShowMultiRepoDialog] = useState(false) const router = useRouter() const searchParams = useSearchParams() const { refreshTasks, addTaskOptimistically } = useTasks() @@ -56,6 +72,13 @@ export function HomePageContent({ const multiRepoMode = useAtomValue(multiRepoModeAtom) const [selectedRepos, setSelectedRepos] = useAtom(selectedReposAtom) + // GitHub connection state + const session = useAtomValue(sessionAtom) + const githubConnection = useAtomValue(githubConnectionAtom) + const githubConnectionInitialized = useAtomValue(githubConnectionInitializedAtom) + const setGitHubConnection = useSetAtom(githubConnectionAtom) + const isGitHubAuthUser = session.authProvider === 'github' + // Check which auth providers are enabled const { github: hasGitHub, vercel: hasVercel } = getEnabledAuthProviders() @@ -123,6 +146,201 @@ export function HomePageContent({ setSelectedRepo(repo) } + const handleRefreshOwners = async () => { + setIsRefreshing(true) + try { + localStorage.removeItem('github-owners') + toast.success('Refreshing owners...') + window.location.reload() + } catch (error) { + console.error('Error refreshing owners:', error) + toast.error('Failed to refresh owners') + } finally { + setIsRefreshing(false) + } + } + + const handleRefreshRepos = async () => { + setIsRefreshing(true) + try { + if (selectedOwner) { + localStorage.removeItem(`github-repos-${selectedOwner}`) + toast.success('Refreshing repositories...') + window.location.reload() + } else { + Object.keys(localStorage).forEach((key) => { + if (key.startsWith('github-repos-')) { + localStorage.removeItem(key) + } + }) + toast.success('Refreshing all repositories...') + window.location.reload() + } + } catch (error) { + console.error('Error refreshing repositories:', error) + toast.error('Failed to refresh repositories') + } finally { + setIsRefreshing(false) + } + } + + const handleDisconnectGitHub = async () => { + try { + const response = await fetch('/api/auth/github/disconnect', { + method: 'POST', + credentials: 'include', + }) + + if (response.ok) { + toast.success('GitHub disconnected') + localStorage.removeItem('github-owners') + Object.keys(localStorage).forEach((key) => { + if (key.startsWith('github-repos-')) { + localStorage.removeItem(key) + } + }) + handleOwnerChange('') + handleRepoChange('') + setGitHubConnection({ connected: false }) + router.refresh() + } else { + const error = await response.json() + console.error('Failed to disconnect GitHub:', error) + toast.error(error.error || 'Failed to disconnect GitHub') + } + } catch (error) { + console.error('Failed to disconnect GitHub:', error) + toast.error('Failed to disconnect GitHub') + } + } + + const handleNewRepo = () => { + const url = selectedOwner ? `/repos/new?owner=${selectedOwner}` : '/repos/new' + router.push(url) + } + + const handleConnectGitHub = () => { + window.location.href = '/api/auth/github/signin' + } + + const handleReconfigureGitHub = () => { + const clientId = process.env.NEXT_PUBLIC_GITHUB_CLIENT_ID + if (clientId) { + window.open(`https://github.com/settings/connections/applications/${clientId}`, '_blank') + } else { + window.location.href = '/api/auth/github/signin' + } + } + + const handleOpenRepoUrl = async (repoUrl: string) => { + try { + if (!user) { + toast.error('Sign in required', { + description: 'Please sign in to create tasks with custom repository URLs.', + }) + return + } + + const taskData = { + prompt: 'Work on this repository', + repoUrl: repoUrl, + selectedAgent: localStorage.getItem('last-selected-agent') || 'claude', + selectedModel: localStorage.getItem('last-selected-model-claude') || 'claude-sonnet-4-5', + installDependencies: true, + maxDuration: 300, + keepAlive: false, + } + + const { id } = addTaskOptimistically(taskData) + router.push(`/tasks/${id}`) + + const response = await fetch('/api/tasks', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ ...taskData, id }), + }) + + if (response.ok) { + toast.success('Task created successfully!') + } else { + const error = await response.json() + toast.error(error.message || error.error || 'Failed to create task') + } + } catch (error) { + console.error('Error creating task:', error) + toast.error('Failed to create task') + } + } + + // Build leftActions for the header + const headerLeftActions = ( +
+ {!githubConnectionInitialized ? null : githubConnection.connected || isGitHubAuthUser ? ( + <> + setShowMultiRepoDialog(true)} + /> + + + + + + + + New Repo + + setShowOpenRepoDialog(true)}> + + Open Repo URL + + + + + Refresh Owners + + + + Refresh Repos + + + + Manage Access + + {!isGitHubAuthUser && ( + + + Disconnect GitHub + + )} + + + + ) : user ? ( + + ) : selectedOwner || selectedRepo ? ( + + ) : null} +
+ ) + const handleTaskSubmit = async (data: { prompt: string repoUrl: string @@ -352,14 +570,7 @@ export function HomePageContent({ return (
- +
@@ -379,6 +590,10 @@ export function HomePageContent({ {/* Mobile Footer with Stars and Deploy Button - Show when logged in OR when owner/repo are selected */} {(user || selectedOwner || selectedRepo) && } + {/* Dialogs */} + + + {/* Sign In Dialog */} diff --git a/components/home-page-header.tsx b/components/home-page-header.tsx deleted file mode 100644 index b6529d80..00000000 --- a/components/home-page-header.tsx +++ /dev/null @@ -1,324 +0,0 @@ -'use client' - -import { PageHeader } from '@/components/page-header' -import { RepoSelector } from '@/components/repo-selector' -import { useTasks } from '@/components/app-layout' -import { Button } from '@/components/ui/button' -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, - DropdownMenuSeparator, -} from '@/components/ui/dropdown-menu' -import { MoreHorizontal, RefreshCw, Unlink, Settings, Plus, ExternalLink } from 'lucide-react' -import { useState } from 'react' -import { VERCEL_DEPLOY_URL } from '@/lib/constants' -import { User } from '@/components/auth/user' -import type { Session } from '@/lib/session/types' -import { toast } from 'sonner' -import { useRouter } from 'next/navigation' -import { useSetAtom, useAtomValue } from 'jotai' -import { sessionAtom } from '@/lib/atoms/session' -import { githubConnectionAtom, githubConnectionInitializedAtom } from '@/lib/atoms/github-connection' -import { GitHubIcon } from '@/components/icons/github-icon' -import { GitHubStarsButton } from '@/components/github-stars-button' -import { OpenRepoUrlDialog } from '@/components/open-repo-url-dialog' -import { MultiRepoDialog } from '@/components/multi-repo-dialog' -import { useTasks as useTasksContext } from '@/components/app-layout' - -interface HomePageHeaderProps { - selectedOwner: string - selectedRepo: string - onOwnerChange: (owner: string) => void - onRepoChange: (repo: string) => void - user?: Session['user'] | null - initialStars?: number -} - -export function HomePageHeader({ - selectedOwner, - selectedRepo, - onOwnerChange, - onRepoChange, - user, - initialStars = 1200, -}: HomePageHeaderProps) { - const { toggleSidebar } = useTasks() - const routerNav = useRouter() - const githubConnection = useAtomValue(githubConnectionAtom) - const githubConnectionInitialized = useAtomValue(githubConnectionInitializedAtom) - const setGitHubConnection = useSetAtom(githubConnectionAtom) - const [isRefreshing, setIsRefreshing] = useState(false) - const [showOpenRepoDialog, setShowOpenRepoDialog] = useState(false) - const [showMultiRepoDialog, setShowMultiRepoDialog] = useState(false) - const { addTaskOptimistically } = useTasksContext() - - const handleRefreshOwners = async () => { - setIsRefreshing(true) - try { - // Clear only owners cache - localStorage.removeItem('github-owners') - toast.success('Refreshing owners...') - - // Reload the page to fetch fresh data - window.location.reload() - } catch (error) { - console.error('Error refreshing owners:', error) - toast.error('Failed to refresh owners') - } finally { - setIsRefreshing(false) - } - } - - const handleRefreshRepos = async () => { - setIsRefreshing(true) - try { - // Clear repos cache for current owner - if (selectedOwner) { - localStorage.removeItem(`github-repos-${selectedOwner}`) - toast.success('Refreshing repositories...') - - // Reload the page to fetch fresh data - window.location.reload() - } else { - // Clear all repos if no owner selected - Object.keys(localStorage).forEach((key) => { - if (key.startsWith('github-repos-')) { - localStorage.removeItem(key) - } - }) - toast.success('Refreshing all repositories...') - window.location.reload() - } - } catch (error) { - console.error('Error refreshing repositories:', error) - toast.error('Failed to refresh repositories') - } finally { - setIsRefreshing(false) - } - } - - const handleDisconnectGitHub = async () => { - try { - const response = await fetch('/api/auth/github/disconnect', { - method: 'POST', - credentials: 'include', // Ensure cookies are sent - }) - - if (response.ok) { - toast.success('GitHub disconnected') - - // Clear GitHub data from localStorage - localStorage.removeItem('github-owners') - Object.keys(localStorage).forEach((key) => { - if (key.startsWith('github-repos-')) { - localStorage.removeItem(key) - } - }) - - // Clear selected owner/repo - onOwnerChange('') - onRepoChange('') - - // Update connection state - setGitHubConnection({ connected: false }) - - // Refresh the page - routerNav.refresh() - } else { - const error = await response.json() - console.error('Failed to disconnect GitHub:', error) - toast.error(error.error || 'Failed to disconnect GitHub') - } - } catch (error) { - console.error('Failed to disconnect GitHub:', error) - toast.error('Failed to disconnect GitHub') - } - } - - const handleNewRepo = () => { - // Navigate to the new repo page with owner as query param - const url = selectedOwner ? `/repos/new?owner=${selectedOwner}` : '/repos/new' - routerNav.push(url) - } - - const handleOpenRepoUrl = async (repoUrl: string) => { - try { - if (!user) { - toast.error('Sign in required', { - description: 'Please sign in to create tasks with custom repository URLs.', - }) - return - } - - // Create a task with the provided repo URL - // Use default settings for the task - const taskData = { - prompt: 'Work on this repository', - repoUrl: repoUrl, - selectedAgent: localStorage.getItem('last-selected-agent') || 'claude', - selectedModel: localStorage.getItem('last-selected-model-claude') || 'claude-sonnet-4-5', - installDependencies: true, - maxDuration: 300, - keepAlive: false, - } - - // Add task optimistically to sidebar immediately - const { id } = addTaskOptimistically(taskData) - - // Navigate to the new task page immediately - routerNav.push(`/tasks/${id}`) - - // Create the task on the server - const response = await fetch('/api/tasks', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ ...taskData, id }), - }) - - if (response.ok) { - toast.success('Task created successfully!') - } else { - const error = await response.json() - toast.error(error.message || error.error || 'Failed to create task') - } - } catch (error) { - console.error('Error creating task:', error) - toast.error('Failed to create task') - } - } - - const actions = ( -
- {/* GitHub Stars Button - Show on mobile only when logged out (unless owner/repo selected), always show on desktop */} -
- -
- - {/* Deploy to Vercel Button - Show on mobile only when logged out (unless owner/repo selected), always show on desktop */} - - - {/* User Authentication */} - -
- ) - - const handleConnectGitHub = () => { - window.location.href = '/api/auth/github/signin' - } - - const handleReconfigureGitHub = () => { - // Link to GitHub's OAuth app settings page where users can reconfigure access - const clientId = process.env.NEXT_PUBLIC_GITHUB_CLIENT_ID - if (clientId) { - window.open(`https://github.com/settings/connections/applications/${clientId}`, '_blank') - } else { - // Fallback to OAuth flow if client ID is not available - window.location.href = '/api/auth/github/signin' - } - } - - // Get session to check auth provider - const session = useAtomValue(sessionAtom) - // Check if user is authenticated with GitHub (not just connected) - const isGitHubAuthUser = session.authProvider === 'github' - - // Always render leftActions container to prevent layout shift - const leftActions = ( -
- {!githubConnectionInitialized ? null : githubConnection.connected || isGitHubAuthUser ? ( // Show nothing while loading to prevent flash of "Connect GitHub" button - <> - setShowMultiRepoDialog(true)} - /> - - - - - - - - New Repo - - setShowOpenRepoDialog(true)}> - - Open Repo URL - - - - - Refresh Owners - - - - Refresh Repos - - - - Manage Access - - {/* Only show Disconnect for Vercel users who connected GitHub, not for GitHub-authenticated users */} - {!isGitHubAuthUser && ( - - - Disconnect GitHub - - )} - - - - ) : user ? ( - - ) : selectedOwner || selectedRepo ? ( - // Show RepoSelector when logged out if owner/repo are provided via URL - - ) : null} -
- ) - - return ( - <> - - - - - ) -} diff --git a/components/page-header.tsx b/components/page-header.tsx deleted file mode 100644 index 249539cb..00000000 --- a/components/page-header.tsx +++ /dev/null @@ -1,41 +0,0 @@ -'use client' - -import { Button } from '@/components/ui/button' -import { Menu } from 'lucide-react' - -interface PageHeaderProps { - title?: string - showMobileMenu?: boolean - onToggleMobileMenu?: () => void - actions?: React.ReactNode - leftActions?: React.ReactNode - showPlatformName?: boolean -} - -export function PageHeader({ - title, - showMobileMenu = false, - onToggleMobileMenu, - actions, - leftActions, - showPlatformName = false, -}: PageHeaderProps) { - return ( -
-
- {/* Left side - Menu Button and Left Actions */} -
- {showMobileMenu && ( - - )} - {leftActions} -
- - {/* Actions - Right side */} - {actions &&
{actions}
} -
-
- ) -} diff --git a/components/repo-layout.tsx b/components/repo-layout.tsx index 03d1de56..b8c1e18d 100644 --- a/components/repo-layout.tsx +++ b/components/repo-layout.tsx @@ -2,13 +2,9 @@ import { usePathname, useRouter } from 'next/navigation' import Link from 'next/link' -import { PageHeader } from '@/components/page-header' +import { SharedHeader } from '@/components/shared-header' import { Button } from '@/components/ui/button' -import { useTasks } from '@/components/app-layout' -import { VERCEL_DEPLOY_URL } from '@/lib/constants' -import { User } from '@/components/auth/user' import type { Session } from '@/lib/session/types' -import { GitHubStarsButton } from '@/components/github-stars-button' import { cn } from '@/lib/utils' import { setSelectedOwner, setSelectedRepo } from '@/lib/utils/cookies' import { Plus } from 'lucide-react' @@ -23,7 +19,6 @@ interface RepoLayoutProps { } export function RepoLayout({ owner, repo, user, authProvider, initialStars = 1200, children }: RepoLayoutProps) { - const { toggleSidebar } = useTasks() const pathname = usePathname() const router = useRouter() @@ -41,47 +36,18 @@ export function RepoLayout({ owner, repo, user, authProvider, initialStars = 120 router.push('/') } + const headerLeftActions = ( +
+

+ {owner}/{repo} +

+
+ ) + return (
- -

- {owner}/{repo} -

-
- } - actions={ -
- - {/* Deploy to Vercel Button */} - - - {/* User Authentication */} - -
- } - /> +
{/* Main content with tabs */} diff --git a/components/repo-page-client.tsx b/components/repo-page-client.tsx index 463969be..a47fd2cc 100644 --- a/components/repo-page-client.tsx +++ b/components/repo-page-client.tsx @@ -1,13 +1,8 @@ 'use client' import { useState } from 'react' -import { PageHeader } from '@/components/page-header' -import { Button } from '@/components/ui/button' -import { useTasks } from '@/components/app-layout' -import { VERCEL_DEPLOY_URL } from '@/lib/constants' -import { User } from '@/components/auth/user' +import { SharedHeader } from '@/components/shared-header' import type { Session } from '@/lib/session/types' -import { GitHubStarsButton } from '@/components/github-stars-button' import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' import { RepoCommits } from '@/components/repo-commits' import { RepoPullRequests } from '@/components/repo-pull-requests' @@ -22,50 +17,20 @@ interface RepoPageClientProps { } export function RepoPageClient({ owner, repo, user, authProvider, initialStars = 1200 }: RepoPageClientProps) { - const { toggleSidebar } = useTasks() const [activeTab, setActiveTab] = useState('commits') + const headerLeftActions = ( +
+

+ {owner}/{repo} +

+
+ ) + return (
- -

- {owner}/{repo} -

-
- } - actions={ -
- - {/* Deploy to Vercel Button */} - - - {/* User Authentication */} - -
- } - /> +
{/* Main content with tabs */} diff --git a/components/shared-header.tsx b/components/shared-header.tsx new file mode 100644 index 00000000..af3f1892 --- /dev/null +++ b/components/shared-header.tsx @@ -0,0 +1,70 @@ +'use client' + +import { Button } from '@/components/ui/button' +import { Menu } from 'lucide-react' +import { useTasks } from '@/components/app-layout' +import { User } from '@/components/auth/user' +import { GitHubStarsButton } from '@/components/github-stars-button' +import { VERCEL_DEPLOY_URL } from '@/lib/constants' + +interface SharedHeaderProps { + leftActions?: React.ReactNode + extraActions?: React.ReactNode + initialStars?: number + hideStars?: boolean + hideDeployButton?: boolean +} + +export function SharedHeader({ + leftActions, + extraActions, + initialStars = 1200, + hideStars = false, + hideDeployButton = false, +}: SharedHeaderProps) { + const { toggleSidebar } = useTasks() + + return ( +
+
+ {/* Left side - Menu Button and Left Actions */} +
+ + {leftActions} +
+ + {/* Actions - Right side */} +
+ {!hideStars && } + + {!hideDeployButton && ( + + )} + + {extraActions} + + +
+
+
+ ) +} diff --git a/components/task-page-client.tsx b/components/task-page-client.tsx index 176ed40d..fe7fa9b6 100644 --- a/components/task-page-client.tsx +++ b/components/task-page-client.tsx @@ -1,17 +1,12 @@ 'use client' -import { useState } from 'react' +import { useState, useMemo } from 'react' import { useTask } from '@/lib/hooks/use-task' import { TaskDetails } from '@/components/task-details' -import { TaskPageHeader } from '@/components/task-page-header' -import { PageHeader } from '@/components/page-header' -import { Button } from '@/components/ui/button' -import { useTasks } from '@/components/app-layout' +import { SharedHeader } from '@/components/shared-header' +import { TaskActions } from '@/components/task-actions' import { LogsPane } from '@/components/logs-pane' -import { VERCEL_DEPLOY_URL } from '@/lib/constants' -import { User } from '@/components/auth/user' import type { Session } from '@/lib/session/types' -import { GitHubStarsButton } from '@/components/github-stars-button' interface TaskPageClientProps { taskId: string @@ -21,6 +16,23 @@ interface TaskPageClientProps { maxSandboxDuration?: number } +function parseRepoFromUrl(repoUrl: string | null): { owner: string; repo: string } | null { + if (!repoUrl) return null + try { + const url = new URL(repoUrl) + const pathParts = url.pathname.split('/').filter(Boolean) + if (pathParts.length >= 2) { + return { + owner: pathParts[0], + repo: pathParts[1].replace(/\.git$/, ''), + } + } + return null + } catch { + return null + } +} + export function TaskPageClient({ taskId, user, @@ -29,44 +41,23 @@ export function TaskPageClient({ maxSandboxDuration = 300, }: TaskPageClientProps) { const { task, isLoading, error } = useTask(taskId) - const { toggleSidebar } = useTasks() const [logsPaneHeight, setLogsPaneHeight] = useState(40) // Default to collapsed height + const repoInfo = useMemo(() => parseRepoFromUrl(task?.repoUrl ?? null), [task?.repoUrl]) + + const headerLeftActions = repoInfo ? ( +
+

+ {repoInfo.owner}/{repoInfo.repo} +

+
+ ) : null + if (isLoading) { return (
- - - {/* Deploy to Vercel Button */} - - - {/* User Authentication */} - -
- } - /> +
) @@ -76,36 +67,7 @@ export function TaskPageClient({ return (
- - - {/* Deploy to Vercel Button */} - - -
- } - /> +
@@ -122,7 +84,11 @@ export function TaskPageClient({ return (
- + } + />
{/* Task details */} diff --git a/components/task-page-header.tsx b/components/task-page-header.tsx deleted file mode 100644 index 6363c030..00000000 --- a/components/task-page-header.tsx +++ /dev/null @@ -1,51 +0,0 @@ -'use client' - -import { Task } from '@/lib/db/schema' -import { PageHeader } from '@/components/page-header' -import { TaskActions } from '@/components/task-actions' -import { useTasks } from '@/components/app-layout' -import { User } from '@/components/auth/user' -import { Button } from '@/components/ui/button' -import { VERCEL_DEPLOY_URL } from '@/lib/constants' -import type { Session } from '@/lib/session/types' -import { GitHubStarsButton } from '@/components/github-stars-button' - -interface TaskPageHeaderProps { - task: Task - user: Session['user'] | null - authProvider: Session['authProvider'] | null - initialStars?: number -} - -export function TaskPageHeader({ task, user, authProvider, initialStars = 1200 }: TaskPageHeaderProps) { - const { toggleSidebar } = useTasks() - - return ( - - - {/* Deploy to Vercel Button */} - - - -
- } - /> - ) -} diff --git a/components/tasks-list-client.tsx b/components/tasks-list-client.tsx index f03df4d2..6af6477e 100644 --- a/components/tasks-list-client.tsx +++ b/components/tasks-list-client.tsx @@ -2,7 +2,7 @@ import { useState, useEffect, useMemo } from 'react' import { Task } from '@/lib/db/schema' -import { PageHeader } from '@/components/page-header' +import { SharedHeader } from '@/components/shared-header' import { useTasks } from '@/components/app-layout' import { Button } from '@/components/ui/button' import { Card, CardContent } from '@/components/ui/card' @@ -19,13 +19,10 @@ import { } from '@/components/ui/alert-dialog' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' import { AlertCircle, Trash2, Square, StopCircle, CheckSquare, X, Clock } from 'lucide-react' -import { GitHubStarsButton } from '@/components/github-stars-button' -import { User } from '@/components/auth/user' import { toast } from 'sonner' import { useRouter } from 'next/navigation' import { cn } from '@/lib/utils' import type { Session } from '@/lib/session/types' -import { VERCEL_DEPLOY_URL } from '@/lib/constants' import { Claude, Codex, Copilot, Cursor, Gemini, OpenCode } from '@/components/logos' import { PRStatusIcon } from '@/components/pr-status-icon' import { PRCheckStatus } from '@/components/pr-check-status' @@ -274,35 +271,7 @@ export function TasksListClient({ user, authProvider, initialStars = 1200 }: Tas return (
- } - /> +
From 496b89266a5b3f5b4c7e6a5ff49e91577b68874f Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Thu, 22 Jan 2026 22:11:14 -0600 Subject: [PATCH 2/6] move repo --- components/task-details.tsx | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/components/task-details.tsx b/components/task-details.tsx index 246a0066..c1e130be 100644 --- a/components/task-details.tsx +++ b/components/task-details.tsx @@ -1493,31 +1493,6 @@ export function TaskDetails({ task, maxSandboxDuration = 300 }: TaskDetailsProps {/* Compact info row */}
- {/* Repo */} - {task.repoUrl && ( - - )} - {/* Branch */} {task.branchName && (
From 18a6f4d6e8e78b9fc976099292cdcbe81bd63281 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Thu, 22 Jan 2026 22:12:31 -0600 Subject: [PATCH 3/6] remove "Preview" --- components/task-details.tsx | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/components/task-details.tsx b/components/task-details.tsx index c1e130be..972bc193 100644 --- a/components/task-details.tsx +++ b/components/task-details.tsx @@ -1588,21 +1588,6 @@ export function TaskDetails({ task, maxSandboxDuration = 300 }: TaskDetailsProps )} - {/* Preview Deployment */} - {!loadingDeployment && deploymentUrl && ( - - )} - {/* Desktop Pane Toggles - Only show on desktop */}
{/* Tab Content */} - {renderTabContent()} +
{renderTabContent()}
{/* Input Area (only for chat tab) */} {activeTab === 'chat' && ( -
-