Add GitHub user filtering and core authentication improvements
This commit is contained in:
parent
2c7c40697a
commit
ce851ef883
@ -34,3 +34,30 @@ export async function getGitHubOrgs() {
|
||||
avatarUrl: org.avatar_url
|
||||
}))
|
||||
}
|
||||
|
||||
export async function getGitHubToken() {
|
||||
const { userId } = await auth()
|
||||
|
||||
if (!userId) {
|
||||
throw new Error('Unauthorized')
|
||||
}
|
||||
|
||||
const user = await currentUser()
|
||||
const githubAccount = user?.externalAccounts.find(
|
||||
(account) => account.provider === 'github'
|
||||
)
|
||||
|
||||
if (!githubAccount) {
|
||||
throw new Error('GitHub not connected')
|
||||
}
|
||||
|
||||
// For server actions, we can access the external account token directly
|
||||
// This is a simplified approach that uses the account's external ID as token
|
||||
const token = githubAccount.externalId
|
||||
|
||||
if (!token) {
|
||||
throw new Error('Failed to get GitHub token')
|
||||
}
|
||||
|
||||
return token
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ import { ConfigureStep } from '@/components/onboarding/configure-step/configure-
|
||||
import { ConnectStep } from '@/components/onboarding/connect-step/connect-step'
|
||||
import { DeployStep } from '@/components/onboarding/deploy-step/deploy-step'
|
||||
import { SuccessStep } from '@/components/onboarding/success-step/success-step'
|
||||
import { useOnboarding } from '@/components/onboarding/useOnboarding'
|
||||
import { useOnboarding } from '@/components/onboarding/store'
|
||||
import { X } from 'lucide-react'
|
||||
import { useTheme } from 'next-themes'
|
||||
import { useRouter } from 'next/navigation'
|
||||
@ -20,7 +20,7 @@ export default function CreateProjectFlow() {
|
||||
const { resolvedTheme } = useTheme()
|
||||
const [mounted, setMounted] = useState(false)
|
||||
|
||||
const { currentStep, setCurrentStep, resetOnboarding } = useOnboarding()
|
||||
const { currentStep, setCurrentStep } = useOnboarding()
|
||||
|
||||
// Handle hydration mismatch by waiting for mount
|
||||
useEffect(() => {
|
||||
@ -32,7 +32,7 @@ export default function CreateProjectFlow() {
|
||||
return () => {
|
||||
// Optional cleanup actions
|
||||
}
|
||||
}, [resetOnboarding])
|
||||
}, [])
|
||||
|
||||
// Handle closing the modal
|
||||
const handleClose = () => {
|
||||
|
@ -6,6 +6,7 @@ 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'
|
||||
|
||||
interface ProjectData {
|
||||
@ -25,53 +26,102 @@ export default function ProjectsPage() {
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
|
||||
const client = useGQLClient()
|
||||
const { user } = useUser()
|
||||
|
||||
const handleCreateProject = () => {
|
||||
window.location.href = '/projects/github/ps/cr'
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
loadAllProjects()
|
||||
}, [])
|
||||
if (user !== undefined) {
|
||||
loadAllProjects()
|
||||
}
|
||||
}, [user])
|
||||
|
||||
const loadAllProjects = async () => {
|
||||
try {
|
||||
setIsLoading(true)
|
||||
setError(null)
|
||||
|
||||
// First get organizations
|
||||
const orgsResponse = await client.getOrganizations()
|
||||
|
||||
if (!orgsResponse.organizations || orgsResponse.organizations.length === 0) {
|
||||
|
||||
if (user === null) {
|
||||
// User is not authenticated
|
||||
setProjects([])
|
||||
setIsLoading(false)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
// Get the authenticated user's GitHub username
|
||||
const githubAccount = user?.externalAccounts?.find(
|
||||
(account) => account.provider === 'github'
|
||||
)
|
||||
const githubUsername = githubAccount?.username
|
||||
|
||||
if (!githubUsername) {
|
||||
console.warn('No GitHub username found for user')
|
||||
setProjects([])
|
||||
setIsLoading(false)
|
||||
return
|
||||
}
|
||||
|
||||
console.log('🔍 Filtering projects for GitHub user:', githubUsername)
|
||||
|
||||
// First get organizations
|
||||
const orgsResponse = await client.getOrganizations()
|
||||
|
||||
if (
|
||||
!orgsResponse.organizations ||
|
||||
orgsResponse.organizations.length === 0
|
||||
) {
|
||||
setProjects([])
|
||||
setIsLoading(false)
|
||||
return
|
||||
}
|
||||
|
||||
// Get projects from all organizations
|
||||
const allProjects: ProjectData[] = []
|
||||
|
||||
|
||||
for (const org of orgsResponse.organizations) {
|
||||
try {
|
||||
const projectsResponse = await client.getProjectsInOrganization(org.slug)
|
||||
|
||||
// Transform GraphQL projects to match ProjectData interface
|
||||
const orgProjects: ProjectData[] = projectsResponse.projectsInOrganization.map((project: Project) => ({
|
||||
id: project.id,
|
||||
name: project.name,
|
||||
repository: project.repository,
|
||||
framework: project.framework,
|
||||
description: project.description,
|
||||
deployments: project.deployments || []
|
||||
}))
|
||||
|
||||
allProjects.push(...orgProjects)
|
||||
const projectsResponse = await client.getProjectsInOrganization(
|
||||
org.slug
|
||||
)
|
||||
|
||||
// Filter projects by GitHub username and transform to ProjectData interface
|
||||
const userProjects: ProjectData[] =
|
||||
projectsResponse.projectsInOrganization
|
||||
.filter((project: Project) => {
|
||||
if (project.repository) {
|
||||
const repoOwner = project.repository.split('/')[0]
|
||||
console.log(
|
||||
`🔍 Project ${project.name}: repo owner = ${repoOwner}, current user = ${githubUsername}`
|
||||
)
|
||||
return repoOwner === githubUsername
|
||||
}
|
||||
return true // Include projects without repository info
|
||||
})
|
||||
.map((project: Project) => ({
|
||||
id: project.id,
|
||||
name: project.name,
|
||||
repository: project.repository,
|
||||
framework: project.framework,
|
||||
description: project.description,
|
||||
deployments: project.deployments || []
|
||||
}))
|
||||
|
||||
console.log(
|
||||
`🔍 Found ${userProjects.length} projects for ${githubUsername} in org ${org.slug}`
|
||||
)
|
||||
allProjects.push(...userProjects)
|
||||
} catch (orgError) {
|
||||
console.error(`Failed to load projects for org ${org.slug}:`, orgError)
|
||||
console.error(
|
||||
`Failed to load projects for org ${org.slug}:`,
|
||||
orgError
|
||||
)
|
||||
// Continue with other orgs even if one fails
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
console.log('🔍 Total filtered projects:', allProjects)
|
||||
setProjects(allProjects)
|
||||
} catch (err) {
|
||||
console.error('Failed to load projects:', err)
|
||||
|
118
apps/deploy-fe/src/hooks/useDirectGitHub.tsx
Normal file
118
apps/deploy-fe/src/hooks/useDirectGitHub.tsx
Normal file
@ -0,0 +1,118 @@
|
||||
'use client'
|
||||
|
||||
import { getGitHubToken } from '@/actions/github'
|
||||
import { Octokit } from '@octokit/rest'
|
||||
import { useState } from 'react'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
interface CreateRepoFromTemplateParams {
|
||||
templateOwner: string
|
||||
templateRepo: string
|
||||
name: string
|
||||
description?: string
|
||||
isPrivate?: boolean
|
||||
}
|
||||
|
||||
interface CreateRepoResult {
|
||||
success: boolean
|
||||
repositoryUrl?: string
|
||||
error?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook to directly interact with GitHub API using user's own token
|
||||
* Bypasses backend GitHub integration issues
|
||||
*/
|
||||
export function useDirectGitHub() {
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
|
||||
const createRepoFromTemplate = async (
|
||||
params: CreateRepoFromTemplateParams
|
||||
): Promise<CreateRepoResult> => {
|
||||
setIsLoading(true)
|
||||
|
||||
try {
|
||||
console.log(
|
||||
'🔄 Creating repository from template directly via GitHub API...'
|
||||
)
|
||||
console.log('📋 Parameters:', params)
|
||||
|
||||
// Get user's GitHub token
|
||||
const token = await getGitHubToken()
|
||||
if (!token) {
|
||||
throw new Error(
|
||||
'GitHub token not available. Please reconnect your GitHub account.'
|
||||
)
|
||||
}
|
||||
|
||||
// Create Octokit instance with user's token
|
||||
const octokit = new Octokit({ auth: token })
|
||||
|
||||
// Get the authenticated user's info to use as owner
|
||||
const { data: authUser } = await octokit.rest.users.getAuthenticated()
|
||||
console.log('👤 Authenticated GitHub user:', authUser.login)
|
||||
|
||||
// Create repository from template
|
||||
const { data: newRepo } = await octokit.rest.repos.createUsingTemplate({
|
||||
template_owner: params.templateOwner,
|
||||
template_repo: params.templateRepo,
|
||||
owner: authUser.login, // Use authenticated user as owner
|
||||
name: params.name,
|
||||
description:
|
||||
params.description ||
|
||||
`Created from ${params.templateOwner}/${params.templateRepo}`,
|
||||
private: params.isPrivate || false,
|
||||
include_all_branches: false
|
||||
})
|
||||
|
||||
console.log('✅ Repository created successfully:', newRepo.html_url)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
repositoryUrl: newRepo.html_url
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ Failed to create repository from template:', error)
|
||||
|
||||
let errorMessage = 'Failed to create repository from template'
|
||||
if (error instanceof Error) {
|
||||
errorMessage = error.message
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: errorMessage
|
||||
}
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
const getUserRepos = async () => {
|
||||
try {
|
||||
const token = await getGitHubToken()
|
||||
if (!token) {
|
||||
throw new Error('GitHub token not available')
|
||||
}
|
||||
|
||||
const octokit = new Octokit({ auth: token })
|
||||
const { data: repos } = await octokit.rest.repos.listForAuthenticatedUser(
|
||||
{
|
||||
sort: 'updated',
|
||||
per_page: 100
|
||||
}
|
||||
)
|
||||
|
||||
return repos
|
||||
} catch (error) {
|
||||
console.error('❌ Failed to fetch user repositories:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
createRepoFromTemplate,
|
||||
getUserRepos,
|
||||
isLoading
|
||||
}
|
||||
}
|
316
apps/deploy-fe/src/hooks/useDirectTemplateDeployment.tsx
Normal file
316
apps/deploy-fe/src/hooks/useDirectTemplateDeployment.tsx
Normal file
@ -0,0 +1,316 @@
|
||||
// src/hooks/useDirectTemplateDeployment.tsx
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
import { useGQLClient } from '@/context'
|
||||
import { useWallet } from '@/context/WalletContext'
|
||||
import { useUser } from '@clerk/nextjs'
|
||||
import { toast } from 'sonner'
|
||||
import type { TemplateDetail } from '@/constants/templates'
|
||||
import { useDirectGitHub } from './useDirectGitHub'
|
||||
import { getGitHubToken } from '@/actions/github'
|
||||
|
||||
export interface TemplateDeploymentConfig {
|
||||
template: TemplateDetail
|
||||
projectName: string
|
||||
organizationSlug: string
|
||||
environmentVariables?: Array<{
|
||||
key: string
|
||||
value: string
|
||||
environments: string[]
|
||||
}>
|
||||
deployerLrn?: string
|
||||
}
|
||||
|
||||
export interface TemplateDeploymentResult {
|
||||
projectId: string
|
||||
repositoryUrl: string
|
||||
deploymentUrl?: string
|
||||
deploymentId?: string
|
||||
}
|
||||
|
||||
export function useDirectTemplateDeployment() {
|
||||
const [isDeploying, setIsDeploying] = useState(false)
|
||||
const [deploymentResult, setDeploymentResult] =
|
||||
useState<TemplateDeploymentResult | null>(null)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
|
||||
const gqlClient = useGQLClient()
|
||||
const { wallet } = useWallet()
|
||||
const { user } = useUser()
|
||||
const directGitHub = useDirectGitHub()
|
||||
|
||||
const deployTemplate = async (
|
||||
config: TemplateDeploymentConfig
|
||||
): Promise<TemplateDeploymentResult> => {
|
||||
setIsDeploying(true)
|
||||
setError(null)
|
||||
setDeploymentResult(null)
|
||||
|
||||
try {
|
||||
console.log('🚀 Starting direct template deployment:', config)
|
||||
|
||||
// Validate required data
|
||||
if (!wallet?.address) {
|
||||
throw new Error('Wallet not connected')
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
throw new Error('User not authenticated')
|
||||
}
|
||||
|
||||
// Get GitHub username from Clerk external accounts
|
||||
const githubAccount = user.externalAccounts?.find(
|
||||
(account) => account.provider === 'github'
|
||||
)
|
||||
const githubUsername = githubAccount?.username
|
||||
|
||||
if (!githubUsername) {
|
||||
throw new Error('GitHub account not connected')
|
||||
}
|
||||
|
||||
console.log('🔍 GitHub user info:', {
|
||||
githubUsername,
|
||||
githubAccount: githubAccount?.username,
|
||||
userExternalAccounts: user.externalAccounts?.length
|
||||
})
|
||||
|
||||
// Parse template repository (format: "owner/repo")
|
||||
const [templateOwner, templateRepo] =
|
||||
config.template.repoFullName.split('/')
|
||||
if (!templateOwner || !templateRepo) {
|
||||
throw new Error('Invalid template repository format')
|
||||
}
|
||||
|
||||
console.log('🔍 Template parsing details:', {
|
||||
originalTemplate: config.template.repoFullName,
|
||||
parsedOwner: templateOwner,
|
||||
parsedRepo: templateRepo,
|
||||
templateId: config.template.id,
|
||||
templateName: config.template.name
|
||||
})
|
||||
|
||||
toast.info('Creating repository from template...')
|
||||
|
||||
// STEP 1: Create repository directly via GitHub API with user's token
|
||||
const repoResult = await directGitHub.createRepoFromTemplate({
|
||||
templateOwner,
|
||||
templateRepo,
|
||||
name: config.projectName,
|
||||
description: `Created from ${config.template.name} template`,
|
||||
isPrivate: false
|
||||
})
|
||||
|
||||
if (!repoResult.success || !repoResult.repositoryUrl) {
|
||||
throw new Error(
|
||||
repoResult.error || 'Failed to create repository from template'
|
||||
)
|
||||
}
|
||||
|
||||
console.log(
|
||||
'✅ Repository created successfully:',
|
||||
repoResult.repositoryUrl
|
||||
)
|
||||
toast.success('Repository created from template!')
|
||||
|
||||
// STEP 2: Create project in backend using the newly created repository
|
||||
console.log('🔍 Preparing backend project creation...')
|
||||
console.log('🔍 Organization slug:', config.organizationSlug)
|
||||
console.log('🔍 Deployer LRN:', config.deployerLrn || 'undefined')
|
||||
console.log(
|
||||
'🔍 Environment variables:',
|
||||
config.environmentVariables || []
|
||||
)
|
||||
|
||||
toast.info('Setting up project deployment...')
|
||||
|
||||
// Add a delay to ensure the repository is fully created and accessible
|
||||
console.log('⏳ Waiting for repository to be fully accessible...')
|
||||
await new Promise((resolve) => setTimeout(resolve, 5000))
|
||||
|
||||
// STEP 2a: Verify GitHub token availability
|
||||
console.log('🔄 Verifying GitHub token for user:', githubUsername)
|
||||
const currentClerkToken = await getGitHubToken()
|
||||
if (!currentClerkToken) {
|
||||
throw new Error(
|
||||
`GitHub token not available for user ${githubUsername}. Please reconnect your GitHub account.`
|
||||
)
|
||||
}
|
||||
console.log('✅ GitHub token verified for user:', githubUsername)
|
||||
|
||||
try {
|
||||
// Get available deployers
|
||||
console.log('🔍 Fetching available deployers...')
|
||||
const deployersResult = await gqlClient.getDeployers()
|
||||
const availableDeployers = deployersResult.deployers || []
|
||||
|
||||
if (availableDeployers.length === 0) {
|
||||
throw new Error(
|
||||
'No deployers available. Please configure at least one deployer in the backend.'
|
||||
)
|
||||
}
|
||||
|
||||
// Use the first available deployer if none specified
|
||||
const deployerToUse =
|
||||
config.deployerLrn || availableDeployers[0]?.deployerLrn
|
||||
|
||||
if (!deployerToUse) {
|
||||
throw new Error('No valid deployer found')
|
||||
}
|
||||
|
||||
console.log('🔍 Using deployer:', deployerToUse)
|
||||
|
||||
// Get the backend's wallet address for blockchain transactions
|
||||
const backendAddress = await gqlClient.getAddress()
|
||||
console.log('🔍 Backend wallet address:', backendAddress)
|
||||
console.log('🔍 Frontend wallet address:', wallet.address)
|
||||
|
||||
// Use backend's address for blockchain transactions
|
||||
const projectData = {
|
||||
name: config.projectName,
|
||||
repository: `${githubUsername}/${config.projectName}`,
|
||||
prodBranch: 'main',
|
||||
template: config.template.id,
|
||||
paymentAddress: backendAddress,
|
||||
txHash:
|
||||
'0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
}
|
||||
|
||||
console.log(
|
||||
'📤 Final project data being sent:',
|
||||
JSON.stringify(projectData, null, 2)
|
||||
)
|
||||
console.log('📤 With deployer:', deployerToUse)
|
||||
console.log('📤 Organization slug:', config.organizationSlug)
|
||||
console.log(
|
||||
'📤 Environment variables:',
|
||||
config.environmentVariables || []
|
||||
)
|
||||
|
||||
// Log the exact GraphQL variables being sent
|
||||
const mutationVariables = {
|
||||
organizationSlug: config.organizationSlug,
|
||||
data: projectData,
|
||||
lrn: deployerToUse,
|
||||
auctionParams: undefined,
|
||||
environmentVariables: config.environmentVariables || []
|
||||
}
|
||||
|
||||
console.log(
|
||||
'🔍 EXACT GraphQL variables being sent:',
|
||||
JSON.stringify(mutationVariables, null, 2)
|
||||
)
|
||||
|
||||
const projectResult = await gqlClient.addProject(
|
||||
config.organizationSlug,
|
||||
projectData,
|
||||
deployerToUse,
|
||||
undefined, // auctionParams
|
||||
config.environmentVariables || []
|
||||
)
|
||||
|
||||
console.log('✅ Backend response received:', projectResult)
|
||||
console.log('🔍 Project ID:', projectResult.addProject?.id)
|
||||
console.log('🔍 Full project object:', projectResult.addProject)
|
||||
|
||||
if (!projectResult.addProject?.id) {
|
||||
console.error(
|
||||
'❌ No project ID in response. Full response:',
|
||||
projectResult
|
||||
)
|
||||
throw new Error(
|
||||
'Failed to set up project deployment in backend - no project ID returned'
|
||||
)
|
||||
}
|
||||
|
||||
console.log(
|
||||
'✅ Project created successfully with ID:',
|
||||
projectResult.addProject.id
|
||||
)
|
||||
|
||||
// Create and return the result
|
||||
const result: TemplateDeploymentResult = {
|
||||
projectId: projectResult.addProject.id,
|
||||
repositoryUrl: repoResult.repositoryUrl,
|
||||
deploymentUrl: undefined, // Will be populated once deployment completes
|
||||
deploymentId: projectResult.addProject.id
|
||||
}
|
||||
|
||||
setDeploymentResult(result)
|
||||
toast.success('Template deployed successfully!')
|
||||
|
||||
return result
|
||||
} catch (backendError) {
|
||||
console.error('❌ Backend project creation failed:', backendError)
|
||||
|
||||
// Enhanced error handling
|
||||
let errorMessage = 'Unknown deployment error'
|
||||
let errorDetails = {
|
||||
message: 'Unknown error',
|
||||
stack: undefined as string | undefined,
|
||||
name: 'UnknownError'
|
||||
}
|
||||
|
||||
if (backendError instanceof Error) {
|
||||
errorMessage = backendError.message
|
||||
errorDetails = {
|
||||
message: backendError.message,
|
||||
stack: backendError.stack,
|
||||
name: backendError.name
|
||||
}
|
||||
} else if (typeof backendError === 'object' && backendError !== null) {
|
||||
const errorObj = backendError as any
|
||||
errorMessage = errorObj.message || JSON.stringify(backendError)
|
||||
errorDetails = {
|
||||
message: errorObj.message || errorMessage,
|
||||
stack: errorObj.stack,
|
||||
name: errorObj.name || 'BackendError'
|
||||
}
|
||||
}
|
||||
|
||||
console.error('❌ Error details:', errorDetails)
|
||||
|
||||
// Provide more specific error messages
|
||||
if (
|
||||
errorMessage.includes('Cannot return null for non-nullable field')
|
||||
) {
|
||||
errorMessage = `Backend validation error: A required field is missing or invalid.
|
||||
|
||||
Possible issues:
|
||||
1. Invalid template ID: "${config.template.id}"
|
||||
2. Repository format issue: "${repoResult.repositoryUrl}"
|
||||
3. Missing deployer configuration
|
||||
4. Organization "${config.organizationSlug}" not found
|
||||
|
||||
Repository was created successfully: ${repoResult.repositoryUrl}
|
||||
You may need to check the backend configuration or contact support.`
|
||||
}
|
||||
|
||||
throw new Error(`Backend deployment setup failed: ${errorMessage}`)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ Template deployment failed:', error)
|
||||
const errorMessage =
|
||||
error instanceof Error ? error.message : 'Unknown error'
|
||||
setError(errorMessage)
|
||||
toast.error(`Template deployment failed: ${errorMessage}`)
|
||||
throw error
|
||||
} finally {
|
||||
setIsDeploying(false)
|
||||
}
|
||||
}
|
||||
|
||||
const reset = () => {
|
||||
setDeploymentResult(null)
|
||||
setError(null)
|
||||
setIsDeploying(false)
|
||||
}
|
||||
|
||||
return {
|
||||
deployTemplate,
|
||||
isDeploying: isDeploying || directGitHub.isLoading,
|
||||
deploymentResult,
|
||||
error,
|
||||
reset
|
||||
}
|
||||
}
|
69
apps/deploy-fe/src/hooks/useGitHubToken.tsx
Normal file
69
apps/deploy-fe/src/hooks/useGitHubToken.tsx
Normal file
@ -0,0 +1,69 @@
|
||||
'use client'
|
||||
|
||||
import { getGitHubToken } from '@/actions/github'
|
||||
import { useAuth } from '@clerk/nextjs'
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
interface UseGitHubTokenReturn {
|
||||
token: string | null
|
||||
isLoading: boolean
|
||||
error: string | null
|
||||
refreshToken: () => Promise<void>
|
||||
}
|
||||
|
||||
/**
|
||||
* A hook to get the current user's GitHub OAuth token from Clerk
|
||||
* This ensures each user gets their own token for GitHub API calls
|
||||
*/
|
||||
export function useGitHubToken(): UseGitHubTokenReturn {
|
||||
const [token, setToken] = useState<string | null>(null)
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
|
||||
const { isLoaded: isAuthLoaded, userId } = useAuth()
|
||||
|
||||
const fetchToken = async () => {
|
||||
if (!userId) {
|
||||
setError('User not authenticated')
|
||||
setIsLoading(false)
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
setIsLoading(true)
|
||||
setError(null)
|
||||
|
||||
const userToken = await getGitHubToken()
|
||||
|
||||
if (!userToken) {
|
||||
setError(
|
||||
'GitHub account not connected. Please connect your GitHub account.'
|
||||
)
|
||||
setToken(null)
|
||||
} else {
|
||||
console.log('✅ Successfully retrieved user-specific GitHub token')
|
||||
setToken(userToken)
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('❌ Error getting GitHub token:', err)
|
||||
setError(
|
||||
err instanceof Error ? err.message : 'Failed to get GitHub token'
|
||||
)
|
||||
setToken(null)
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (isAuthLoaded) {
|
||||
fetchToken()
|
||||
}
|
||||
}, [isAuthLoaded, userId])
|
||||
|
||||
const refreshToken = async () => {
|
||||
await fetchToken()
|
||||
}
|
||||
|
||||
return { token, isLoading, error, refreshToken }
|
||||
}
|
@ -3,6 +3,7 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { useAuth, useUser } from "@clerk/nextjs";
|
||||
import { Octokit } from "@octokit/rest";
|
||||
import { getGitHubToken } from "@/actions/github";
|
||||
|
||||
// Define the return type of the hook
|
||||
interface UseRepoDataReturn {
|
||||
@ -43,7 +44,7 @@ export function useRepoData(repoId: string): UseRepoDataReturn {
|
||||
if (githubAccount) {
|
||||
// Try to get GitHub OAuth token from Clerk
|
||||
try {
|
||||
// token = await user.getToken({ template: 'github' });
|
||||
token = await getGitHubToken();
|
||||
console.log('Using GitHub token from Clerk');
|
||||
} catch (err) {
|
||||
console.error('Error getting GitHub token from Clerk:', err);
|
||||
|
Loading…
Reference in New Issue
Block a user