Fixed filtering and simplified octokit context

This commit is contained in:
NasSharaf 2025-09-04 12:05:20 -04:00
parent 3a2a6dca7e
commit cd3cd7667a
4 changed files with 187 additions and 165 deletions

View File

@ -2,12 +2,12 @@
import { PageWrapper } from '@/components/foundation'
import CheckBalanceIframe from '@/components/iframe/check-balance-iframe/CheckBalanceIframe'
import { FixedProjectCard } from '@/components/projects/project/ProjectCard/FixedProjectCard'
import { Button } from '@workspace/ui/components/button'
import { useEffect, useState } from 'react'
import { Shapes } from 'lucide-react'
import { useGQLClient } from '@/context'
import { useUser } from '@clerk/nextjs'
import type { Project } from '@workspace/gql-client'
import { Button } from '@workspace/ui/components/button'
import { Shapes } from 'lucide-react'
import { useEffect, useState } from 'react'
interface ProjectData {
id: string
@ -24,7 +24,7 @@ export default function ProjectsPage() {
const [projects, setProjects] = useState<ProjectData[]>([])
const [isLoading, setIsLoading] = useState<boolean>(true)
const [error, setError] = useState<string | null>(null)
const client = useGQLClient()
const { user } = useUser()
@ -95,7 +95,7 @@ export default function ProjectsPage() {
console.log(
`🔍 Project ${project.name}: repo owner = ${repoOwner}, current user = ${githubUsername}`
)
return repoOwner === githubUsername
return repoOwner.toLowerCase() === githubUsername.toLowerCase()
}
return true // Include projects without repository info
})
@ -130,7 +130,7 @@ export default function ProjectsPage() {
setIsLoading(false)
}
}
return (
<PageWrapper
header={{
@ -157,7 +157,7 @@ export default function ProjectsPage() {
<p className="text-gray-400 text-center max-w-md mb-6">
Failed to load your deployed projects. Please try again.
</p>
<Button
<Button
className="bg-white text-black hover:bg-gray-200 flex items-center"
onClick={loadAllProjects}
>
@ -174,13 +174,20 @@ export default function ProjectsPage() {
</div>
<h2 className="text-xl font-semibold mb-2">Deploy your first app</h2>
<p className="text-gray-400 text-center max-w-md mb-6">
You don't have any deployed projects yet. Create your first project to get started.
You don't have any deployed projects yet. Create your first project
to get started.
</p>
<Button
<Button
className="bg-white text-black hover:bg-gray-200 flex items-center"
onClick={handleCreateProject}
>
<svg className="mr-2 h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<svg
className="mr-2 h-4 w-4"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
>
<line x1="12" y1="5" x2="12" y2="19"></line>
<line x1="5" y1="12" x2="19" y2="12"></line>
</svg>
@ -193,9 +200,11 @@ export default function ProjectsPage() {
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
{projects.map((project) => {
// Get the current deployment for status
const currentDeployment = project.deployments.find(d => d.isCurrent)
const currentDeployment = project.deployments.find(
(d) => d.isCurrent
)
const latestDeployment = project.deployments[0] // Assuming sorted by date
// Determine status based on deployment
let status = 'pending'
if (currentDeployment || latestDeployment) {
@ -217,30 +226,34 @@ export default function ProjectsPage() {
status = 'pending'
}
}
// Format the project data to match what FixedProjectCard expects
const formattedProject = {
id: project.id,
name: project.name,
full_name: project.repository ? project.repository.replace('https://github.com/', '') : project.name,
full_name: project.repository
? project.repository.replace('https://github.com/', '')
: project.name,
repository: project.repository,
framework: project.framework,
description: project.description,
// Ensure deployments array is properly formatted
deployments: project.deployments.map(deployment => ({
deployments: project.deployments.map((deployment) => ({
...deployment,
// Make sure the date is in a format the card can parse
createdAt: deployment.createdAt,
applicationDeploymentRecordData: {
url: deployment.applicationDeploymentRecordData?.url || `https://${project.name.toLowerCase()}.example.com`
url:
deployment.applicationDeploymentRecordData?.url ||
`https://${project.name.toLowerCase()}.example.com`
}
}))
}
return (
<FixedProjectCard
project={formattedProject}
key={project.id}
<FixedProjectCard
project={formattedProject}
key={project.id}
status={status as any}
/>
)
@ -248,7 +261,7 @@ export default function ProjectsPage() {
</div>
</div>
)}
{/* Wrap in try/catch to prevent breaking if there are issues */}
{(() => {
try {
@ -258,12 +271,12 @@ export default function ProjectsPage() {
isPollingEnabled={false}
amount="1"
/>
);
)
} catch (error) {
console.error('Failed to render CheckBalanceIframe:', error);
return null;
console.error('Failed to render CheckBalanceIframe:', error)
return null
}
})()}
</PageWrapper>
)
}
}

View File

@ -73,7 +73,17 @@ export function ConnectStep() {
} = useAuthStatus()
// Repository data
const { repoData: repositories, isLoading: isLoadingRepos } = useRepoData('')
const { repoData: repositories, isLoading: isLoadingRepos, error: repoError } = useRepoData('')
// Debug repository data
useEffect(() => {
console.log('🔍 ConnectStep: Repository data changed:', {
repositories: repositories ? `Array with ${Array.isArray(repositories) ? repositories.length : 'not array'} items` : 'null',
isLoadingRepos,
repoError,
isFullyAuthenticated
})
}, [repositories, isLoadingRepos, repoError, isFullyAuthenticated])
// Handle hydration mismatch by waiting for mount
useEffect(() => {

View File

@ -1,11 +1,11 @@
'use client'
import { useEffect, useState } from 'react'
import { useRouter, useParams } from 'next/navigation'
import { useTheme } from 'next-themes'
import { CheckCircle } from 'lucide-react'
import { useOnboarding } from '@/components/onboarding/store'
import { Button } from '@workspace/ui/components/button'
import { useOnboarding } from '@/components/onboarding/useOnboarding'
import { CheckCircle } from 'lucide-react'
import { useTheme } from 'next-themes'
import { useParams, useRouter } from 'next/navigation'
import { useEffect, useState } from 'react'
export function SuccessStep() {
const router = useRouter()
@ -14,45 +14,49 @@ export function SuccessStep() {
const [mounted, setMounted] = useState(false)
const providerParam = params?.provider ? String(params.provider) : 'github'
const { formData, resetOnboarding } = useOnboarding()
// Handle hydration mismatch by waiting for mount
useEffect(() => {
setMounted(true)
}, [])
// Get deployment info from form data
const repoName = formData.githubRepo ? formData.githubRepo.split('/').pop() : (formData.projectName || 'project')
const deploymentUrl = formData.deploymentUrl || `https://${repoName}.laconic.deploy`
const projectId = formData.projectId || 'unknown-id'
// Get deployment info from form data - using available properties
const repoName = formData.githubRepo
? formData.githubRepo.split('/').pop()
: formData.repoName
? formData.repoName.split('/').pop()
: formData.projectName || 'project'
const deploymentUrl = `https://${repoName}.laconic.deploy` // Default deployment URL
const projectId = formData.projectId || formData.deploymentId || 'unknown-id' // Use projectId first, fallback to deploymentId
// Handle "View Project" button - navigates to project page
const handleViewProject = () => {
console.log('Navigating to project with ID:', projectId)
resetOnboarding() // Reset state for next time
// Navigate to the project detail page using the GraphQL project ID
router.push(`/projects/${providerParam}/ps/${projectId}`)
}
// Auto-redirect after a delay (optional)
useEffect(() => {
if (mounted && projectId && projectId !== 'unknown-id') {
const timer = setTimeout(() => {
handleViewProject()
}, 3000) // Auto-redirect after 3 seconds
return () => clearTimeout(timer)
}
}, [mounted, projectId])
// Don't render UI until after mount to prevent hydration mismatch
if (!mounted) {
return null
}
// Determine if dark mode is active
const isDarkMode = resolvedTheme === 'dark'
return (
<div className="w-full h-full flex flex-col items-center justify-center p-8">
<div className="max-w-md w-full mx-auto">
@ -60,17 +64,21 @@ export function SuccessStep() {
<div className="mx-auto mb-6 flex justify-center">
<CheckCircle className="h-16 w-16 text-green-500" />
</div>
{/* Success header */}
<h2 className={`text-2xl font-medium ${isDarkMode ? "text-white" : "text-zinc-900"} text-center mb-2`}>
<h2
className={`text-2xl font-medium ${isDarkMode ? 'text-white' : 'text-zinc-900'} text-center mb-2`}
>
Successfully Deployed!
</h2>
<p className="text-center text-zinc-500 mb-8">
Your project has been deployed successfully
</p>
{/* Deployment summary */}
<div className={`border rounded-md p-4 mb-6 ${isDarkMode ? "border-zinc-700" : "border-zinc-300"}`}>
<div
className={`border rounded-md p-4 mb-6 ${isDarkMode ? 'border-zinc-700' : 'border-zinc-300'}`}
>
<div className="space-y-2">
<div className="flex justify-between">
<span className="text-muted-foreground">Project:</span>
@ -86,39 +94,82 @@ export function SuccessStep() {
</div>
</div>
</div>
{/* Next steps section */}
<div className="mb-8">
<h3 className={`text-lg font-medium ${isDarkMode ? "text-white" : "text-zinc-900"} mb-4`}>Next steps</h3>
<div className={`border rounded-md overflow-hidden mb-4 ${isDarkMode ? "border-zinc-700" : "border-zinc-300"}`}>
<h3
className={`text-lg font-medium ${isDarkMode ? 'text-white' : 'text-zinc-900'} mb-4`}
>
Next steps
</h3>
<div
className={`border rounded-md overflow-hidden mb-4 ${isDarkMode ? 'border-zinc-700' : 'border-zinc-300'}`}
>
<div className="flex items-center p-4 justify-between">
<div>
<div className={isDarkMode ? "text-white font-medium" : "text-zinc-900 font-medium"}>Setup Domain</div>
<div className="text-zinc-500 text-sm">Add a custom domain to your project.</div>
<div
className={
isDarkMode
? 'text-white font-medium'
: 'text-zinc-900 font-medium'
}
>
Setup Domain
</div>
<div className="text-zinc-500 text-sm">
Add a custom domain to your project.
</div>
</div>
<Button variant="outline" className={`rounded-full p-1 w-8 h-8 flex items-center justify-center ${isDarkMode ? "border-zinc-700" : "border-zinc-300"}`}>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" className={isDarkMode ? "text-white" : "text-zinc-900"}>
<path d="M9 18L15 12L9 6" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
<Button
variant="outline"
className={`rounded-full p-1 w-8 h-8 flex items-center justify-center ${isDarkMode ? 'border-zinc-700' : 'border-zinc-300'}`}
>
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={isDarkMode ? 'text-white' : 'text-zinc-900'}
>
<path
d="M9 18L15 12L9 6"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
</Button>
</div>
</div>
</div>
{/* Action buttons */}
<div className="flex flex-col space-y-3">
<Button
<Button
className="w-full bg-white hover:bg-white/90 text-black flex items-center justify-center"
onClick={handleViewProject}
disabled={!projectId || projectId === 'unknown-id'}
>
View Project
<svg className="ml-2 h-4 w-4" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5 12H19M19 12L13 6M19 12L13 18" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
View Project
<svg
className="ml-2 h-4 w-4"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M5 12H19M19 12L13 6M19 12L13 18"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
</Button>
{/* Manual navigation button if auto-redirect fails */}
{projectId === 'unknown-id' && (
<p className="text-center text-xs text-muted-foreground">
@ -129,4 +180,4 @@ export function SuccessStep() {
</div>
</div>
)
}
}

View File

@ -1,8 +1,6 @@
'use client'
import { getGitHubToken } from '@/actions/github'
import { useAuth, useUser } from '@clerk/nextjs'
import { Octokit } from '@octokit/rest'
import { useOctokit } from '@/context/OctokitContext'
import { useEffect, useState } from 'react'
// Define the return type of the hook
@ -22,113 +20,53 @@ export function useRepoData(repoId: string): UseRepoDataReturn {
const [repoData, setRepoData] = useState<any>(null)
const [isLoading, setIsLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
const [octokit, setOctokit] = useState<Octokit | null>(null)
// Get auth data from Clerk
const { isLoaded: isAuthLoaded, getToken, isSignedIn } = useAuth()
const { isLoaded: isUserLoaded, user } = useUser()
// Use the centralized Octokit context instead of creating our own
const { octokit, isAuth } = useOctokit()
// Initialize Octokit with the appropriate token
// Debug the context state
useEffect(() => {
async function initializeOctokit() {
let token = null
console.log('🔍 useRepoData: Context state changed:', {
hasOctokit: !!octokit,
isAuth,
repoId
})
}, [octokit, isAuth, repoId])
// Try to get GitHub OAuth token from Clerk using multiple methods
if (isSignedIn && user) {
try {
// Check if user has connected GitHub account
const githubAccount = user.externalAccounts.find(
(account) => account.provider === 'github'
)
if (githubAccount) {
// Try multiple methods to get the GitHub OAuth token (same as test-connection page)
// Method 1: Try getToken from useAuth hook
try {
token = await getToken()
console.log('Method 1 (getToken from useAuth) worked:', token ? 'SUCCESS' : 'NO TOKEN')
} catch (error) {
console.log('Method 1 failed:', error)
}
// Method 2: Try with GitHub template parameter
if (!token) {
try {
token = await getToken({ template: 'github' })
console.log('Method 2 (getToken with github template) worked:', token ? 'SUCCESS' : 'NO TOKEN')
} catch (error) {
console.log('Method 2 failed:', error)
}
}
// Method 3: Try accessing window.Clerk
if (!token && typeof window !== 'undefined' && (window as any).Clerk) {
try {
token = await (window as any).Clerk.session?.getToken()
console.log('Method 3 (window.Clerk.session.getToken) worked:', token ? 'SUCCESS' : 'NO TOKEN')
} catch (error) {
console.log('Method 3 failed:', error)
}
}
// Method 4: Try window.Clerk with GitHub template
if (!token && typeof window !== 'undefined' && (window as any).Clerk) {
try {
token = await (window as any).Clerk.session?.getToken({ template: 'github' })
console.log('Method 4 (window.Clerk with github template) worked:', token ? 'SUCCESS' : 'NO TOKEN')
} catch (error) {
console.log('Method 4 failed:', error)
}
}
if (token) {
console.log('Successfully obtained GitHub token from Clerk')
} else {
console.warn('All methods failed to get GitHub OAuth token from Clerk')
}
}
} catch (err) {
console.error('Error accessing Clerk user data:', err)
}
}
// Fallback to token from environment variable
if (!token) {
token = process.env.NEXT_PUBLIC_GITHUB_FALLBACK_TOKEN || ''
if (token) {
console.warn(
'Using fallback GitHub token. This should only be used for development.'
)
}
}
// Create Octokit instance with whatever token we found
if (token) {
setOctokit(new Octokit({ auth: token }))
} else {
setError('No GitHub token available')
setIsLoading(false)
}
}
if (isAuthLoaded && isUserLoaded) {
initializeOctokit()
}
}, [isAuthLoaded, isUserLoaded, user])
// Fetch repo data when Octokit is available
// Fetch repo data when Octokit is available and authenticated
useEffect(() => {
let isMounted = true
async function fetchRepoData() {
if (!octokit) {
// Don't attempt to fetch if not authenticated
if (!isAuth) {
console.log('🔍 fetchRepoData: Not authenticated, skipping fetch')
if (isMounted) {
setError('GitHub authentication required')
setRepoData(null)
setIsLoading(false)
}
return
}
if (!octokit) {
console.log('🔍 fetchRepoData: No octokit instance available')
if (isMounted) {
setError('GitHub client not available')
setRepoData(null)
setIsLoading(false)
}
return
}
console.log('🔍 fetchRepoData: Starting to fetch repos...')
console.log('🔍 fetchRepoData: isAuth =', isAuth)
try {
// Fetch repos from GitHub
const { data: repos } = await octokit.repos.listForAuthenticatedUser()
console.log('🔍 Making GitHub API call: octokit.rest.repos.listForAuthenticatedUser()')
const { data: repos } = await octokit.rest.repos.listForAuthenticatedUser()
console.log('🔍 GitHub API success! Received', repos.length, 'repositories')
// If no repoId is provided, return all repos
if (!repoId) {
@ -141,7 +79,7 @@ export function useRepoData(repoId: string): UseRepoDataReturn {
}
// Find the specific repo by ID if repoId is provided
const repo = repos.find((repo) => repo.id.toString() === repoId)
const repo = repos.find((repo: any) => repo.id.toString() === repoId)
if (!repo) {
if (isMounted) {
@ -157,7 +95,12 @@ export function useRepoData(repoId: string): UseRepoDataReturn {
}
}
} catch (err) {
console.error('Error fetching GitHub repo:', err)
console.error('❌ Error fetching GitHub repo:', err)
console.error('❌ Error details:', {
message: err instanceof Error ? err.message : 'Unknown error',
status: (err as any)?.status,
response: (err as any)?.response?.data,
})
if (isMounted) {
setError('Failed to fetch repository data')
setRepoData(null)
@ -166,14 +109,19 @@ export function useRepoData(repoId: string): UseRepoDataReturn {
}
}
if (octokit) {
if (octokit && isAuth) {
fetchRepoData()
} else if (!isAuth) {
// Handle case where we're not authenticated yet
console.log('🔍 useRepoData: Waiting for authentication...')
setIsLoading(true)
setError(null)
}
return () => {
isMounted = false
}
}, [repoId, octokit])
}, [repoId, octokit, isAuth])
return { repoData, isLoading, error }
}