Removed flickering and updated form entry

This commit is contained in:
NasSharaf 2025-09-02 00:35:20 -04:00
parent ce851ef883
commit 3a2a6dca7e
6 changed files with 294 additions and 181 deletions

View File

@ -1,7 +1,7 @@
// src/components/onboarding/configure-step/configure-step.tsx // src/components/onboarding/configure-step/configure-step.tsx
'use client' 'use client'
import { useOnboarding } from '@/components/onboarding/useOnboarding' import { useOnboarding } from '@/components/onboarding/store'
import { useGQLClient } from '@/context' import { useGQLClient } from '@/context'
import { useWallet } from '@/context/WalletContext' import { useWallet } from '@/context/WalletContext'
import { Alert, AlertDescription } from '@workspace/ui/components/alert' import { Alert, AlertDescription } from '@workspace/ui/components/alert'
@ -53,19 +53,13 @@ export function ConfigureStep() {
const [isLoadingDeployers, setIsLoadingDeployers] = useState(true) const [isLoadingDeployers, setIsLoadingDeployers] = useState(true)
const [isLoadingOrgs, setIsLoadingOrgs] = useState(true) const [isLoadingOrgs, setIsLoadingOrgs] = useState(true)
// Form state // Form state - using local state since these aren't in the simplified store
const [deployOption, setDeployOption] = useState<'auction' | 'lrn'>( const [deployOption, setDeployOption] = useState<'auction' | 'lrn'>('lrn') // Default to LRN for simplicity
(formData.deploymentType as 'auction' | 'lrn') || 'lrn' // Default to LRN for simplicity const [numberOfDeployers, setNumberOfDeployers] = useState<string>('1')
) const [maxPrice, setMaxPrice] = useState<string>('1000')
const [numberOfDeployers, setNumberOfDeployers] = useState<string>( const [selectedLrn, setSelectedLrn] = useState<string>('')
formData.deployerCount || '1'
)
const [maxPrice, setMaxPrice] = useState<string>(formData.maxPrice || '1000')
const [selectedLrn, setSelectedLrn] = useState<string>(
formData.selectedLrn || ''
)
const [selectedOrg, setSelectedOrg] = useState<string>( const [selectedOrg, setSelectedOrg] = useState<string>(
formData.selectedOrg || '' formData.organizationSlug || ''
) )
const [envVars, setEnvVars] = useState< const [envVars, setEnvVars] = useState<
{ key: string; value: string; environments: string[] }[] { key: string; value: string; environments: string[] }[]
@ -88,19 +82,8 @@ export function ConfigureStep() {
} }
}, [mounted]) }, [mounted])
// Initialize environment variables from formData if available // Environment variables are managed locally
useEffect(() => { // (Removed environment variables initialization since not in simple store)
if (
formData.environmentVariables &&
Array.isArray(formData.environmentVariables)
) {
setEnvVars(
formData.environmentVariables.length > 0
? formData.environmentVariables
: [{ key: '', value: '', environments: ['Production'] }]
)
}
}, [formData.environmentVariables])
// Fetch deployers from backend // Fetch deployers from backend
const fetchDeployers = async () => { const fetchDeployers = async () => {
@ -231,12 +214,12 @@ export function ConfigureStep() {
// Save configuration to form data // Save configuration to form data
setFormData({ setFormData({
deploymentType: deployOption, organizationSlug: selectedOrg,
deployerCount: numberOfDeployers,
maxPrice: maxPrice,
selectedLrn: selectedLrn,
selectedOrg: selectedOrg, selectedOrg: selectedOrg,
paymentAddress: wallet?.address, selectedLrn: selectedLrn,
deploymentType: deployOption,
maxPrice: maxPrice,
deployerCount: numberOfDeployers,
environmentVariables: validEnvVars environmentVariables: validEnvVars
}) })
@ -251,11 +234,9 @@ export function ConfigureStep() {
// Determine if dark mode is active // Determine if dark mode is active
const isDarkMode = resolvedTheme === 'dark' const isDarkMode = resolvedTheme === 'dark'
// Get deployment mode info // Get deployment mode info - determine from available data
const isTemplateMode = formData.deploymentMode === 'template' const isTemplateMode = !!formData.framework && !formData.repoName
const selectedItem = isTemplateMode const selectedItem = isTemplateMode ? formData.framework : formData.repoName
? formData.template?.name
: formData.githubRepo
return ( return (
<div className="w-full h-full flex flex-col p-8 overflow-y-auto"> <div className="w-full h-full flex flex-col p-8 overflow-y-auto">

View File

@ -2,7 +2,7 @@
'use client' 'use client'
import { GitHubBackendAuth } from '@/components/GitHubBackendAuth' import { GitHubBackendAuth } from '@/components/GitHubBackendAuth'
import { useOnboarding } from '@/components/onboarding/useOnboarding' import { useOnboarding } from '@/components/onboarding/store'
import { AVAILABLE_TEMPLATES, type TemplateDetail } from '@/constants/templates' import { AVAILABLE_TEMPLATES, type TemplateDetail } from '@/constants/templates'
import { useAuthStatus } from '@/hooks/useAuthStatus' import { useAuthStatus } from '@/hooks/useAuthStatus'
import { useRepoData } from '@/hooks/useRepoData' import { useRepoData } from '@/hooks/useRepoData'
@ -46,10 +46,12 @@ export function ConnectStep() {
// Repository vs Template selection // Repository vs Template selection
const [selectedRepo, setSelectedRepo] = useState<string>( const [selectedRepo, setSelectedRepo] = useState<string>(
formData.githubRepo || '' formData.repoName || ''
) )
const [selectedTemplate, setSelectedTemplate] = useState( const [selectedTemplate, setSelectedTemplate] = useState<
adaptOptionalTemplate(formData.template) TemplateDetail | undefined
>(
undefined // We'll simplify template handling
) )
const [projectName, setProjectName] = useState<string>( const [projectName, setProjectName] = useState<string>(
formData.projectName || '' formData.projectName || ''
@ -90,9 +92,10 @@ export function ConnectStep() {
setSelectedRepo(repo) setSelectedRepo(repo)
setSelectedTemplate(undefined) setSelectedTemplate(undefined)
setFormData({ setFormData({
githubRepo: repo, repoName: repo,
template: undefined, githubRepo: repo, // Store repo path for deploy step
deploymentMode: 'repository', template: undefined, // Clear template selection
framework: '', // Clear framework
projectName projectName
}) })
} }
@ -107,9 +110,10 @@ export function ConnectStep() {
setProjectName(suggestedName) setProjectName(suggestedName)
} }
setFormData({ setFormData({
template: template, framework: template.name, // Keep for backwards compatibility
githubRepo: '', template: template, // Store the full template object
deploymentMode: 'template', githubRepo: '', // Clear repo selection
repoName: '',
projectName: projectName:
projectName || projectName ||
`my-${template.name.toLowerCase().replace(/[^a-z0-9]/g, '-')}` `my-${template.name.toLowerCase().replace(/[^a-z0-9]/g, '-')}`
@ -123,15 +127,17 @@ export function ConnectStep() {
if (mode === 'import') { if (mode === 'import') {
setSelectedTemplate(undefined) setSelectedTemplate(undefined)
setFormData({ setFormData({
framework: '',
template: undefined, template: undefined,
deploymentMode: 'repository', githubRepo: '',
projectName projectName
}) })
} else { } else {
setSelectedRepo('') setSelectedRepo('')
setFormData({ setFormData({
repoName: '',
githubRepo: '', githubRepo: '',
deploymentMode: 'template', template: undefined,
projectName projectName
}) })
} }
@ -186,9 +192,8 @@ export function ConnectStep() {
// Set final form data and proceed // Set final form data and proceed
setFormData({ setFormData({
deploymentMode: isImportMode ? 'repository' : 'template', repoName: isImportMode ? selectedRepo : '',
githubRepo: isImportMode ? selectedRepo : '', framework: !isImportMode ? selectedTemplate?.name || '' : '',
template: !isImportMode ? (selectedTemplate as Template) : undefined,
projectName: finalProjectName projectName: finalProjectName
}) })

View File

@ -18,6 +18,7 @@ import type { OnboardingFormData, Step } from './types'
* @property {(data: Partial<OnboardingFormData>) => void} setFormData - Updates form data * @property {(data: Partial<OnboardingFormData>) => void} setFormData - Updates form data
* @property {() => void} nextStep - Moves to the next step * @property {() => void} nextStep - Moves to the next step
* @property {() => void} previousStep - Moves to the previous step * @property {() => void} previousStep - Moves to the previous step
* @property {() => void} resetOnboarding - Resets the onboarding state to initial values
*/ */
export interface OnboardingState { export interface OnboardingState {
currentStep: Step currentStep: Step
@ -26,11 +27,34 @@ export interface OnboardingState {
setFormData: (data: Partial<OnboardingFormData>) => void setFormData: (data: Partial<OnboardingFormData>) => void
nextStep: () => void nextStep: () => void
previousStep: () => void previousStep: () => void
resetOnboarding: () => void
} }
/** Order of steps in the onboarding flow */ /** Order of steps in the onboarding flow */
const STEP_ORDER: Step[] = ['connect', 'configure', 'deploy'] const STEP_ORDER: Step[] = ['connect', 'configure', 'deploy']
/** Initial form data values */
const initialFormData: OnboardingFormData = {
projectName: '',
repoName: '',
repoDescription: '',
framework: '',
access: 'public',
organizationSlug: '',
template: undefined,
githubRepo: '',
selectedOrg: '',
environmentVariables: [],
selectedLrn: '',
deploymentType: 'lrn',
maxPrice: '1000',
deployerCount: '1',
deploymentId: undefined,
deploymentUrl: undefined,
projectId: undefined,
repositoryUrl: undefined
}
/** /**
* Zustand store for managing onboarding state * Zustand store for managing onboarding state
* Used across all onboarding components to maintain flow state * Used across all onboarding components to maintain flow state
@ -42,14 +66,7 @@ const STEP_ORDER: Step[] = ['connect', 'configure', 'deploy']
*/ */
export const useOnboarding = create<OnboardingState>((set) => ({ export const useOnboarding = create<OnboardingState>((set) => ({
currentStep: 'connect', currentStep: 'connect',
formData: { formData: initialFormData,
projectName: '',
repoName: '',
repoDescription: '',
framework: '',
access: 'public',
organizationSlug: ''
},
setCurrentStep: (step) => set({ currentStep: step }), setCurrentStep: (step) => set({ currentStep: step }),
setFormData: (data) => setFormData: (data) =>
set((state) => ({ set((state) => ({
@ -66,5 +83,6 @@ export const useOnboarding = create<OnboardingState>((set) => ({
const currentIndex = STEP_ORDER.indexOf(state.currentStep) const currentIndex = STEP_ORDER.indexOf(state.currentStep)
const previousStep = STEP_ORDER[currentIndex - 1] const previousStep = STEP_ORDER[currentIndex - 1]
return previousStep ? { currentStep: previousStep } : state return previousStep ? { currentStep: previousStep } : state
}) }),
resetOnboarding: () => set({ currentStep: 'connect', formData: initialFormData })
})) }))

View File

@ -18,6 +18,18 @@ export type Step = 'connect' | 'configure' | 'deploy' | 'success'
* @property {string} framework - Framework used for the project * @property {string} framework - Framework used for the project
* @property {string} access - Access level of the repository * @property {string} access - Access level of the repository
* @property {string} organizationSlug - Organization slug * @property {string} organizationSlug - Organization slug
* @property {Template | undefined} template - Selected template for deployment
* @property {string} githubRepo - GitHub repository path (owner/repo)
* @property {string} selectedOrg - Selected organization for deployment
* @property {EnvironmentVariable[]} environmentVariables - Environment variables for deployment
* @property {string} selectedLrn - Selected LRN for deployment
* @property {string} deploymentType - Type of deployment (lrn, wallet, etc.)
* @property {string} maxPrice - Maximum price for deployment
* @property {string} deployerCount - Number of deployers
* @property {string} deploymentId - ID of the deployment after creation
* @property {string} deploymentUrl - URL of the deployed project
* @property {string} projectId - ID of the created project
* @property {string} repositoryUrl - URL of the repository
*/ */
export interface OnboardingFormData { export interface OnboardingFormData {
projectName: string projectName: string
@ -26,6 +38,18 @@ export interface OnboardingFormData {
framework: string framework: string
access: 'public' | 'private' access: 'public' | 'private'
organizationSlug: string organizationSlug: string
template?: Template
githubRepo: string
selectedOrg: string
environmentVariables: EnvironmentVariable[]
selectedLrn: string
deploymentType: string
maxPrice: string
deployerCount: string
deploymentId?: string
deploymentUrl?: string
projectId?: string
repositoryUrl?: string
} }
/** /**
@ -54,12 +78,16 @@ export interface Repository {
* @property {string} name - Template name * @property {string} name - Template name
* @property {string} [description] - Template description * @property {string} [description] - Template description
* @property {string} [thumbnail] - Template thumbnail URL * @property {string} [thumbnail] - Template thumbnail URL
* @property {string} [repoFullName] - Full repository name for the template
* @property {any} [icon] - Template icon
*/ */
export interface Template { export interface Template {
id: string id: string
name: string name: string
description?: string description?: string
thumbnail?: string thumbnail?: string
repoFullName?: string
icon?: any
} }
/** /**
@ -81,9 +109,11 @@ export interface DeploymentType {
* @property {string} key - Environment variable key * @property {string} key - Environment variable key
* @property {string} value - Environment variable value * @property {string} value - Environment variable value
* @property {boolean} [isSecret] - Whether the variable is a secret * @property {boolean} [isSecret] - Whether the variable is a secret
* @property {string[]} environments - Environment names where this variable applies
*/ */
export interface EnvironmentVariable { export interface EnvironmentVariable {
key: string key: string
value: string value: string
isSecret?: boolean isSecret?: boolean
environments: string[]
} }

View File

@ -1,11 +1,11 @@
// src/hooks/useAuthStatus.tsx // src/hooks/useAuthStatus.tsx
'use client' 'use client'
import { useAuth, useUser } from '@clerk/nextjs'
import { useWallet } from '@/context/WalletContext' // Use the full provider!
import { useBackend } from '@/context/BackendContext'
import { useGQLClient } from '@/context' import { useGQLClient } from '@/context'
import { useState, useEffect, useCallback } from 'react' import { useBackend } from '@/context/BackendContext'
import { useWallet } from '@/context/WalletContext' // Use the full provider!
import { useAuth, useUser } from '@clerk/nextjs'
import { useCallback, useEffect, useRef, useState } from 'react'
/** /**
* @interface AuthStatus * @interface AuthStatus
@ -95,13 +95,30 @@ export function useAuthStatus(): AuthStatus & AuthActions {
// GitHub backend auth state // GitHub backend auth state
const [isGithubBackendAuth, setIsGithubBackendAuth] = useState(false) const [isGithubBackendAuth, setIsGithubBackendAuth] = useState(false)
const [isCheckingGithubAuth, setIsCheckingGithubAuth] = useState(false) const [isCheckingGithubAuth, setIsCheckingGithubAuth] = useState(false)
const lastGithubCheckRef = useRef(0)
const isCheckingRef = useRef(false)
// Stable status to prevent rapid UI changes
const [stableAuthStatus, setStableAuthStatus] = useState({
isFullyAuthenticated: false,
lastUpdate: 0
})
// Check GitHub backend auth via GraphQL // Check GitHub backend auth via GraphQL
const checkGithubBackendAuth = useCallback(async (): Promise<boolean> => { const checkGithubBackendAuth = useCallback(async (): Promise<boolean> => {
if (!isBackendConnected) return false if (!isBackendConnected) return false
// Prevent multiple rapid calls - only allow once every 3 seconds
const now = Date.now()
if (isCheckingRef.current || (now - lastGithubCheckRef.current < 3000)) {
return isGithubBackendAuth
}
try { try {
isCheckingRef.current = true
setIsCheckingGithubAuth(true) setIsCheckingGithubAuth(true)
lastGithubCheckRef.current = now
const userData = await gqlClient.getUser() const userData = await gqlClient.getUser()
const hasGitHubToken = !!userData.user.gitHubToken const hasGitHubToken = !!userData.user.gitHubToken
setIsGithubBackendAuth(hasGitHubToken) setIsGithubBackendAuth(hasGitHubToken)
@ -111,9 +128,10 @@ export function useAuthStatus(): AuthStatus & AuthActions {
setIsGithubBackendAuth(false) setIsGithubBackendAuth(false)
return false return false
} finally { } finally {
isCheckingRef.current = false
setIsCheckingGithubAuth(false) setIsCheckingGithubAuth(false)
} }
}, [isBackendConnected, gqlClient]) }, [isBackendConnected, isGithubBackendAuth]) // Minimal dependencies
// Check GitHub auth when backend connection changes // Check GitHub auth when backend connection changes
useEffect(() => { useEffect(() => {
@ -122,7 +140,7 @@ export function useAuthStatus(): AuthStatus & AuthActions {
} else { } else {
setIsGithubBackendAuth(false) setIsGithubBackendAuth(false)
} }
}, [isBackendConnected, checkGithubBackendAuth]) }, [isBackendConnected]) // Remove checkGithubBackendAuth from dependencies to prevent infinite loop
// Check backend connection when wallet session is active (SIWE completed) // Check backend connection when wallet session is active (SIWE completed)
useEffect(() => { useEffect(() => {
@ -133,11 +151,14 @@ export function useAuthStatus(): AuthStatus & AuthActions {
}, 1000) }, 1000)
return () => clearTimeout(timer) return () => clearTimeout(timer)
} }
}, [isWalletSessionActive, refreshBackendStatus]) }, [isWalletSessionActive]) // Remove refreshBackendStatus from dependencies to prevent rapid retriggers
// Check if GitHub is connected in Clerk // Check if GitHub is connected in Clerk
const hasGithubInClerk = user?.externalAccounts?.find( const hasGithubInClerk =
account => account.provider === 'github' || account.verification?.strategy === 'oauth_github' user?.externalAccounts?.find(
(account) =>
account.provider === 'github' ||
account.verification?.strategy === 'oauth_github'
) !== undefined ) !== undefined
// Calculate what's missing // Calculate what's missing
@ -163,10 +184,28 @@ export function useAuthStatus(): AuthStatus & AuthActions {
const progressPercentage = Math.round((completedSteps / totalSteps) * 100) const progressPercentage = Math.round((completedSteps / totalSteps) * 100)
// Determine if fully authenticated // Determine if fully authenticated
const isFullyAuthenticated = authSteps.every(Boolean) const currentIsFullyAuthenticated = authSteps.every(Boolean)
// Debounce authentication status changes to prevent flickering
useEffect(() => {
const now = Date.now()
const timeSinceLastUpdate = now - stableAuthStatus.lastUpdate
// Only update if status actually changed and enough time has passed (300ms debounce)
if (currentIsFullyAuthenticated !== stableAuthStatus.isFullyAuthenticated && timeSinceLastUpdate > 300) {
setStableAuthStatus({
isFullyAuthenticated: currentIsFullyAuthenticated,
lastUpdate: now
})
}
}, [currentIsFullyAuthenticated, stableAuthStatus])
// Use stable status for UI
const isFullyAuthenticated = stableAuthStatus.isFullyAuthenticated
// Determine if ready (all auth systems loaded) // Determine if ready (all auth systems loaded)
const isReady = isClerkLoaded && isUserLoaded && !isBackendLoading && !isCheckingGithubAuth const isReady =
isClerkLoaded && isUserLoaded && !isBackendLoading && !isCheckingGithubAuth
// Combined refresh action // Combined refresh action
const refreshAllStatus = async () => { const refreshAllStatus = async () => {

View File

@ -1,15 +1,15 @@
"use client"; 'use client'
import { useState, useEffect } from "react"; import { getGitHubToken } from '@/actions/github'
import { useAuth, useUser } from "@clerk/nextjs"; import { useAuth, useUser } from '@clerk/nextjs'
import { Octokit } from "@octokit/rest"; import { Octokit } from '@octokit/rest'
import { getGitHubToken } from "@/actions/github"; import { useEffect, useState } from 'react'
// Define the return type of the hook // Define the return type of the hook
interface UseRepoDataReturn { interface UseRepoDataReturn {
repoData: any; repoData: any
isLoading: boolean; isLoading: boolean
error: string | null; error: string | null
} }
/** /**
@ -19,121 +19,161 @@ interface UseRepoDataReturn {
* @returns Object containing repository data, loading state, and any errors * @returns Object containing repository data, loading state, and any errors
*/ */
export function useRepoData(repoId: string): UseRepoDataReturn { export function useRepoData(repoId: string): UseRepoDataReturn {
const [repoData, setRepoData] = useState<any>(null); const [repoData, setRepoData] = useState<any>(null)
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(true)
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null)
const [octokit, setOctokit] = useState<Octokit | null>(null); const [octokit, setOctokit] = useState<Octokit | null>(null)
// Get auth data from Clerk // Get auth data from Clerk
const { isLoaded: isAuthLoaded } = useAuth(); const { isLoaded: isAuthLoaded, getToken, isSignedIn } = useAuth()
const { isLoaded: isUserLoaded, user } = useUser(); const { isLoaded: isUserLoaded, user } = useUser()
// Initialize Octokit with the appropriate token // Initialize Octokit with the appropriate token
useEffect(() => { useEffect(() => {
async function initializeOctokit() { async function initializeOctokit() {
let token = null; let token = null
// Try to get GitHub token from Clerk // Try to get GitHub OAuth token from Clerk using multiple methods
if (user) { if (isSignedIn && user) {
try { try {
// Check if user has connected GitHub account // Check if user has connected GitHub account
const githubAccount = user.externalAccounts.find( const githubAccount = user.externalAccounts.find(
account => account.provider === 'github' (account) => account.provider === 'github'
); )
if (githubAccount) { if (githubAccount) {
// Try to get GitHub OAuth token from Clerk // Try multiple methods to get the GitHub OAuth token (same as test-connection page)
// Method 1: Try getToken from useAuth hook
try { try {
token = await getGitHubToken(); token = await getToken()
console.log('Using GitHub token from Clerk'); console.log('Method 1 (getToken from useAuth) worked:', token ? 'SUCCESS' : 'NO TOKEN')
} catch (err) { } catch (error) {
console.error('Error getting GitHub token from Clerk:', err); 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) { } catch (err) {
console.error('Error accessing Clerk user data:', err); console.error('Error accessing Clerk user data:', err)
} }
} }
// Fallback to token from environment variable // Fallback to token from environment variable
if (!token && typeof process !== 'undefined') { if (!token) {
token = process.env.NEXT_PUBLIC_GITHUB_FALLBACK_TOKEN || ''; token = process.env.NEXT_PUBLIC_GITHUB_FALLBACK_TOKEN || ''
if (token) { if (token) {
console.warn('Using fallback GitHub token. This should only be used for development.'); console.warn(
'Using fallback GitHub token. This should only be used for development.'
)
} }
} }
// Create Octokit instance with whatever token we found // Create Octokit instance with whatever token we found
if (token) { if (token) {
setOctokit(new Octokit({ auth: token })); setOctokit(new Octokit({ auth: token }))
} else { } else {
setError("No GitHub token available"); setError('No GitHub token available')
setIsLoading(false); setIsLoading(false)
} }
} }
if (isAuthLoaded && isUserLoaded) { if (isAuthLoaded && isUserLoaded) {
initializeOctokit(); initializeOctokit()
} }
}, [isAuthLoaded, isUserLoaded, user]); }, [isAuthLoaded, isUserLoaded, user])
// Fetch repo data when Octokit is available // Fetch repo data when Octokit is available
useEffect(() => { useEffect(() => {
let isMounted = true; let isMounted = true
async function fetchRepoData() { async function fetchRepoData() {
if (!octokit) { if (!octokit) {
return; return
} }
try { try {
// Fetch repos from GitHub // Fetch repos from GitHub
const { data: repos } = await octokit.repos.listForAuthenticatedUser(); const { data: repos } = await octokit.repos.listForAuthenticatedUser()
// If no repoId is provided, return all repos // If no repoId is provided, return all repos
if (!repoId) { if (!repoId) {
if (isMounted) { if (isMounted) {
setRepoData(repos); setRepoData(repos)
setError(null); setError(null)
setIsLoading(false); setIsLoading(false)
} }
return; return
} }
// Find the specific repo by ID if repoId is provided // Find the specific repo by ID if repoId is provided
const repo = repos.find(repo => repo.id.toString() === repoId); const repo = repos.find((repo) => repo.id.toString() === repoId)
if (!repo) { if (!repo) {
if (isMounted) { if (isMounted) {
setError("Repository not found"); setError('Repository not found')
setRepoData(null); setRepoData(null)
setIsLoading(false); setIsLoading(false)
} }
} else { } else {
if (isMounted) { if (isMounted) {
setRepoData(repo); setRepoData(repo)
setError(null); setError(null)
setIsLoading(false); setIsLoading(false)
} }
} }
} catch (err) { } catch (err) {
console.error('Error fetching GitHub repo:', err); console.error('Error fetching GitHub repo:', err)
if (isMounted) { if (isMounted) {
setError('Failed to fetch repository data'); setError('Failed to fetch repository data')
setRepoData(null); setRepoData(null)
setIsLoading(false); setIsLoading(false)
} }
} }
} }
if (octokit) { if (octokit) {
fetchRepoData(); fetchRepoData()
} }
return () => { return () => {
isMounted = false; isMounted = false
}; }
}, [repoId, octokit]); }, [repoId, octokit])
return { repoData, isLoading, error }; return { repoData, isLoading, error }
} }