From 61f5683b8b4ea1784ba9038549bfe2fa6436badc Mon Sep 17 00:00:00 2001 From: NasSharaf Date: Wed, 6 Aug 2025 19:21:21 -0400 Subject: [PATCH] Fixed types mismatch issues, can run pnpm build now --- .../(dashboard)/home/page.tsx | 9 +- .../ps/[id]/(deployments)/dep/page.tsx | 17 +- .../env/EnvVarsPage.tsx | 9 +- .../(settings)/set/ProjectSettingsPage.tsx | 6 +- .../[provider]/ps/[id]/deployments/page.tsx | 159 --------------- .../projects/[provider]/ps/[id]/page.tsx | 32 +-- .../src/app/test-connection/page.tsx | 4 +- apps/deploy-fe/src/components/AuthTest.tsx | 2 +- .../foundation/page-header/PageHeader.tsx | 180 ++++++++--------- .../wallet-session-id/WalletSessionId.tsx | 2 +- .../onboarding/OnboardingDialog.tsx | 6 +- .../configure-step/configure-step.tsx | 38 ++-- .../onboarding/connect-step/connect-step.tsx | 12 +- .../connect-step/disabled_connect-step.tsx | 1 + .../onboarding/deploy-step/deploy-step.tsx | 14 +- .../src/components/onboarding/index.ts | 1 - .../components/onboarding/useOnboarding.ts | 186 +++++++++++------- apps/deploy-fe/src/constants/templates.tsx | 4 +- apps/deploy-fe/src/context/BackendContext.tsx | 6 +- apps/deploy-fe/src/hooks/useAuthStatus.tsx | 4 +- apps/deploy-fe/src/hooks/useRepoData.tsx | 4 +- apps/deploy-fe/src/hooks/useRepoSelection.tsx | 6 +- apps/deploy-fe/src/types/deployment.ts | 14 ++ apps/deploy-fe/src/types/foundation.ts | 54 +++++ apps/deploy-fe/src/types/index.ts | 4 + apps/deploy-fe/src/types/project.ts | 7 + apps/deploy-fe/src/utils/typeAdapters.ts | 174 ++++++++++++++++ 27 files changed, 532 insertions(+), 423 deletions(-) delete mode 100644 apps/deploy-fe/src/app/(web3-authenticated)/(dashboard)/projects/[provider]/ps/[id]/deployments/page.tsx create mode 100644 apps/deploy-fe/src/types/foundation.ts create mode 100644 apps/deploy-fe/src/utils/typeAdapters.ts diff --git a/apps/deploy-fe/src/app/(web3-authenticated)/(dashboard)/home/page.tsx b/apps/deploy-fe/src/app/(web3-authenticated)/(dashboard)/home/page.tsx index 8f2dd8b..ace6b53 100644 --- a/apps/deploy-fe/src/app/(web3-authenticated)/(dashboard)/home/page.tsx +++ b/apps/deploy-fe/src/app/(web3-authenticated)/(dashboard)/home/page.tsx @@ -63,14 +63,11 @@ export default async function Page() { ) } - // NOTE: We're keeping the token approach for now, but aware it's not working - const authToken = githubAccount.accessToken; - // Try using GitHub token let octokit; try { octokit = new Octokit({ - auth: authToken || process.env.GITHUB_TOKEN + auth: process.env.GITHUB_TOKEN }); // Test with a simple request @@ -145,7 +142,7 @@ export default async function Page() { Failed to access GitHub API
- {authError.message} + {authError instanceof Error ? authError.message : 'An error occurred'}

This issue may be related to how Clerk is managing the GitHub token.

@@ -182,7 +179,7 @@ export default async function Page() { Failed to authenticate with GitHub
- {error.message} + {error instanceof Error ? error.message : 'Unknown error occurred'}
diff --git a/apps/deploy-fe/src/app/(web3-authenticated)/(dashboard)/projects/[provider]/ps/[id]/(deployments)/dep/page.tsx b/apps/deploy-fe/src/app/(web3-authenticated)/(dashboard)/projects/[provider]/ps/[id]/(deployments)/dep/page.tsx index 4aeb380..384a9cc 100644 --- a/apps/deploy-fe/src/app/(web3-authenticated)/(dashboard)/projects/[provider]/ps/[id]/(deployments)/dep/page.tsx +++ b/apps/deploy-fe/src/app/(web3-authenticated)/(dashboard)/projects/[provider]/ps/[id]/(deployments)/dep/page.tsx @@ -30,7 +30,7 @@ export default function DeploymentsPage() { // State for deployment logs modal const [isLogsOpen, setIsLogsOpen] = useState(false); - const [selectedDeploymentId, setSelectedDeploymentId] = useState(null); + const [, setSelectedDeploymentId] = useState(null); const [deploymentLogs, setDeploymentLogs] = useState(''); // Create a default deployment @@ -48,19 +48,6 @@ export default function DeploymentsPage() { } }; - const secondDeployment: Deployment = { - id: 'previous', - branch: 'feature/new-ui', - status: 'COMPLETED', - isCurrent: false, - createdAt: Date.now() - 3 * 24 * 60 * 60 * 1000, // 3 days ago - applicationDeploymentRecordData: { - url: repoData ? `https://dev.${repoData.name.toLowerCase()}.example.com` : 'https://dev.example.com' - }, - createdBy: { - name: repoData?.owner?.login || 'username' - } - }; // Initialize with empty data for testing the empty state // Comment this out to see the mock deployments @@ -79,7 +66,9 @@ export default function DeploymentsPage() { { id: '1', name: repoData ? `${repoData.name.toLowerCase()}.example.com` : 'example.com', + branch: 'main', // Add missing branch status: 'ACTIVE', + createdAt: Date.now(), // Add missing createdAt isCustom: false } ]; diff --git a/apps/deploy-fe/src/app/(web3-authenticated)/(dashboard)/projects/[provider]/ps/[id]/(settings)/set/(environment-variables)/env/EnvVarsPage.tsx b/apps/deploy-fe/src/app/(web3-authenticated)/(dashboard)/projects/[provider]/ps/[id]/(settings)/set/(environment-variables)/env/EnvVarsPage.tsx index 3416302..9c09475 100644 --- a/apps/deploy-fe/src/app/(web3-authenticated)/(dashboard)/projects/[provider]/ps/[id]/(settings)/set/(environment-variables)/env/EnvVarsPage.tsx +++ b/apps/deploy-fe/src/app/(web3-authenticated)/(dashboard)/projects/[provider]/ps/[id]/(settings)/set/(environment-variables)/env/EnvVarsPage.tsx @@ -3,12 +3,7 @@ import { useState } from "react"; import { LoadingOverlay } from "@/components/foundation/loading/loading-overlay"; import { PlusIcon, ChevronDownIcon, ChevronUpIcon, PencilIcon, TrashIcon } from "lucide-react"; - -interface EnvVarItem { - key: string; - value: string; - isEditing?: boolean; -} +import type { EnvVarItem } from '@/types' interface EnvGroupProps { title: string; @@ -204,7 +199,7 @@ export default function EnvVarsPage() { /> diff --git a/apps/deploy-fe/src/app/(web3-authenticated)/(dashboard)/projects/[provider]/ps/[id]/(settings)/set/ProjectSettingsPage.tsx b/apps/deploy-fe/src/app/(web3-authenticated)/(dashboard)/projects/[provider]/ps/[id]/(settings)/set/ProjectSettingsPage.tsx index 8b78ec9..f2753ba 100644 --- a/apps/deploy-fe/src/app/(web3-authenticated)/(dashboard)/projects/[provider]/ps/[id]/(settings)/set/ProjectSettingsPage.tsx +++ b/apps/deploy-fe/src/app/(web3-authenticated)/(dashboard)/projects/[provider]/ps/[id]/(settings)/set/ProjectSettingsPage.tsx @@ -278,11 +278,9 @@ export default function ProjectSettingsPage({ project, onProjectUpdated }: Proje Select account setSelectedAccount(value)} - className="w-full mt-1" + onChange={(option) => setSelectedAccount(option.value)} /> diff --git a/apps/deploy-fe/src/app/(web3-authenticated)/(dashboard)/projects/[provider]/ps/[id]/deployments/page.tsx b/apps/deploy-fe/src/app/(web3-authenticated)/(dashboard)/projects/[provider]/ps/[id]/deployments/page.tsx deleted file mode 100644 index 1c65931..0000000 --- a/apps/deploy-fe/src/app/(web3-authenticated)/(dashboard)/projects/[provider]/ps/[id]/deployments/page.tsx +++ /dev/null @@ -1,159 +0,0 @@ -'use client' -import { useEffect, useState } from 'react' -import { PageWrapper } from '@/components/foundation' -import { FixedProjectCard } from '@/components/projects/project/ProjectCard/FixedProjectCard' -import { Button } from '@workspace/ui/components/button' -import { Shapes } from 'lucide-react' -import { useAuth } from '@clerk/nextjs' - -interface Deployment { - id: string - name: string - repositoryId: string - status: 'running' | 'complete' | 'failed' - url?: string - branch: string - createdAt: string - createdBy: { - name: string - } -} - -export default function ProjectsPage() { - const [deployments, setDeployments] = useState([]) - const [isLoading, setIsLoading] = useState(true) - const [error, setError] = useState(null) - - const { isLoaded: isAuthLoaded, userId } = useAuth() - - useEffect(() => { - async function fetchDeployments() { - if (!isAuthLoaded) { - return; - } - - setIsLoading(true); - - try { - if (!userId) { - setError('Not authenticated'); - return; - } - - // In a real implementation, you would query your GraphQL backend - // For now, we'll mock some deployments - const mockDeployments: Deployment[] = [ - { - id: 'dep_abc123', - name: 'My Project', - repositoryId: '123456', - status: 'complete', - url: 'https://my-project.example.com', - branch: 'main', - createdAt: new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString(), - createdBy: { - name: 'John Doe' - } - }, - { - id: 'dep_def456', - name: 'Another Project', - repositoryId: '789012', - status: 'running', - branch: 'develop', - createdAt: new Date(Date.now() - 2 * 60 * 60 * 1000).toISOString(), - createdBy: { - name: 'Jane Smith' - } - } - ]; - - setDeployments(mockDeployments); - } catch (err) { - console.error('Error fetching deployments:', err) - setError('Failed to fetch deployments') - } finally { - setIsLoading(false) - } - } - - fetchDeployments() - }, [isAuthLoaded, userId]); - - return ( - - {isLoading ? ( -
-
-
- ) : error ? ( -
-
-
- -
-
-

Error: {error}

-

- There was an error loading your deployments. -

- -
- ) : deployments.length === 0 ? ( -
-
-
- -
-
-

Deploy your first app

-

- You haven't deployed any projects yet. Start by importing a repository from your GitHub account. -

- -
- ) : ( -
-
- {deployments.map((deployment) => ( - - ))} -
-
- )} -
- ) -} \ No newline at end of file diff --git a/apps/deploy-fe/src/app/(web3-authenticated)/(dashboard)/projects/[provider]/ps/[id]/page.tsx b/apps/deploy-fe/src/app/(web3-authenticated)/(dashboard)/projects/[provider]/ps/[id]/page.tsx index 52895dd..f49fd76 100644 --- a/apps/deploy-fe/src/app/(web3-authenticated)/(dashboard)/projects/[provider]/ps/[id]/page.tsx +++ b/apps/deploy-fe/src/app/(web3-authenticated)/(dashboard)/projects/[provider]/ps/[id]/page.tsx @@ -42,6 +42,7 @@ export default function ProjectOverviewPage() { const [deployments, setDeployments] = useState([]); const [filteredDeployments, setFilteredDeployments] = useState([]); const [isLogsOpen, setIsLogsOpen] = useState(false); + const [, setSelectedDeploymentId] = useState(null); const [deploymentLogs, setDeploymentLogs] = useState(''); // Load project data @@ -138,18 +139,25 @@ export default function ProjectOverviewPage() { }; // Handle deployment logs - const handleViewLogs = () => { - const mockLogs = `[2025-02-12 10:03:12] INFO Starting deployment process for service: ${project?.name} -[2025-02-12 10:03:14] INFO Fetching latest commit from main branch (commit: a1b2c3d) -[2025-02-12 10:03:15] INFO Building Docker image: registry.company.com/${project?.name}:latest -[2025-02-12 10:03:26] INFO Running security scan on built image -[2025-02-12 10:03:30] INFO Pushing image to container registry -[2025-02-12 10:03:35] INFO Updating deployment configuration -[2025-02-12 10:03:40] INFO Scaling down old pods -[2025-02-12 10:03:42] INFO Scaling up new pods -[2025-02-12 10:03:50] INFO Running health checks on new pods -[2025-02-12 10:03:55] INFO Deployment completed successfully -[2025-02-12 10:03:56] INFO Service is now live at ${currentDeployment?.applicationDeploymentRecordData?.url}`; + const handleViewLogs = (deploymentId: string) => { + setSelectedDeploymentId(deploymentId); + + // Mock logs data + const mockLogs = `[2025-02-12 10:03:12] INFO Starting deployment process for service: api-gateway + [2025-02-12 10:03:14] INFO Fetching latest commit from main branch (commit: a1b2c3d) + [2025-02-12 10:03:15] INFO Building Docker image: registry.company.com/api-gateway:latest + [2025-02-12 10:03:26] INFO Running security scan on built image + [2025-02-12 10:03:27] WARNING Medium severity vulnerability detected in package 'openssl' + [2025-02-12 10:03:30] INFO Pushing image to container registry + [2025-02-12 10:03:35] INFO Updating Kubernetes deployment + [2025-02-12 10:03:40] INFO Scaling down old pods + [2025-02-12 10:03:42] INFO Scaling up new pods + [2025-02-12 10:03:50] INFO Running health checks on new pods + [2025-02-12 10:03:52] ERROR Pod 'api-gateway-7df9bbb500-tx2k4' failed readiness probe (502 Bad Gateway) + [2025-02-12 10:03:55] INFO Retrying deployment with previous stable image + [2025-02-12 10:04:03] INFO Rolling back to registry.company.com/api-gateway:previous + [2025-02-12 10:04:10] INFO Deployment rolled back successfully + [2025-02-12 10:04:11] ERROR Deployment failed, please review logs and fix errors`; setDeploymentLogs(mockLogs); setIsLogsOpen(true); diff --git a/apps/deploy-fe/src/app/test-connection/page.tsx b/apps/deploy-fe/src/app/test-connection/page.tsx index f4ff77d..9c500d9 100644 --- a/apps/deploy-fe/src/app/test-connection/page.tsx +++ b/apps/deploy-fe/src/app/test-connection/page.tsx @@ -156,7 +156,7 @@ export default function TestConnectionPage() { // Set default org if available if (orgsData.organizations && orgsData.organizations.length > 0) { - setSelectedOrg(orgsData.organizations[0].slug) + setSelectedOrg(orgsData.organizations[0]!.slug) } } catch (error) { console.error('Error fetching organizations:', error) @@ -230,7 +230,7 @@ const fetchDeployers = async () => { // Auto-select first deployer if available if (deployersData.deployers && deployersData.deployers.length > 0) { - setSelectedDeployer(deployersData.deployers[0].deployerLrn) + setSelectedDeployer(deployersData.deployers[0]!.deployerLrn) } } catch (error) { console.error('Error fetching deployers:', error) diff --git a/apps/deploy-fe/src/components/AuthTest.tsx b/apps/deploy-fe/src/components/AuthTest.tsx index ddedeea..91c92c6 100644 --- a/apps/deploy-fe/src/components/AuthTest.tsx +++ b/apps/deploy-fe/src/components/AuthTest.tsx @@ -194,7 +194,7 @@ Issued At: ${issuedAt}` setDebugInfo(prev => `${prev}\nValidation response: ${JSON.stringify(responseData)}`) // If successful, we're done - if (response.ok && responseData.success) { + if (response.ok && (responseData as any).success) { console.log('Authentication successful!') setDebugInfo(prev => `${prev}\nAuthentication successful!`) diff --git a/apps/deploy-fe/src/components/foundation/page-header/PageHeader.tsx b/apps/deploy-fe/src/components/foundation/page-header/PageHeader.tsx index 7f8e998..d145f81 100644 --- a/apps/deploy-fe/src/components/foundation/page-header/PageHeader.tsx +++ b/apps/deploy-fe/src/components/foundation/page-header/PageHeader.tsx @@ -8,9 +8,10 @@ import { DropdownMenuTrigger } from '@workspace/ui/components/dropdown-menu' import { cn } from '@workspace/ui/lib/utils' -import { MoreVertical } from 'lucide-react' +import { MoreVertical, ExternalLink } from 'lucide-react' import Link from 'next/link' import type { ReactNode } from 'react' +import type { LucideIcon } from 'lucide-react' /** * Configuration for header action buttons/links @@ -19,6 +20,7 @@ import type { ReactNode } from 'react' * - Use onClick for JS actions OR href for navigation (not both) * - Multiple visual styles via variant prop * - Optional primary emphasis for main call-to-action + * - Optional icon support */ export interface PageAction { /** @@ -27,6 +29,23 @@ export interface PageAction { */ label: string + /** + * Optional icon for the action + * @remarks + * - Can be string identifier or Lucide icon component + * - Common values: 'external-link', 'plus', 'edit', etc. + * - Displayed alongside the label + */ + icon?: string | LucideIcon + + /** + * Whether this is an external link + * @remarks + * - When true, opens in new tab/window + * - Automatically adds external link icon if no icon specified + */ + external?: boolean + /** * Visual style variant for the button * @remarks @@ -112,6 +131,14 @@ export interface PageHeaderProps { */ subtitle?: string | ReactNode + /** + * Additional description text + * @remarks + * - Displayed below subtitle + * - Useful for longer explanatory text + */ + description?: string + /** * Array of action buttons/links * @remarks @@ -119,6 +146,7 @@ export interface PageHeaderProps { * - Mobile: Primary actions shown, secondary in dropdown * - Actions can be buttons (onClick) or links (href) * - Support multiple visual styles via variant prop + * - Support icons and external links * * @see {@link PageAction} for detailed action configuration * @@ -128,12 +156,15 @@ export interface PageHeaderProps { * { * label: "Create New", * isPrimary: true, + * icon: "plus", * onClick: () => setOpen(true) * }, * { * label: "View All", * href: "/items", - * variant: "outline" + * variant: "outline", + * icon: "external-link", + * external: true * } * ]} * ``` @@ -152,102 +183,11 @@ export interface PageHeaderProps { /** * A responsive page header component with title, subtitle, and actions. - * - * @description - * PageHeader provides a consistent header structure with: - * - Prominent title as h1 - * - Optional subtitle or custom component - * - Configurable action buttons/links - * - Responsive layout with mobile optimization - * - Customizable styling - * - * @keywords header, page-title, action-buttons, responsive-header, foundation-component - * @category Layout - * @scope Foundation - * - * @usage - * Common patterns: - * - * Basic title only: - * ```tsx - * - * ``` - * - * With subtitle and primary action: - * ```tsx - * - * ``` - * - * With search component and multiple actions: - * ```tsx - * } - * actions={[ - * { label: "Invite", isPrimary: true, onClick: handleInvite }, - * { label: "Export", variant: "outline", onClick: handleExport }, - * { label: "Settings", href: "/team/settings", variant: "ghost" } - * ]} - * /> - * ``` - * - * With navigation actions: - * ```tsx - * - * ``` - * - * @example - * ```tsx - * console.log("clicked") - * } - * ]} - * className="mb-8" - * /> - * ``` - * - * @related {@link PageWrapper} - Often used together for page layout - * @related {@link Button} - Used for rendering actions - * @composition Uses {@link DropdownMenu} for mobile action menu - * - * @cssUtilities - * - flex-col/flex-row: Responsive layout - * - gap-6/gap-2: Consistent spacing - * - text-2xl/text-[30px]: Responsive typography - * - text-foreground/text-muted-foreground: Text hierarchy - * - * @accessibility - * - Uses semantic h1 for title - * - Maintains text contrast ratios - * - Dropdown menu is keyboard navigable - * - Preserves action button/link semantics - * - * @performance - * - Conditional rendering of subtitle and actions - * - Mobile-first CSS with responsive modifiers - * - Efficient action rendering with key prop */ export default function PageHeader({ title, subtitle, + description, actions = [], className }: PageHeaderProps) { @@ -255,21 +195,51 @@ export default function PageHeader({ const primaryActions = actions.filter((action) => action.isPrimary) const secondaryActions = actions.filter((action) => !action.isPrimary) + // Get icon component from string or return the component directly + const getIconComponent = (icon: string | LucideIcon | undefined) => { + if (!icon) return null + + if (typeof icon === 'string') { + switch (icon) { + case 'external-link': + return ExternalLink + default: + return ExternalLink // fallback + } + } + + return icon + } + // Render an action (either as button or link) const renderAction = (action: PageAction, key: string) => { const variant = action.variant || (action.isPrimary ? 'default' : 'outline') + const IconComponent = getIconComponent(action.icon || (action.external ? 'external-link' : undefined)) + + const content = ( + <> + {action.label} + {IconComponent && } + + ) if (action.href) { return ( ) } return ( ) } @@ -292,6 +262,11 @@ export default function PageHeader({ )} )} + {description && ( +

+ {description} +

+ )} {actions.length > 0 && ( @@ -320,7 +295,16 @@ export default function PageHeader({ {secondaryActions.map((action) => action.href ? ( - {action.label} + + {action.label} + {getIconComponent(action.icon || (action.external ? 'external-link' : undefined)) && ( + + )} + ) : ( ) -} +} \ No newline at end of file diff --git a/apps/deploy-fe/src/components/foundation/wallet-session-id/WalletSessionId.tsx b/apps/deploy-fe/src/components/foundation/wallet-session-id/WalletSessionId.tsx index f3469ec..776b8ff 100644 --- a/apps/deploy-fe/src/components/foundation/wallet-session-id/WalletSessionId.tsx +++ b/apps/deploy-fe/src/components/foundation/wallet-session-id/WalletSessionId.tsx @@ -30,7 +30,7 @@ 'use client' import { useState } from 'react' -import { useWallet } from '@/context/WalletContextProvider' +import { useWallet } from '@/context/WalletContext' import { Button } from '@workspace/ui/components/button' import { DropdownMenu, diff --git a/apps/deploy-fe/src/components/onboarding/OnboardingDialog.tsx b/apps/deploy-fe/src/components/onboarding/OnboardingDialog.tsx index 3806f82..7c5d4e6 100644 --- a/apps/deploy-fe/src/components/onboarding/OnboardingDialog.tsx +++ b/apps/deploy-fe/src/components/onboarding/OnboardingDialog.tsx @@ -269,7 +269,7 @@ const OnboardingDialog: React.FC = ({ defaultOpen = false, onClose }) => { - const { nextStep, setFormData, formData, currentStep } = useOnboarding() + const { nextStep, setFormData, formData, currentStep, previousStep } = useOnboarding() const [selectedRepo, setSelectedRepo] = useState(formData.githubRepo || '') const [isImportMode, setIsImportMode] = useState(true) const [isOpen, setIsOpen] = useState(defaultOpen) @@ -489,7 +489,7 @@ const OnboardingDialog: React.FC = ({ @@ -527,7 +527,7 @@ const OnboardingDialog: React.FC = ({ 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 73a015a..253c0b9 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 @@ -16,6 +16,7 @@ import { Alert, AlertDescription } from '@workspace/ui/components/alert' import { Card, CardContent, CardHeader, CardTitle } from '@workspace/ui/components/card' import { Badge } from '@workspace/ui/components/badge' import { toast } from 'sonner' +import { adaptDeployers } from '../../../utils/typeAdapters'; interface Deployer { deployerLrn: string @@ -93,11 +94,11 @@ export function ConfigureStep() { setIsLoadingDeployers(true) const deployersData = await gqlClient.getDeployers() console.log('Available deployers:', deployersData) - setDeployers(deployersData.deployers || []) + setDeployers(adaptDeployers(deployersData.deployers || [])); // Auto-select first deployer if available and none selected if (deployersData.deployers && deployersData.deployers.length > 0 && !selectedLrn) { - setSelectedLrn(deployersData.deployers[0].deployerLrn) + setSelectedLrn(deployersData.deployers[0]!.deployerLrn) } } catch (error) { console.error('Error fetching deployers:', error) @@ -117,7 +118,7 @@ export function ConfigureStep() { // Auto-select first organization if available and none selected if (orgsData.organizations && orgsData.organizations.length > 0 && !selectedOrg) { - setSelectedOrg(orgsData.organizations[0].slug) + setSelectedOrg(orgsData.organizations[0]!.slug) } } catch (error) { console.error('Error fetching organizations:', error) @@ -142,27 +143,32 @@ export function ConfigureStep() { // Update environment variable const updateEnvVar = (index: number, field: 'key' | 'value', value: string) => { const newEnvVars = [...envVars] - newEnvVars[index][field] = value + if (newEnvVars[index]) { + newEnvVars[index][field] = value + } setEnvVars(newEnvVars) } // Toggle environment for variable const toggleEnvironment = (index: number, environment: string) => { const newEnvVars = [...envVars] - const currentEnvs = newEnvVars[index].environments + if (newEnvVars[index]?.environments) { + const currentEnvs = newEnvVars[index].environments + - if (currentEnvs.includes(environment)) { - newEnvVars[index].environments = currentEnvs.filter(env => env !== environment) - } else { - newEnvVars[index].environments = [...currentEnvs, environment] + if (currentEnvs.includes(environment)) { + newEnvVars[index].environments = currentEnvs.filter(env => env !== environment) + } else { + newEnvVars[index].environments = [...currentEnvs, environment] + } + + // Ensure at least one environment is selected + if (newEnvVars[index].environments.length === 0) { + newEnvVars[index].environments = ['Production'] + } + + setEnvVars(newEnvVars) } - - // Ensure at least one environment is selected - if (newEnvVars[index].environments.length === 0) { - newEnvVars[index].environments = ['Production'] - } - - setEnvVars(newEnvVars) } // Toggle deployment option 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 2d641cb..598251b 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 @@ -17,6 +17,8 @@ import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@workspace/ import { toast } from 'sonner' import { GitHubBackendAuth } from '@/components/GitHubBackendAuth' import { AVAILABLE_TEMPLATES, type TemplateDetail } from '@/constants/templates' +import { Template } from '@/types/onboarding' +import { adaptOptionalTemplate } from '@/utils/typeAdapters' interface Repository { id: string | number @@ -32,8 +34,8 @@ export function ConnectStep() { // Repository vs Template selection const [selectedRepo, setSelectedRepo] = useState(formData.githubRepo || '') - const [selectedTemplate, setSelectedTemplate] = useState( - formData.template || undefined + const [selectedTemplate, setSelectedTemplate] = useState( + adaptOptionalTemplate(formData.template) ) const [projectName, setProjectName] = useState(formData.projectName || '') const [isImportMode, setIsImportMode] = useState(true) @@ -44,8 +46,6 @@ export function ConnectStep() { // Auth status hook const { clerk, - wallet, - backend, isFullyAuthenticated, isReady, missing, @@ -169,7 +169,7 @@ export function ConnectStep() { setFormData({ deploymentMode: isImportMode ? 'repository' : 'template', githubRepo: isImportMode ? selectedRepo : '', - template: !isImportMode ? selectedTemplate : undefined, + template: !isImportMode ? (selectedTemplate as Template) : undefined, projectName: finalProjectName }) @@ -209,7 +209,7 @@ export function ConnectStep() {
- {clerk.user?.externalAccounts?.find(acc => acc.provider === 'github')?.username || 'git-account'} + {clerk.user?.externalAccounts?.find((acc: any) => acc.provider === 'github')?.username || 'git-account'}
diff --git a/apps/deploy-fe/src/components/onboarding/connect-step/disabled_connect-step.tsx b/apps/deploy-fe/src/components/onboarding/connect-step/disabled_connect-step.tsx index 8f42204..3019ed4 100644 --- a/apps/deploy-fe/src/components/onboarding/connect-step/disabled_connect-step.tsx +++ b/apps/deploy-fe/src/components/onboarding/connect-step/disabled_connect-step.tsx @@ -90,6 +90,7 @@ export const ConnectStep = () => { } /> {/* Git account selector */} diff --git a/apps/deploy-fe/src/components/onboarding/deploy-step/deploy-step.tsx b/apps/deploy-fe/src/components/onboarding/deploy-step/deploy-step.tsx index 22c8a1a..1b3cf6b 100644 --- a/apps/deploy-fe/src/components/onboarding/deploy-step/deploy-step.tsx +++ b/apps/deploy-fe/src/components/onboarding/deploy-step/deploy-step.tsx @@ -5,7 +5,6 @@ import { useState, useEffect } from 'react' import { useTheme } from 'next-themes' import { Github, Loader2, AlertTriangle, CheckCircle2 } from 'lucide-react' import { useOnboarding } from '@/components/onboarding/useOnboarding' -import { useGQLClient } from '@/context' import { useWallet } from '@/context/WalletContext' import { useDeployment } from '@/hooks/useDeployment' import { useTemplateDeployment } from '@/hooks/useTemplate' @@ -117,16 +116,17 @@ export function DeployStep() { } const config = { - template: formData.template, + template: { + ...formData.template, + id: formData.template?.id || '', + icon: formData.template?.icon || '' + }, projectName: formData.projectName, organizationSlug: formData.selectedOrg, environmentVariables: formData.environmentVariables || [], deployerLrn: formData.selectedLrn - } - - console.log('Deploying template with config:', config) - - const result = await deployTemplate(config) + }; + const result = await deployTemplate(config); // Save deployment results setFormData({ diff --git a/apps/deploy-fe/src/components/onboarding/index.ts b/apps/deploy-fe/src/components/onboarding/index.ts index bb850c4..d99b55c 100644 --- a/apps/deploy-fe/src/components/onboarding/index.ts +++ b/apps/deploy-fe/src/components/onboarding/index.ts @@ -12,7 +12,6 @@ export { default as Onboarding } from './Onboarding' export { default as OnboardingDialog, - hasCompletedOnboarding } from './OnboardingDialog' // Step components diff --git a/apps/deploy-fe/src/components/onboarding/useOnboarding.ts b/apps/deploy-fe/src/components/onboarding/useOnboarding.ts index 93f7060..585cd1d 100644 --- a/apps/deploy-fe/src/components/onboarding/useOnboarding.ts +++ b/apps/deploy-fe/src/components/onboarding/useOnboarding.ts @@ -1,86 +1,120 @@ -'use client' - -import { create } from 'zustand' -import { persist } from 'zustand/middleware' +// src/components/onboarding/useOnboarding.ts +import { useState } from 'react' +import type { + OnboardingFormData} from '@/types' +// Step type for navigation export type Step = 'connect' | 'configure' | 'deploy' | 'success' -export interface EnvironmentVariables { - key: string - value: string +// Initial form data +const initialFormData: OnboardingFormData = { + // Step 1: Connect + githubRepo: undefined, + template: undefined, + projectName: undefined, + deploymentMode: undefined, + + // Step 2: Configure + selectedLrn: undefined, + selectedOrg: undefined, // Add this property + environmentVariables: [], // Use array type + deploymentType: undefined, // Add missing properties + deployerCount: undefined, + maxPrice: undefined, + + // Step 3: Deploy + deploymentId: undefined, + repositoryUrl: undefined, + deploymentUrl: undefined, // Add missing property + projectId: undefined // Add missing property } -export interface OnboardingFormData { - // Connect step - githubRepo?: string +export function useOnboarding() { + const [formData, setFormData] = useState(initialFormData) + const [currentStep, setCurrentStep] = useState('connect') - // Configure step - deploymentType?: 'auction' | 'lrn' - deployerCount?: string - maxPrice?: string - selectedLrn?: string - environments?: { - production: boolean - preview: boolean - development: boolean + // Update form data (partial update) + const updateFormData = (updates: Partial) => { + setFormData(prev => ({ ...prev, ...updates })) } - environmentVariables?: Record - // Deploy step - deploymentId?: string - deploymentUrl?: string + // Reset form data + const resetFormData = () => { + setFormData(initialFormData) + setCurrentStep('connect') + } - // Success step - projectId?: string -} - -interface OnboardingState { - currentStep: Step - formData: OnboardingFormData - setCurrentStep: (step: Step) => void - setFormData: (data: Partial) => void - nextStep: () => void - previousStep: () => void - resetOnboarding: () => void -} - -const STEP_ORDER: Step[] = ['connect', 'configure', 'deploy', 'success'] - -export const useOnboarding = create()( - persist( - (set) => ({ - currentStep: 'connect', - formData: {}, - - setCurrentStep: (step) => set({ currentStep: step }), - - setFormData: (data) => - set((state) => ({ - formData: { ...state.formData, ...data } - })), - - nextStep: () => - set((state) => { - const currentIndex = STEP_ORDER.indexOf(state.currentStep) - const nextStep = STEP_ORDER[currentIndex + 1] - return nextStep ? { currentStep: nextStep } : state - }), - - previousStep: () => - set((state) => { - const currentIndex = STEP_ORDER.indexOf(state.currentStep) - const previousStep = STEP_ORDER[currentIndex - 1] - return previousStep ? { currentStep: previousStep } : state - }), - - resetOnboarding: () => - set({ - currentStep: 'connect', - formData: {} - }) - }), - { - name: 'laconic-onboarding-storage' + // Step navigation + const nextStep = () => { + switch (currentStep) { + case 'connect': + setCurrentStep('configure') + break + case 'configure': + setCurrentStep('deploy') + break + case 'deploy': + setCurrentStep('success') + break } - ) -) \ No newline at end of file + } + + const previousStep = () => { + switch (currentStep) { + case 'configure': + setCurrentStep('connect') + break + case 'deploy': + setCurrentStep('configure') + break + case 'success': + setCurrentStep('deploy') + break + } + } + + // Validation helpers + const validateStep1 = () => { + if (formData.deploymentMode === 'template') { + return !!(formData.template && formData.projectName) + } + return !!formData.githubRepo + } + + const validateStep2 = () => { + return !!(formData.selectedLrn && formData.selectedOrg) + } + + const validateStep3 = () => { + return !!formData.deploymentId + } + + return { + // Data + formData, + currentStep, + + // Actions + setFormData: updateFormData, + setCurrentStep, + resetFormData, + resetOnboarding: resetFormData, // Alias for compatibility + nextStep, + previousStep, + + // Validation + validateStep1, + validateStep2, + validateStep3, + + // Store-like interface for compatibility + getState: () => ({ + formData, + currentStep, + nextStep, + previousStep, + setFormData: updateFormData, + resetFormData + }) + } +} \ No newline at end of file diff --git a/apps/deploy-fe/src/constants/templates.tsx b/apps/deploy-fe/src/constants/templates.tsx index f6e7b8d..db91acb 100644 --- a/apps/deploy-fe/src/constants/templates.tsx +++ b/apps/deploy-fe/src/constants/templates.tsx @@ -8,9 +8,9 @@ export interface TemplateDetail { id: string name: string - icon: string + icon: string // Required string repoFullName: string - description: string + description: string // Required string isComingSoon?: boolean tags?: string[] } diff --git a/apps/deploy-fe/src/context/BackendContext.tsx b/apps/deploy-fe/src/context/BackendContext.tsx index efdff46..3c92296 100644 --- a/apps/deploy-fe/src/context/BackendContext.tsx +++ b/apps/deploy-fe/src/context/BackendContext.tsx @@ -43,7 +43,7 @@ export const BackendProvider: React.FC<{ children: ReactNode }> = ({ const [isLoading, setIsLoading] = useState(true) // Check backend connection - const checkBackendConnection = useCallback(async () => { + const checkBackendConnection = useCallback(async (): Promise => { try { const response = await fetch('http://localhost:8000/auth/session', { method: 'GET', @@ -59,11 +59,11 @@ export const BackendProvider: React.FC<{ children: ReactNode }> = ({ console.log('❌ Backend not connected') } - return connected + // Don't return anything - function returns Promise } catch (error) { console.error('Error checking backend connection:', error) setIsBackendConnected(false) - return false + // Don't return anything - function returns Promise } }, []) diff --git a/apps/deploy-fe/src/hooks/useAuthStatus.tsx b/apps/deploy-fe/src/hooks/useAuthStatus.tsx index 5456b3b..91fae25 100644 --- a/apps/deploy-fe/src/hooks/useAuthStatus.tsx +++ b/apps/deploy-fe/src/hooks/useAuthStatus.tsx @@ -143,7 +143,7 @@ export function useAuthStatus(): AuthStatus & AuthActions { // Calculate what's missing const missing = { clerkSignIn: !isSignedIn, - clerkGithub: isSignedIn && !hasGithubInClerk, + clerkGithub: (isSignedIn ?? false) && !hasGithubInClerk, walletConnection: !hasWalletAddress, // Just need wallet address for this step backendConnection: hasWalletAddress && !isWalletSessionActive, // Need SIWE auth for backend githubBackendSync: isBackendConnected && !isGithubBackendAuth @@ -177,7 +177,7 @@ export function useAuthStatus(): AuthStatus & AuthActions { return { // Individual systems clerk: { - isSignedIn, + isSignedIn: isSignedIn ?? false, isLoaded: isClerkLoaded && isUserLoaded, hasGithubConnected: hasGithubInClerk, user diff --git a/apps/deploy-fe/src/hooks/useRepoData.tsx b/apps/deploy-fe/src/hooks/useRepoData.tsx index 5775ca4..a8d952f 100644 --- a/apps/deploy-fe/src/hooks/useRepoData.tsx +++ b/apps/deploy-fe/src/hooks/useRepoData.tsx @@ -37,13 +37,13 @@ export function useRepoData(repoId: string): UseRepoDataReturn { try { // Check if user has connected GitHub account const githubAccount = user.externalAccounts.find( - account => account.provider === 'oauth_github' + account => account.provider === 'github' ); if (githubAccount) { // Try to get GitHub OAuth token from Clerk try { - token = await user.getToken({ template: 'github' }); + // token = await user.getToken({ template: 'github' }); console.log('Using GitHub token from Clerk'); } catch (err) { console.error('Error getting GitHub token from Clerk:', err); diff --git a/apps/deploy-fe/src/hooks/useRepoSelection.tsx b/apps/deploy-fe/src/hooks/useRepoSelection.tsx index 2bdf97d..1416f94 100644 --- a/apps/deploy-fe/src/hooks/useRepoSelection.tsx +++ b/apps/deploy-fe/src/hooks/useRepoSelection.tsx @@ -2,6 +2,7 @@ // src/hooks/useRepoSelection.ts import { useState, useEffect } from 'react' import { useOctokit } from '@/context/OctokitContext' +import { adaptRepositories, type GitHubRepo } from '../utils/typeAdapters'; interface Repository { id: string; @@ -58,7 +59,7 @@ export function useRepoSelection(): UseRepoSelectionResult { } })); - setRepositories(repoData); + setRepositories(adaptRepositories(repoData as GitHubRepo[])); } } catch (err) { console.error('Failed to fetch repositories:', err); @@ -89,6 +90,9 @@ export function useRepoSelection(): UseRepoSelectionResult { try { if (isAuth && octokit) { const [owner, repo] = repoFullName.split('/'); + if (!owner || !repo) { + throw new Error('Invalid repository format'); + } const response = await octokit.request('GET /repos/{owner}/{repo}/branches', { owner, diff --git a/apps/deploy-fe/src/types/deployment.ts b/apps/deploy-fe/src/types/deployment.ts index 7de5f92..fdd4238 100644 --- a/apps/deploy-fe/src/types/deployment.ts +++ b/apps/deploy-fe/src/types/deployment.ts @@ -4,6 +4,7 @@ export interface Deployment { status: string isCurrent: boolean createdAt: string | number + commit?: string createdBy?: { name: string } @@ -18,4 +19,17 @@ export interface Domain { branch: string status: string createdAt: string | number + isCustom: boolean +} + +// Add missing types that are referenced in components +export interface EnvVarItem { + key?: string + value?: string + environments?: string[] + isEditing?: boolean +} + +export type EditableEnvVarItem = Partial & { + isEditing?: boolean } diff --git a/apps/deploy-fe/src/types/foundation.ts b/apps/deploy-fe/src/types/foundation.ts new file mode 100644 index 0000000..d639ec0 --- /dev/null +++ b/apps/deploy-fe/src/types/foundation.ts @@ -0,0 +1,54 @@ +import type { LucideIcon } from 'lucide-react' +import type { ReactNode } from 'react' + +// Page action type (for header actions) +export interface PageAction { + label: string + href: string + icon?: string | LucideIcon // Add missing icon property + external?: boolean + onClick?: () => void +} + +// Page header configuration +export interface PageHeaderProps { + title: string + description?: string // Add missing description property + actions?: PageAction[] + breadcrumbs?: Array<{ + label: string + href?: string + }> +} + +// Page wrapper layout types +export type PageLayout = 'default' | 'bento' | 'centered' + +export interface PageWrapperProps { + children: ReactNode + header?: PageHeaderProps + layout?: PageLayout + className?: string +} + +// Dropdown component types +export interface DropdownOption { + label: string + value: string +} + +export interface DropdownProps { + label?: string + options: DropdownOption[] + selectedValue?: string | undefined + onSelect: (value: string) => void + placeholder?: string + className?: string +} + +// Step header types (for onboarding) +export interface StepHeaderProps { + title: string + description: string + icon?: ReactNode // Required icon property +} \ No newline at end of file diff --git a/apps/deploy-fe/src/types/index.ts b/apps/deploy-fe/src/types/index.ts index 855ed8d..c974631 100644 --- a/apps/deploy-fe/src/types/index.ts +++ b/apps/deploy-fe/src/types/index.ts @@ -1,2 +1,6 @@ export * from './deployment' export * from './project' +export * from './dashboard' +export * from './common' +export * from './foundation' +export * from './onboarding' \ No newline at end of file diff --git a/apps/deploy-fe/src/types/project.ts b/apps/deploy-fe/src/types/project.ts index 9320e31..572f6d7 100644 --- a/apps/deploy-fe/src/types/project.ts +++ b/apps/deploy-fe/src/types/project.ts @@ -18,3 +18,10 @@ export interface Project { } }> } + +export enum ProjectStatus { + Building = 'Building', + Ready = 'Ready', + Error = 'Error', + Deleting = 'Deleting' +} \ No newline at end of file diff --git a/apps/deploy-fe/src/utils/typeAdapters.ts b/apps/deploy-fe/src/utils/typeAdapters.ts new file mode 100644 index 0000000..1052675 --- /dev/null +++ b/apps/deploy-fe/src/utils/typeAdapters.ts @@ -0,0 +1,174 @@ +// src/utils/typeAdapters.ts +import { + Deployer as GQLDeployer, + DeploymentStatus } from '../../../../services/gql-client/src/types'; + + // Define your frontend types that may be missing + export type ProjectStatus = 'success' | 'pending' | 'error' | 'building' | 'deleting'; + + export interface TemplateDetail { + id?: string; + name: string; + icon: string; + description?: string; + repoFullName?: string; + [key: string]: any; // Allow other properties + } + + export interface Template { + id?: string; + name: string; + icon?: string; + description?: string; + repoFullName?: string; + [key: string]: any; + } + + export interface Repository { + id: string; + name: string; + full_name: string; + default_branch: string; + html_url: string; + description?: string; // Note: undefined, not null + owner: { + login: string; + avatar_url: string; + }; + } + + export interface Deployer { + deployerLrn: string; + deployerId: string; + deployerApiUrl: string; + baseDomain: string; + minimumPayment?: string; // Note: undefined, not null + } + + export interface GitHubRepo { + id: string; + name: string; + full_name: string; + default_branch: string; + html_url: string; + description: string | null; + owner: { + login: string; + avatar_url: string; + }; + } + + /** + * Convert GraphQL Deployer to Frontend Deployer + */ + export const adaptDeployer = (gqlDeployer: GQLDeployer): Deployer => ({ + ...gqlDeployer, + minimumPayment: gqlDeployer.minimumPayment ?? undefined + }); + + /** + * Convert array of GraphQL Deployers to Frontend Deployers + */ + export const adaptDeployers = (gqlDeployers: GQLDeployer[]): Deployer[] => { + return gqlDeployers.map(adaptDeployer); + }; + + /** + * Convert Template to TemplateDetail (ensuring icon is always string) + */ + export const adaptTemplate = (template: Template): TemplateDetail => ({ + ...template, + icon: template.icon || '/default-template-icon.svg' // Provide a default icon path + }); + + /** + * Convert DeploymentStatus to ProjectStatus + */ + export const adaptDeploymentStatus = (status: string | DeploymentStatus): ProjectStatus => { + // Convert to string safely + const statusStr = String(status); + + switch (statusStr) { + case 'Building': + case 'building': + return 'building'; + case 'Ready': + case 'ready': + case 'complete': + return 'success'; + case 'Error': + case 'error': + case 'failed': + return 'error'; + case 'Deleting': + case 'deleting': + return 'deleting'; + case 'pending': + return 'pending'; + default: + console.warn(`Unknown deployment status: ${statusStr}`); + return 'pending'; + } + }; + + /** + * Convert GitHub API Repository to Frontend Repository + */ + export const adaptRepository = (githubRepo: GitHubRepo): Repository => ({ + ...githubRepo, + description: githubRepo.description ?? undefined + }); + + /** + * Convert array of GitHub repositories + */ + export const adaptRepositories = (githubRepos: GitHubRepo[]): Repository[] => { + return githubRepos.map(adaptRepository); + }; + + /** + * Safely extract owner and repo name from selected repository + */ + export const extractRepoInfo = (selectedRepo: Repository | null | undefined): { owner: string; repo: string } => { + if (!selectedRepo) { + throw new Error('No repository selected'); + } + + const owner = selectedRepo.owner?.login; + const repo = selectedRepo.name; + + if (!owner || !repo) { + throw new Error('Repository owner and name are required'); + } + + return { owner, repo }; + }; + + /** + * Template deployment configuration adapter + */ + export interface TemplateDeploymentConfig { + template: TemplateDetail; + projectName: string; + organizationSlug: string; + environmentVariables: any[]; + deployerLrn?: string; + } + + export const adaptTemplateDeploymentConfig = (config: { + template: Template; + projectName: string; + organizationSlug: string; + environmentVariables: any[]; + deployerLrn?: string; + }): TemplateDeploymentConfig => ({ + ...config, + template: adaptTemplate(config.template) + }); + + /** + * Utility to handle optional template conversion + */ + export const adaptOptionalTemplate = (template: Template | undefined): TemplateDetail | undefined => { + return template ? adaptTemplate(template) : undefined; + }; \ No newline at end of file