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/file-browser.tsx b/components/file-browser.tsx index 8b0df46a..3603cf98 100644 --- a/components/file-browser.tsx +++ b/components/file-browser.tsx @@ -1347,9 +1347,9 @@ export function FileBrowser({ return (
{!hideHeader && ( -
+
{/* Main Navigation Row */} -
+
{/* Changes / Files Navigation */}
+ + + + + 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 +555,7 @@ export function HomePageContent({ return (
- +
@@ -379,6 +575,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-chat.tsx b/components/task-chat.tsx index bff2042a..71f7bd8e 100644 --- a/components/task-chat.tsx +++ b/components/task-chat.tsx @@ -1226,7 +1226,7 @@ export function TaskChat({ taskId, task }: TaskChatProps) { return (
{/* Header Tabs */} -
+
{/* Tab Content */} - {renderTabContent()} +
{renderTabContent()}
{/* Input Area (only for chat tab) */} {activeTab === 'chat' && ( -
-