Removed flickering and updated form entry
This commit is contained in:
parent
ce851ef883
commit
3a2a6dca7e
@ -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<string>(
|
||||
formData.deployerCount || '1'
|
||||
)
|
||||
const [maxPrice, setMaxPrice] = useState<string>(formData.maxPrice || '1000')
|
||||
const [selectedLrn, setSelectedLrn] = useState<string>(
|
||||
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<string>('1')
|
||||
const [maxPrice, setMaxPrice] = useState<string>('1000')
|
||||
const [selectedLrn, setSelectedLrn] = useState<string>('')
|
||||
const [selectedOrg, setSelectedOrg] = useState<string>(
|
||||
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 (
|
||||
<div className="w-full h-full flex flex-col p-8 overflow-y-auto">
|
||||
|
@ -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<string>(
|
||||
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<string>(
|
||||
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
|
||||
})
|
||||
|
||||
|
@ -18,6 +18,7 @@ import type { OnboardingFormData, Step } from './types'
|
||||
* @property {(data: Partial<OnboardingFormData>) => 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<OnboardingFormData>) => 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<OnboardingState>((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<OnboardingState>((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 })
|
||||
}))
|
||||
|
@ -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[]
|
||||
}
|
||||
|
@ -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<void>
|
||||
|
||||
|
||||
// Combined actions
|
||||
refreshAllStatus: () => Promise<void>
|
||||
checkGithubBackendAuth: () => Promise<boolean>
|
||||
@ -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<boolean> => {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<any>(null);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [octokit, setOctokit] = useState<Octokit | null>(null);
|
||||
|
||||
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 } = 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 };
|
||||
}
|
||||
if (octokit) {
|
||||
fetchRepoData()
|
||||
}
|
||||
|
||||
return () => {
|
||||
isMounted = false
|
||||
}
|
||||
}, [repoId, octokit])
|
||||
|
||||
return { repoData, isLoading, error }
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user