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
|
// 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">
|
||||||
|
|||||||
@ -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
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@ -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 })
|
||||||
}))
|
}))
|
||||||
|
|||||||
@ -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[]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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 () => {
|
||||||
|
|||||||
@ -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 }
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue
Block a user