From f2472f4ce93a248e9e0bb11ce4ad075e562fce03 Mon Sep 17 00:00:00 2001 From: icld Date: Thu, 27 Feb 2025 13:15:01 -0800 Subject: [PATCH] Onboarding opens from with state changes --- packages/frontend/src/app/layout.tsx | 17 -- packages/frontend/src/app/page.tsx | 74 ----- .../layout/navigation/TopNavigation.tsx | 122 +++++++- .../components/onboarding-flow/Onboarding.tsx | 23 +- .../onboarding-flow/OnboardingDialog.tsx | 275 ++++++++++++++++++ .../common/onboarding-container.tsx | 2 +- .../configure-step/configure-step.tsx | 43 +-- .../connect-step/connect-step.tsx | 8 +- .../deploy-step/deploy-step.tsx | 38 +-- .../src/components/onboarding-flow/index.ts | 1 + .../src/components/onboarding-flow/store.ts | 9 +- .../src/components/onboarding-flow/store.tsx | 69 +++++ .../src/components/onboarding-flow/types.ts | 18 +- .../projects/create/ConnectAccount.tsx | 2 +- packages/frontend/src/pages/index.tsx | 74 +++-- 15 files changed, 603 insertions(+), 172 deletions(-) delete mode 100644 packages/frontend/src/app/layout.tsx delete mode 100644 packages/frontend/src/app/page.tsx create mode 100644 packages/frontend/src/components/onboarding-flow/OnboardingDialog.tsx create mode 100644 packages/frontend/src/components/onboarding-flow/store.tsx diff --git a/packages/frontend/src/app/layout.tsx b/packages/frontend/src/app/layout.tsx deleted file mode 100644 index dbeabcf1..00000000 --- a/packages/frontend/src/app/layout.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import type React from "react" -import "@/styles/globals.css" - -export default function RootLayout({ - children, -}: { - children: React.ReactNode -}) { - return ( - - -
{children}
- - - ) -} - diff --git a/packages/frontend/src/app/page.tsx b/packages/frontend/src/app/page.tsx deleted file mode 100644 index 9496910c..00000000 --- a/packages/frontend/src/app/page.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import { - OnboardingContainer, - SidebarNav, - StepHeader, - StepNavigation, -} from '@/components/onboarding-flow'; -import { ConfigureStep } from '@/components/onboarding-flow/configure-step/configure-step'; -import { ConnectStep } from '@/components/onboarding-flow/connect-step/connect-step'; -import { DeployStep } from '@/components/onboarding-flow/deploy-step/deploy-step'; -import { useOnboarding } from '@/components/onboarding-flow/store'; -import { FileCog, GitPullRequest, SquareArrowOutDownRight } from 'lucide-react'; - -/** Icons for each step in the onboarding flow */ -const stepIcons = { - connect: , - configure: , - deploy: , -}; - -/** Titles for each step in the onboarding flow */ -const stepTitles = { - connect: 'Connect', - configure: 'Configure', - deploy: 'Deploy', -}; - -/** Descriptions for each step in the onboarding flow */ -const stepDescriptions = { - connect: 'Connect and import a GitHub repository to start deploying.', - configure: 'Set up your deployment configuration and environment variables.', - deploy: 'Review your settings and deploy your project.', -}; - -/** - * Main onboarding page component - * Orchestrates the entire onboarding flow and manages step transitions - * - * Component Hierarchy: - * - OnboardingContainer - * - SidebarNav (step progress) - * - Main content - * - StepHeader (current step info) - * - Step content (ConnectStep | ConfigureStep | DeployStep) - * - StepNavigation (previous/next controls) - * - * @returns {JSX.Element} Complete onboarding interface - */ -export default function Page() { - const { currentStep, nextStep, previousStep } = useOnboarding(); - - return ( - - -
- -
- {currentStep === 'connect' && } - {currentStep === 'configure' && } - {currentStep === 'deploy' && } -
- -
-
- ); -} diff --git a/packages/frontend/src/components/layout/navigation/TopNavigation.tsx b/packages/frontend/src/components/layout/navigation/TopNavigation.tsx index e723eb65..b1d05174 100644 --- a/packages/frontend/src/components/layout/navigation/TopNavigation.tsx +++ b/packages/frontend/src/components/layout/navigation/TopNavigation.tsx @@ -1,9 +1,21 @@ 'use client'; +import { OnboardingDialog } from '@/components/onboarding-flow'; import { Button } from '@/components/ui/button'; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from '@/components/ui/dropdown-menu'; import { Sheet, SheetContent, SheetTrigger } from '@/components/ui/sheet'; +import { useGQLClient } from '@/context/GQLClientContext'; +import { useOctokit } from '@/context/OctokitContext'; import { LaconicMark } from '@/laconic-assets/laconic-mark'; import * as PopoverPrimitive from '@radix-ui/react-popover'; -import { Menu, Shapes, Wallet } from 'lucide-react'; +import { Organization } from 'gql-client'; +import { Menu, Plus, Shapes, Wallet } from 'lucide-react'; +import { useCallback, useEffect, useState } from 'react'; import { Link, useNavigate } from 'react-router-dom'; import { ProjectSearchBar } from '../search/ProjectSearchBar'; import { ColorModeToggle } from './components/ColorModeToggle'; @@ -26,6 +38,58 @@ import { WalletSessionId } from './components/WalletSessionId'; */ export function TopNavigation() { const navigate = useNavigate(); + const [showOnboarding, setShowOnboarding] = useState(false); + const { octokit } = useOctokit(); + const client = useGQLClient(); + const [defaultOrg, setDefaultOrg] = useState(null); + + // Check if GitHub is connected + const isGitHubConnected = Boolean(octokit); + + // Fetch the default organization (first one in the list) + const fetchDefaultOrganization = useCallback(async () => { + try { + const { organizations } = await client.getOrganizations(); + if (organizations && organizations.length > 0) { + setDefaultOrg(organizations[0]); + } + } catch (error) { + console.error('Error fetching organizations:', error); + } + }, [client]); + + useEffect(() => { + if (isGitHubConnected) { + fetchDefaultOrganization(); + } + }, [isGitHubConnected, fetchDefaultOrganization]); + + const handleOnboardingClose = () => { + setShowOnboarding(false); + // Refresh organization data after onboarding + fetchDefaultOrganization(); + }; + + // Navigate to create page with organization slug + const handleCreateNew = () => { + if (defaultOrg) { + navigate(`/${defaultOrg.slug}/projects/create`); + } else { + // If no organization is available, show onboarding + setShowOnboarding(true); + } + }; + + const handleRunOnboarding = () => { + // Clear existing onboarding progress data + localStorage.removeItem('onboarding_progress'); + localStorage.removeItem('onboarding_state'); + + // Force starting from connect step + localStorage.setItem('onboarding_force_connect', 'true'); + + setShowOnboarding(true); + }; return ( @@ -79,6 +143,28 @@ export function TopNavigation() {
+ {/* Add New Button with Dropdown */} + + + + + + {isGitHubConnected && ( + <> + + Create New + + + + )} + + Run Onboarding + + + + {/* */} + + {isGitHubConnected && ( + + )}
@@ -143,11 +237,37 @@ export function TopNavigation() {
+ {/* Add New Button (Mobile) */} + + + + + + {isGitHubConnected && ( + <> + + Create New + + + + )} + + Run Onboarding + + +
+ + {/* Onboarding Dialog */} + {showOnboarding && ( + + )} ); } diff --git a/packages/frontend/src/components/onboarding-flow/Onboarding.tsx b/packages/frontend/src/components/onboarding-flow/Onboarding.tsx index dec80943..abef6bcf 100644 --- a/packages/frontend/src/components/onboarding-flow/Onboarding.tsx +++ b/packages/frontend/src/components/onboarding-flow/Onboarding.tsx @@ -6,15 +6,14 @@ */ import { - OnboardingContainer, - StepHeader, - StepNavigation, + OnboardingContainer, StepNavigation } from '@/components/onboarding-flow/common'; import { ConfigureStep } from '@/components/onboarding-flow/configure-step'; import { ConnectStep } from '@/components/onboarding-flow/connect-step'; import { DeployStep } from '@/components/onboarding-flow/deploy-step'; import { SidebarNav } from '@/components/onboarding-flow/sidebar'; import { useOnboarding } from '@/components/onboarding-flow/store'; +import { ScrollArea } from '@/components/ui/scroll-area'; import { FileCog, GitPullRequest, SquareArrowOutDownRight } from 'lucide-react'; /** Icons for each step in the onboarding flow */ @@ -58,17 +57,19 @@ export default function Onboarding() { return ( -
- + {/* -
- {currentStep === 'connect' && } - {currentStep === 'configure' && } - {currentStep === 'deploy' && } -
+ /> */} +
+ + {currentStep === 'connect' && } + {currentStep === 'configure' && } + {currentStep === 'deploy' && } + +
void; +} + +/** + * OnboardingDialog component + * + * A dialog modal that contains the onboarding flow. + * Can be triggered by a custom element or automatically opened. + * Sets the initial step based on GitHub connection status. + * Provides warnings when exiting mid-step and options to continue progress. + */ +const OnboardingDialog: React.FC = ({ + trigger, + defaultOpen = false, + onClose +}) => { + const onboardingStore = useOnboarding(); + const { setCurrentStep, currentStep, formData } = onboardingStore; + const { octokit } = useOctokit(); + const [showExitWarning, setShowExitWarning] = useState(false); + const [showContinueAlert, setShowContinueAlert] = useState(false); + const [isOpen, setIsOpen] = useState(defaultOpen); + const [forceConnectStep, setForceConnectStep] = useState(false); + + // Check for force connect flag when component mounts + useEffect(() => { + const shouldForceConnect = localStorage.getItem(ONBOARDING_FORCE_CONNECT_KEY) === 'true'; + if (shouldForceConnect) { + setForceConnectStep(true); + // Clear the flag so it's only used once + localStorage.removeItem(ONBOARDING_FORCE_CONNECT_KEY); + } + }, []); + + // Local implementation of reset function that handles all necessary state + const resetOnboardingState = () => { + // Reset step to connect + setCurrentStep('connect'); + + // Flag to force starting from the connect step + setForceConnectStep(true); + + // Also reset form data to ensure substeps are cleared + const store = onboardingStore as any; + if (typeof store.updateFormData === 'function') { + store.updateFormData({ + projectName: '', + repoName: '', + repoDescription: '', + framework: '', + access: 'public', + organizationSlug: '', + }); + } + }; + + // Check if there's existing progress + useEffect(() => { + if (isOpen) { + const savedProgress = localStorage.getItem(ONBOARDING_PROGRESS_KEY); + const savedState = localStorage.getItem(ONBOARDING_STATE_KEY); + + if (savedProgress === 'true' && savedState && !forceConnectStep) { + // Show continue or start fresh dialog + setShowContinueAlert(true); + } else { + // Set initial step based on GitHub connection status + initializeOnboarding(); + } + } + }, [isOpen, forceConnectStep]); + + // Set the initial step based on GitHub connection status + const initializeOnboarding = () => { + // Reset previous state + resetOnboardingState(); + + // If GitHub is connected AND we're not forcing the connect step, + // start at the configure step. Otherwise, start at the connect step + if (octokit && !forceConnectStep) { + setCurrentStep('configure'); + } else { + setCurrentStep('connect'); + } + + // Mark that we have onboarding in progress + localStorage.setItem(ONBOARDING_PROGRESS_KEY, 'true'); + + // Save the initial state + saveCurrentState(); + }; + + // Start fresh by initializing onboarding and forcing the connect step + const startFresh = () => { + // Set flag to force starting from the connect step + setForceConnectStep(true); + initializeOnboarding(); + setShowContinueAlert(false); + }; + + // Continue from saved state and don't force the connect step + const continueOnboarding = () => { + // Reset the force flag since we're continuing + setForceConnectStep(false); + loadSavedState(); + }; + + // Save current onboarding state + const saveCurrentState = () => { + try { + const state = { + currentStep, + formData, + forceConnectStep // Save this flag as part of the state + }; + localStorage.setItem(ONBOARDING_STATE_KEY, JSON.stringify(state)); + } catch (error) { + console.error('Error saving onboarding state:', error); + } + }; + + // Load saved onboarding state + const loadSavedState = () => { + try { + const savedState = localStorage.getItem(ONBOARDING_STATE_KEY); + if (savedState) { + const state = JSON.parse(savedState); + + // Restore the force flag if it exists + if (state.forceConnectStep !== undefined) { + setForceConnectStep(state.forceConnectStep); + } + + setCurrentStep(state.currentStep as Step); + + // Also restore form data to preserve org/repo selection + const store = onboardingStore as any; + if (state.formData && typeof store.updateFormData === 'function') { + store.updateFormData(state.formData as Partial); + } + } + } catch (error) { + console.error('Error loading onboarding state:', error); + initializeOnboarding(); + } + setShowContinueAlert(false); + }; + + // Save state on step changes + useEffect(() => { + if (isOpen) { + saveCurrentState(); + } + }, [currentStep, formData, forceConnectStep]); + + // Mark onboarding as completed when user reaches the deploy step + useEffect(() => { + if (currentStep === 'deploy') { + localStorage.setItem(ONBOARDING_COMPLETED_KEY, 'true'); + } + }, [currentStep]); + + // Handle dialog close attempt + const handleOpenChange = (open: boolean) => { + if (!open && isOpen) { + // If closing and not on the last step, show warning + if (currentStep !== 'deploy') { + setShowExitWarning(true); + return; // Prevent closing until user confirms + } + + // If on the last step or user confirmed, close normally + completeClose(); + } else { + setIsOpen(open); + } + }; + + // Complete the closing process + const completeClose = () => { + // Mark as completed when dialog is closed + localStorage.setItem(ONBOARDING_COMPLETED_KEY, 'true'); + + // Clear progress flag + localStorage.removeItem(ONBOARDING_PROGRESS_KEY); + localStorage.removeItem(ONBOARDING_STATE_KEY); + + // Reset onboarding state for next time + resetOnboardingState(); + + // Close the dialog + setIsOpen(false); + + if (onClose) { + onClose(); + } + }; + + // Cancel closing + const cancelClose = () => { + setShowExitWarning(false); + }; + + return ( + <> + + {trigger && {trigger}} + +
+ +
+
+
+ + {/* Exit Warning Dialog */} + + + + Exit Onboarding? + + You haven't completed the onboarding process. If you exit now, your progress will be lost, including any organization or repository selections. + + + + Cancel + Exit Anyway + + + + + {/* Continue Progress Dialog */} + + + + Continue Onboarding? + + You're in the middle of setting up your project, including organization and repository selection. Would you like to continue where you left off or start fresh? + + + + Start Fresh + Continue + + + + + ); +}; + +/** + * Helper function to check if the user has completed onboarding + * @returns {boolean} Whether onboarding has been completed + */ +export const hasCompletedOnboarding = (): boolean => { + return localStorage.getItem(ONBOARDING_COMPLETED_KEY) === 'true'; +}; + +export default OnboardingDialog; \ No newline at end of file diff --git a/packages/frontend/src/components/onboarding-flow/common/onboarding-container.tsx b/packages/frontend/src/components/onboarding-flow/common/onboarding-container.tsx index b346f03e..68b64165 100644 --- a/packages/frontend/src/components/onboarding-flow/common/onboarding-container.tsx +++ b/packages/frontend/src/components/onboarding-flow/common/onboarding-container.tsx @@ -27,7 +27,7 @@ interface OnboardingContainerProps { export function OnboardingContainer({ children }: OnboardingContainerProps) { return (
-
{children}
+
{children}
) } diff --git a/packages/frontend/src/components/onboarding-flow/configure-step/configure-step.tsx b/packages/frontend/src/components/onboarding-flow/configure-step/configure-step.tsx index aa022609..d89e4bcc 100644 --- a/packages/frontend/src/components/onboarding-flow/configure-step/configure-step.tsx +++ b/packages/frontend/src/components/onboarding-flow/configure-step/configure-step.tsx @@ -1,4 +1,5 @@ import { useOnboarding } from "@/components/onboarding-flow/store" +import Configure from "@/components/projects/create/Configure" import { FileCog } from "lucide-react" import { useState } from "react" @@ -36,27 +37,31 @@ export function ConfigureStep() { // } return ( -
-
- {/* Header section with icon and description */} -
- -
-

Configure

-

- Set the deployer LRN for a single deployment or by creating a deployer auction for multiple deployments -

+
+
+
+
+ {/* Header section with icon and description */} +
+ +
+

Configure

+

+ Set the deployer LRN for a single deployment or by creating a deployer auction for multiple deployments +

+
+
+ + {/* Content sections will be placed here: + 1. Deployment type tabs (auction/LRN) + 2. Configuration forms + 3. Environment variables + 4. Account selection + + ...content here/ */} +
- - Content sections will be placed here: - 1. Deployment type tabs (auction/LRN) - 2. Configuration forms - 3. Environment variables - 4. Account selection - - ...content here/ - {/* */}
) diff --git a/packages/frontend/src/components/onboarding-flow/connect-step/connect-step.tsx b/packages/frontend/src/components/onboarding-flow/connect-step/connect-step.tsx index 891ecc03..c9b24b60 100644 --- a/packages/frontend/src/components/onboarding-flow/connect-step/connect-step.tsx +++ b/packages/frontend/src/components/onboarding-flow/connect-step/connect-step.tsx @@ -28,20 +28,20 @@ export function ConnectStep() { }; const handleRepositorySelect = (repo: { name: string }) => { - setFormData({ githubRepo: repo.name }); + setFormData({ repoName: repo.name }); nextStep(); }; const handleTemplateSelect = (template: { id: string; name: string }) => { setFormData({ - githubRepo: projectName, - deploymentType: template.id, + repoName: projectName, + framework: template.id, }); nextStep(); }; return ( -
+
{/* \ */} {connectState === 'initial' ? (
diff --git a/packages/frontend/src/components/onboarding-flow/deploy-step/deploy-step.tsx b/packages/frontend/src/components/onboarding-flow/deploy-step/deploy-step.tsx index 4cb6ec38..18593db9 100644 --- a/packages/frontend/src/components/onboarding-flow/deploy-step/deploy-step.tsx +++ b/packages/frontend/src/components/onboarding-flow/deploy-step/deploy-step.tsx @@ -23,25 +23,29 @@ export function DeployStep() { } return ( -
-
- {/* Header section */} -
-
-

Deploy

-

- Your deployment is configured and ready to go! -

+
+
+
+
+ {/* Header section */} +
+
+

Deploy

+

+ Your deployment is configured and ready to go! +

+
+
+ + Content sections will be placed here: + 1. Repository info card + 2. Configuration summary + 3. Deploy button + + {/* ...content here */} +
- - Content sections will be placed here: - 1. Repository info card - 2. Configuration summary - 3. Deploy button - - {/* ...content here */} -
) diff --git a/packages/frontend/src/components/onboarding-flow/index.ts b/packages/frontend/src/components/onboarding-flow/index.ts index 85c40cd9..66f328f3 100644 --- a/packages/frontend/src/components/onboarding-flow/index.ts +++ b/packages/frontend/src/components/onboarding-flow/index.ts @@ -10,6 +10,7 @@ // Main component export { default as Onboarding } from './Onboarding'; +export { default as OnboardingDialog, hasCompletedOnboarding } from './OnboardingDialog'; // Step components export { ConfigureStep } from './configure-step'; diff --git a/packages/frontend/src/components/onboarding-flow/store.ts b/packages/frontend/src/components/onboarding-flow/store.ts index 5b63ae09..eef71e95 100644 --- a/packages/frontend/src/components/onboarding-flow/store.ts +++ b/packages/frontend/src/components/onboarding-flow/store.ts @@ -40,7 +40,14 @@ const STEP_ORDER: Step[] = ['connect', 'configure', 'deploy']; */ export const useOnboarding = create((set) => ({ currentStep: 'connect', - formData: {}, + formData: { + projectName: '', + repoName: '', + repoDescription: '', + framework: '', + access: 'public', + organizationSlug: '', + }, setCurrentStep: (step) => set({ currentStep: step }), setFormData: (data) => set((state) => ({ diff --git a/packages/frontend/src/components/onboarding-flow/store.tsx b/packages/frontend/src/components/onboarding-flow/store.tsx new file mode 100644 index 00000000..ff90f774 --- /dev/null +++ b/packages/frontend/src/components/onboarding-flow/store.tsx @@ -0,0 +1,69 @@ +import { create } from 'zustand'; +import { OnboardingFormData, Step } from './types'; + +// Define the state for the onboarding flow +export interface OnboardingState { + currentStep: Step; + setCurrentStep: (step: Step) => void; + nextStep: () => void; + previousStep: () => void; + formData: OnboardingFormData; + updateFormData: (data: Partial) => void; + resetOnboarding: () => void; +} + +// Create the store with the initial state +export const useOnboarding = create((set) => { + // The steps in order + const STEPS: Step[] = ['connect', 'configure', 'deploy']; + + // Initial form data + const initialFormData: OnboardingFormData = { + projectName: '', + repoName: '', + repoDescription: '', + framework: '', + access: 'public', + organizationSlug: '', + }; + + return { + // Current step state (start with the connect step) + currentStep: 'connect', + + // Function to set the current step + setCurrentStep: (step) => set({ currentStep: step }), + + // Function to move to the next step + nextStep: () => set((state) => { + const currentIndex = STEPS.indexOf(state.currentStep); + if (currentIndex < STEPS.length - 1) { + return { currentStep: STEPS[currentIndex + 1] }; + } + return state; + }), + + // Function to move to the previous step + previousStep: () => set((state) => { + const currentIndex = STEPS.indexOf(state.currentStep); + if (currentIndex > 0) { + return { currentStep: STEPS[currentIndex - 1] }; + } + return state; + }), + + // Form data state + formData: initialFormData, + + // Function to update form data + updateFormData: (data) => set((state) => ({ + formData: { ...state.formData, ...data } + })), + + // Function to reset the onboarding state + resetOnboarding: () => set({ + currentStep: 'connect', + formData: initialFormData + }), + }; +}); \ No newline at end of file diff --git a/packages/frontend/src/components/onboarding-flow/types.ts b/packages/frontend/src/components/onboarding-flow/types.ts index 1378e66f..7aebd66f 100644 --- a/packages/frontend/src/components/onboarding-flow/types.ts +++ b/packages/frontend/src/components/onboarding-flow/types.ts @@ -12,14 +12,20 @@ export type Step = 'connect' | 'configure' | 'deploy'; /** * Form data collected during the onboarding process * @interface OnboardingFormData - * @property {string} [githubRepo] - Selected GitHub repository - * @property {string} [deploymentType] - Selected deployment type (e.g., "pwa") - * @property {Record} [environmentVars] - Environment variables + * @property {string} projectName - Project name + * @property {string} repoName - Repository name + * @property {string} repoDescription - Repository description + * @property {string} framework - Framework used for the project + * @property {string} access - Access level of the repository + * @property {string} organizationSlug - Organization slug */ export interface OnboardingFormData { - githubRepo?: string; - deploymentType?: string; - environmentVars?: Record; + projectName: string; + repoName: string; + repoDescription: string; + framework: string; + access: 'public' | 'private'; + organizationSlug: string; } /** diff --git a/packages/frontend/src/components/projects/create/ConnectAccount.tsx b/packages/frontend/src/components/projects/create/ConnectAccount.tsx index edf30b2c..44ef1ce0 100644 --- a/packages/frontend/src/components/projects/create/ConnectAccount.tsx +++ b/packages/frontend/src/components/projects/create/ConnectAccount.tsx @@ -53,7 +53,7 @@ const ConnectAccount: React.FC = ({ // TODO: Use correct height return ( -
+
{/** Icons */}
diff --git a/packages/frontend/src/pages/index.tsx b/packages/frontend/src/pages/index.tsx index add9a2c2..f5fec7b5 100644 --- a/packages/frontend/src/pages/index.tsx +++ b/packages/frontend/src/pages/index.tsx @@ -2,51 +2,85 @@ import { Organization } from 'gql-client'; import { Loader2 } from 'lucide-react'; import { useCallback, useEffect, useState } from 'react'; import { Navigate, useNavigate } from 'react-router-dom'; +import { OnboardingDialog, hasCompletedOnboarding } from '../components/onboarding-flow'; import { useGQLClient } from '../context/GQLClientContext'; +import { useOctokit } from '../context/OctokitContext'; /** - * Index component that fetches user organizations and redirects to the first organization's slug. - * If no organization is found, it displays a loading spinner. + * Index component that handles post-authentication flow. + * Shows onboarding dialog if needed, then redirects to first organization. * - * @returns {JSX.Element} A JSX element that either navigates to the organization's slug or displays a loading spinner. + * @returns {JSX.Element} The rendered component. */ const Index = () => { const client = useGQLClient(); const [organization, setOrganization] = useState(); + const [loading, setLoading] = useState(true); + const [showOnboarding, setShowOnboarding] = useState(false); const navigate = useNavigate(); + const { octokit } = useOctokit(); + + // Check if GitHub is connected + const isGitHubConnected = Boolean(octokit); /** * Fetches the user's organizations from the GQLClient. * Sets the first organization in the list to the organization state. - * If no organizations are found, navigates to the '/auth' route. + * If no organizations are found, shows onboarding. * * @async * @function fetchUserOrganizations * @returns {Promise} */ const fetchUserOrganizations = useCallback(async () => { - const { organizations } = await client.getOrganizations(); - if (organizations && organizations.length > 0) { - // By default information of first organization displayed - setOrganization(organizations[0]); - } else { - navigate('/auth'); + try { + setLoading(true); + const { organizations } = await client.getOrganizations(); + + if (organizations && organizations.length > 0) { + // By default information of first organization displayed + setOrganization(organizations[0]); + + // Check if onboarding is needed + const onboardingCompleted = hasCompletedOnboarding(); + + // We need onboarding if it hasn't been completed or GitHub is not connected + if (!onboardingCompleted || !isGitHubConnected) { + setShowOnboarding(true); + } + } else { + // No organizations found, show onboarding + setShowOnboarding(true); + } + } catch (error) { + console.error('Error fetching organizations:', error); + // Show onboarding on error + setShowOnboarding(true); + } finally { + setLoading(false); } - }, [client, navigate]); + }, [client, isGitHubConnected]); useEffect(() => { fetchUserOrganizations(); }, [fetchUserOrganizations]); - return ( - <> - {Boolean(organization) ? ( - - ) : ( - - )} - - ); + // Handle onboarding completion + const handleOnboardingClosed = () => { + setShowOnboarding(false); + // Fetch organizations again after onboarding in case new ones were created + fetchUserOrganizations(); + }; + + if (loading) { + return ; + } + + if (showOnboarding) { + return ; + } + + return organization ? : ; }; export default Index;