From 3a2a6dca7e201a32f5a82d4a05836ceedc48b2ee Mon Sep 17 00:00:00 2001 From: NasSharaf Date: Tue, 2 Sep 2025 00:35:20 -0400 Subject: [PATCH] Removed flickering and updated form entry --- .../configure-step/configure-step.tsx | 53 ++--- .../onboarding/connect-step/connect-step.tsx | 35 ++-- .../src/components/onboarding/store.ts | 36 +++- .../src/components/onboarding/types.ts | 30 +++ apps/deploy-fe/src/hooks/useAuthStatus.tsx | 133 ++++++++----- apps/deploy-fe/src/hooks/useRepoData.tsx | 188 +++++++++++------- 6 files changed, 294 insertions(+), 181 deletions(-) diff --git a/apps/deploy-fe/src/components/onboarding/configure-step/configure-step.tsx b/apps/deploy-fe/src/components/onboarding/configure-step/configure-step.tsx index bbf2f62..201d89f 100644 --- a/apps/deploy-fe/src/components/onboarding/configure-step/configure-step.tsx +++ b/apps/deploy-fe/src/components/onboarding/configure-step/configure-step.tsx @@ -1,7 +1,7 @@ // src/components/onboarding/configure-step/configure-step.tsx 'use client' -import { useOnboarding } from '@/components/onboarding/useOnboarding' +import { useOnboarding } from '@/components/onboarding/store' import { useGQLClient } from '@/context' import { useWallet } from '@/context/WalletContext' import { Alert, AlertDescription } from '@workspace/ui/components/alert' @@ -53,19 +53,13 @@ export function ConfigureStep() { const [isLoadingDeployers, setIsLoadingDeployers] = useState(true) const [isLoadingOrgs, setIsLoadingOrgs] = useState(true) - // Form state - const [deployOption, setDeployOption] = useState<'auction' | 'lrn'>( - (formData.deploymentType as 'auction' | 'lrn') || 'lrn' // Default to LRN for simplicity - ) - const [numberOfDeployers, setNumberOfDeployers] = useState( - formData.deployerCount || '1' - ) - const [maxPrice, setMaxPrice] = useState(formData.maxPrice || '1000') - const [selectedLrn, setSelectedLrn] = useState( - formData.selectedLrn || '' - ) + // Form state - using local state since these aren't in the simplified store + const [deployOption, setDeployOption] = useState<'auction' | 'lrn'>('lrn') // Default to LRN for simplicity + const [numberOfDeployers, setNumberOfDeployers] = useState('1') + const [maxPrice, setMaxPrice] = useState('1000') + const [selectedLrn, setSelectedLrn] = useState('') const [selectedOrg, setSelectedOrg] = useState( - formData.selectedOrg || '' + formData.organizationSlug || '' ) const [envVars, setEnvVars] = useState< { key: string; value: string; environments: string[] }[] @@ -88,19 +82,8 @@ export function ConfigureStep() { } }, [mounted]) - // Initialize environment variables from formData if available - useEffect(() => { - if ( - formData.environmentVariables && - Array.isArray(formData.environmentVariables) - ) { - setEnvVars( - formData.environmentVariables.length > 0 - ? formData.environmentVariables - : [{ key: '', value: '', environments: ['Production'] }] - ) - } - }, [formData.environmentVariables]) + // Environment variables are managed locally + // (Removed environment variables initialization since not in simple store) // Fetch deployers from backend const fetchDeployers = async () => { @@ -231,12 +214,12 @@ export function ConfigureStep() { // Save configuration to form data setFormData({ - deploymentType: deployOption, - deployerCount: numberOfDeployers, - maxPrice: maxPrice, - selectedLrn: selectedLrn, + organizationSlug: selectedOrg, selectedOrg: selectedOrg, - paymentAddress: wallet?.address, + selectedLrn: selectedLrn, + deploymentType: deployOption, + maxPrice: maxPrice, + deployerCount: numberOfDeployers, environmentVariables: validEnvVars }) @@ -251,11 +234,9 @@ export function ConfigureStep() { // Determine if dark mode is active const isDarkMode = resolvedTheme === 'dark' - // Get deployment mode info - const isTemplateMode = formData.deploymentMode === 'template' - const selectedItem = isTemplateMode - ? formData.template?.name - : formData.githubRepo + // Get deployment mode info - determine from available data + const isTemplateMode = !!formData.framework && !formData.repoName + const selectedItem = isTemplateMode ? formData.framework : formData.repoName return (
diff --git a/apps/deploy-fe/src/components/onboarding/connect-step/connect-step.tsx b/apps/deploy-fe/src/components/onboarding/connect-step/connect-step.tsx index bfd2382..0f9d5e3 100644 --- a/apps/deploy-fe/src/components/onboarding/connect-step/connect-step.tsx +++ b/apps/deploy-fe/src/components/onboarding/connect-step/connect-step.tsx @@ -2,7 +2,7 @@ 'use client' 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 { useAuthStatus } from '@/hooks/useAuthStatus' import { useRepoData } from '@/hooks/useRepoData' @@ -46,10 +46,12 @@ export function ConnectStep() { // Repository vs Template selection const [selectedRepo, setSelectedRepo] = useState( - formData.githubRepo || '' + formData.repoName || '' ) - const [selectedTemplate, setSelectedTemplate] = useState( - adaptOptionalTemplate(formData.template) + const [selectedTemplate, setSelectedTemplate] = useState< + TemplateDetail | undefined + >( + undefined // We'll simplify template handling ) const [projectName, setProjectName] = useState( formData.projectName || '' @@ -90,9 +92,10 @@ export function ConnectStep() { setSelectedRepo(repo) setSelectedTemplate(undefined) setFormData({ - githubRepo: repo, - template: undefined, - deploymentMode: 'repository', + repoName: repo, + githubRepo: repo, // Store repo path for deploy step + template: undefined, // Clear template selection + framework: '', // Clear framework projectName }) } @@ -107,9 +110,10 @@ export function ConnectStep() { setProjectName(suggestedName) } setFormData({ - template: template, - githubRepo: '', - deploymentMode: 'template', + framework: template.name, // Keep for backwards compatibility + template: template, // Store the full template object + githubRepo: '', // Clear repo selection + repoName: '', projectName: projectName || `my-${template.name.toLowerCase().replace(/[^a-z0-9]/g, '-')}` @@ -123,15 +127,17 @@ export function ConnectStep() { if (mode === 'import') { setSelectedTemplate(undefined) setFormData({ + framework: '', template: undefined, - deploymentMode: 'repository', + githubRepo: '', projectName }) } else { setSelectedRepo('') setFormData({ + repoName: '', githubRepo: '', - deploymentMode: 'template', + template: undefined, projectName }) } @@ -186,9 +192,8 @@ export function ConnectStep() { // Set final form data and proceed setFormData({ - deploymentMode: isImportMode ? 'repository' : 'template', - githubRepo: isImportMode ? selectedRepo : '', - template: !isImportMode ? (selectedTemplate as Template) : undefined, + repoName: isImportMode ? selectedRepo : '', + framework: !isImportMode ? selectedTemplate?.name || '' : '', projectName: finalProjectName }) diff --git a/apps/deploy-fe/src/components/onboarding/store.ts b/apps/deploy-fe/src/components/onboarding/store.ts index 69a4793..28b605a 100644 --- a/apps/deploy-fe/src/components/onboarding/store.ts +++ b/apps/deploy-fe/src/components/onboarding/store.ts @@ -18,6 +18,7 @@ import type { OnboardingFormData, Step } from './types' * @property {(data: Partial) => void} setFormData - Updates form data * @property {() => void} nextStep - Moves to the next step * @property {() => void} previousStep - Moves to the previous step + * @property {() => void} resetOnboarding - Resets the onboarding state to initial values */ export interface OnboardingState { currentStep: Step @@ -26,11 +27,34 @@ export interface OnboardingState { setFormData: (data: Partial) => void nextStep: () => void previousStep: () => void + resetOnboarding: () => void } /** Order of steps in the onboarding flow */ 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 * Used across all onboarding components to maintain flow state @@ -42,14 +66,7 @@ const STEP_ORDER: Step[] = ['connect', 'configure', 'deploy'] */ export const useOnboarding = create((set) => ({ currentStep: 'connect', - formData: { - projectName: '', - repoName: '', - repoDescription: '', - framework: '', - access: 'public', - organizationSlug: '' - }, + formData: initialFormData, setCurrentStep: (step) => set({ currentStep: step }), setFormData: (data) => set((state) => ({ @@ -66,5 +83,6 @@ export const useOnboarding = create((set) => ({ const currentIndex = STEP_ORDER.indexOf(state.currentStep) const previousStep = STEP_ORDER[currentIndex - 1] return previousStep ? { currentStep: previousStep } : state - }) + }), + resetOnboarding: () => set({ currentStep: 'connect', formData: initialFormData }) })) diff --git a/apps/deploy-fe/src/components/onboarding/types.ts b/apps/deploy-fe/src/components/onboarding/types.ts index a153502..033a9ad 100644 --- a/apps/deploy-fe/src/components/onboarding/types.ts +++ b/apps/deploy-fe/src/components/onboarding/types.ts @@ -18,6 +18,18 @@ export type Step = 'connect' | 'configure' | 'deploy' | 'success' * @property {string} framework - Framework used for the project * @property {string} access - Access level of the repository * @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 { projectName: string @@ -26,6 +38,18 @@ export interface OnboardingFormData { framework: string access: 'public' | 'private' 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} [description] - Template description * @property {string} [thumbnail] - Template thumbnail URL + * @property {string} [repoFullName] - Full repository name for the template + * @property {any} [icon] - Template icon */ export interface Template { id: string name: string description?: string thumbnail?: string + repoFullName?: string + icon?: any } /** @@ -81,9 +109,11 @@ export interface DeploymentType { * @property {string} key - Environment variable key * @property {string} value - Environment variable value * @property {boolean} [isSecret] - Whether the variable is a secret + * @property {string[]} environments - Environment names where this variable applies */ export interface EnvironmentVariable { key: string value: string isSecret?: boolean + environments: string[] } diff --git a/apps/deploy-fe/src/hooks/useAuthStatus.tsx b/apps/deploy-fe/src/hooks/useAuthStatus.tsx index 91fae25..4174a99 100644 --- a/apps/deploy-fe/src/hooks/useAuthStatus.tsx +++ b/apps/deploy-fe/src/hooks/useAuthStatus.tsx @@ -1,11 +1,11 @@ // src/hooks/useAuthStatus.tsx '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 { 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 @@ -20,8 +20,8 @@ export interface AuthStatus { user: any } wallet: { - isConnected: boolean // SIWE authenticated + backend session - hasAddress: boolean // Just has wallet address + isConnected: boolean // SIWE authenticated + backend session + hasAddress: boolean // Just has wallet address wallet: any } backend: { @@ -29,11 +29,11 @@ export interface AuthStatus { hasGithubAuth: boolean isLoading: boolean } - + // Computed status isFullyAuthenticated: boolean isReady: boolean - + // What's missing (for UI feedback) missing: { clerkSignIn: boolean @@ -42,7 +42,7 @@ export interface AuthStatus { backendConnection: boolean githubBackendSync: boolean } - + // Progress (for UI indicators) progress: { completed: number @@ -58,7 +58,7 @@ export interface AuthStatus { export interface AuthActions { // Wallet actions connectWallet: () => Promise - + // Combined actions refreshAllStatus: () => Promise checkGithubBackendAuth: () => Promise @@ -73,35 +73,52 @@ export function useAuthStatus(): AuthStatus & AuthActions { // Clerk authentication const { isSignedIn, isLoaded: isClerkLoaded } = useAuth() const { user, isLoaded: isUserLoaded } = useUser() - + // Wallet authentication - const { - isConnected: isWalletSessionActive, // SIWE authenticated - hasWalletAddress, - wallet, - connect: connectWallet + const { + isConnected: isWalletSessionActive, // SIWE authenticated + hasWalletAddress, + wallet, + connect: connectWallet } = useWallet() - + // Backend authentication const { isBackendConnected, isLoading: isBackendLoading, refreshStatus: refreshBackendStatus } = useBackend() - + // GraphQL client for checking GitHub backend auth const gqlClient = useGQLClient() - + // GitHub backend auth state const [isGithubBackendAuth, setIsGithubBackendAuth] = 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 const checkGithubBackendAuth = useCallback(async (): Promise => { 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 { + isCheckingRef.current = true setIsCheckingGithubAuth(true) + lastGithubCheckRef.current = now + const userData = await gqlClient.getUser() const hasGitHubToken = !!userData.user.gitHubToken setIsGithubBackendAuth(hasGitHubToken) @@ -111,10 +128,11 @@ export function useAuthStatus(): AuthStatus & AuthActions { setIsGithubBackendAuth(false) return false } finally { + isCheckingRef.current = false setIsCheckingGithubAuth(false) } - }, [isBackendConnected, gqlClient]) - + }, [isBackendConnected, isGithubBackendAuth]) // Minimal dependencies + // Check GitHub auth when backend connection changes useEffect(() => { if (isBackendConnected) { @@ -122,9 +140,9 @@ export function useAuthStatus(): AuthStatus & AuthActions { } else { setIsGithubBackendAuth(false) } - }, [isBackendConnected, checkGithubBackendAuth]) - - // Check backend connection when wallet session is active (SIWE completed) + }, [isBackendConnected]) // Remove checkGithubBackendAuth from dependencies to prevent infinite loop + + // Check backend connection when wallet session is active (SIWE completed) useEffect(() => { if (isWalletSessionActive) { // Wait a moment for wallet session to be established, then check backend @@ -133,13 +151,16 @@ export function useAuthStatus(): AuthStatus & AuthActions { }, 1000) return () => clearTimeout(timer) } - }, [isWalletSessionActive, refreshBackendStatus]) - + }, [isWalletSessionActive]) // Remove refreshBackendStatus from dependencies to prevent rapid retriggers + // Check if GitHub is connected in Clerk - const hasGithubInClerk = user?.externalAccounts?.find( - account => account.provider === 'github' || account.verification?.strategy === 'oauth_github' - ) !== undefined - + const hasGithubInClerk = + user?.externalAccounts?.find( + (account) => + account.provider === 'github' || + account.verification?.strategy === 'oauth_github' + ) !== undefined + // Calculate what's missing const missing = { clerkSignIn: !isSignedIn, @@ -148,32 +169,50 @@ export function useAuthStatus(): AuthStatus & AuthActions { backendConnection: hasWalletAddress && !isWalletSessionActive, // Need SIWE auth for backend githubBackendSync: isBackendConnected && !isGithubBackendAuth } - + // Calculate progress const authSteps = [ - isSignedIn, // Clerk sign in - hasGithubInClerk, // GitHub connected to Clerk - hasWalletAddress, // Wallet address obtained - isWalletSessionActive, // SIWE authentication completed - isGithubBackendAuth // GitHub synced to backend + isSignedIn, // Clerk sign in + hasGithubInClerk, // GitHub connected to Clerk + hasWalletAddress, // Wallet address obtained + isWalletSessionActive, // SIWE authentication completed + isGithubBackendAuth // GitHub synced to backend ] - + const completedSteps = authSteps.filter(Boolean).length const totalSteps = authSteps.length const progressPercentage = Math.round((completedSteps / totalSteps) * 100) - + // 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) - const isReady = isClerkLoaded && isUserLoaded && !isBackendLoading && !isCheckingGithubAuth - + const isReady = + isClerkLoaded && isUserLoaded && !isBackendLoading && !isCheckingGithubAuth + // Combined refresh action const refreshAllStatus = async () => { await refreshBackendStatus() await checkGithubBackendAuth() } - + return { // Individual systems clerk: { @@ -192,24 +231,24 @@ export function useAuthStatus(): AuthStatus & AuthActions { hasGithubAuth: isGithubBackendAuth, isLoading: isBackendLoading || isCheckingGithubAuth }, - + // Computed status isFullyAuthenticated, isReady, - + // Missing items missing, - + // Progress progress: { completed: completedSteps, total: totalSteps, percentage: progressPercentage }, - + // Actions connectWallet, refreshAllStatus, checkGithubBackendAuth } -} \ No newline at end of file +} diff --git a/apps/deploy-fe/src/hooks/useRepoData.tsx b/apps/deploy-fe/src/hooks/useRepoData.tsx index beb37a1..fff00af 100644 --- a/apps/deploy-fe/src/hooks/useRepoData.tsx +++ b/apps/deploy-fe/src/hooks/useRepoData.tsx @@ -1,139 +1,179 @@ -"use client"; +'use client' -import { useState, useEffect } from "react"; -import { useAuth, useUser } from "@clerk/nextjs"; -import { Octokit } from "@octokit/rest"; -import { getGitHubToken } from "@/actions/github"; +import { getGitHubToken } from '@/actions/github' +import { useAuth, useUser } from '@clerk/nextjs' +import { Octokit } from '@octokit/rest' +import { useEffect, useState } from 'react' // Define the return type of the hook interface UseRepoDataReturn { - repoData: any; - isLoading: boolean; - error: string | null; + repoData: any + isLoading: boolean + error: string | null } /** * A hook to fetch repository data from GitHub - * + * * @param repoId - The GitHub repository ID to fetch, or empty string to fetch all repos * @returns Object containing repository data, loading state, and any errors */ export function useRepoData(repoId: string): UseRepoDataReturn { - const [repoData, setRepoData] = useState(null); - const [isLoading, setIsLoading] = useState(true); - const [error, setError] = useState(null); - const [octokit, setOctokit] = useState(null); - + const [repoData, setRepoData] = useState(null) + const [isLoading, setIsLoading] = useState(true) + const [error, setError] = useState(null) + const [octokit, setOctokit] = useState(null) + // Get auth data from Clerk - const { isLoaded: isAuthLoaded } = useAuth(); - const { isLoaded: isUserLoaded, user } = useUser(); + const { isLoaded: isAuthLoaded, getToken, isSignedIn } = useAuth() + const { isLoaded: isUserLoaded, user } = useUser() // Initialize Octokit with the appropriate token useEffect(() => { async function initializeOctokit() { - let token = null; - - // Try to get GitHub token from Clerk - if (user) { + let token = null + + // 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' - ); - + (account) => account.provider === 'github' + ) + 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 { - token = await getGitHubToken(); - console.log('Using GitHub token from Clerk'); - } catch (err) { - console.error('Error getting GitHub token from Clerk:', err); + 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); + console.error('Error accessing Clerk user data:', err) } } - + // Fallback to token from environment variable - if (!token && typeof process !== 'undefined') { - token = process.env.NEXT_PUBLIC_GITHUB_FALLBACK_TOKEN || ''; + 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.'); + 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 })); + setOctokit(new Octokit({ auth: token })) } else { - setError("No GitHub token available"); - setIsLoading(false); + setError('No GitHub token available') + setIsLoading(false) } } - + if (isAuthLoaded && isUserLoaded) { - initializeOctokit(); + initializeOctokit() } - }, [isAuthLoaded, isUserLoaded, user]); + }, [isAuthLoaded, isUserLoaded, user]) // Fetch repo data when Octokit is available useEffect(() => { - let isMounted = true; - + let isMounted = true + async function fetchRepoData() { if (!octokit) { - return; + return } - + try { // 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 (!repoId) { if (isMounted) { - setRepoData(repos); - setError(null); - setIsLoading(false); + setRepoData(repos) + setError(null) + setIsLoading(false) } - return; + return } - + // 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 (isMounted) { - setError("Repository not found"); - setRepoData(null); - setIsLoading(false); + setError('Repository not found') + setRepoData(null) + setIsLoading(false) } } else { if (isMounted) { - setRepoData(repo); - setError(null); - setIsLoading(false); + setRepoData(repo) + setError(null) + setIsLoading(false) } } } catch (err) { - console.error('Error fetching GitHub repo:', err); + console.error('Error fetching GitHub repo:', err) if (isMounted) { - setError('Failed to fetch repository data'); - setRepoData(null); - setIsLoading(false); + setError('Failed to fetch repository data') + setRepoData(null) + setIsLoading(false) } } } - - if (octokit) { - fetchRepoData(); - } - - return () => { - isMounted = false; - }; - }, [repoId, octokit]); - return { repoData, isLoading, error }; -} \ No newline at end of file + if (octokit) { + fetchRepoData() + } + + return () => { + isMounted = false + } + }, [repoId, octokit]) + + return { repoData, isLoading, error } +}