From 7cb0a0048b24f23e44deb03b1ed5987c03ce9212 Mon Sep 17 00:00:00 2001 From: Your Name Date: Thu, 21 Aug 2025 16:20:52 +0000 Subject: [PATCH] working --- .../[provider]/ps/(create)/cr/page.tsx | 310 +++++++++++---- apps/deploy-fe/src/app/layout.tsx | 11 +- .../src/components/onboarding/Onboarding.tsx | 2 +- .../configure-step/configure-step.tsx | 358 ++++++++++++------ .../onboarding/connect-step/connect-step.tsx | 310 +++++++++------ .../onboarding/deploy-step/deploy-step.tsx | 306 ++++++++++----- .../src/components/onboarding/index.ts | 6 +- .../src/components/onboarding/types.ts | 2 +- pnpm-lock.yaml | 50 ++- 9 files changed, 903 insertions(+), 452 deletions(-) diff --git a/apps/deploy-fe/src/app/(web3-authenticated)/(dashboard)/projects/[provider]/ps/(create)/cr/page.tsx b/apps/deploy-fe/src/app/(web3-authenticated)/(dashboard)/projects/[provider]/ps/(create)/cr/page.tsx index 8fb1258..89b36c1 100644 --- a/apps/deploy-fe/src/app/(web3-authenticated)/(dashboard)/projects/[provider]/ps/(create)/cr/page.tsx +++ b/apps/deploy-fe/src/app/(web3-authenticated)/(dashboard)/projects/[provider]/ps/(create)/cr/page.tsx @@ -1,15 +1,15 @@ 'use client' -import { useEffect, useState } from 'react' -import { useRouter } from 'next/navigation' -import { X } from 'lucide-react' -import { useTheme } from 'next-themes' -import { useOnboarding } from '@/components/onboarding/store' -import { ConnectStep } from '@/components/onboarding/connect-step/connect-step' +import { LaconicMark } from '@/components/assets/laconic-mark' import { ConfigureStep } from '@/components/onboarding/configure-step/configure-step' +import { ConnectStep } from '@/components/onboarding/connect-step/connect-step' import { DeployStep } from '@/components/onboarding/deploy-step/deploy-step' import { SuccessStep } from '@/components/onboarding/success-step/success-step' -import { LaconicMark } from '@/components/assets/laconic-mark' +import { useOnboarding } from '@/components/onboarding/useOnboarding' +import { X } from 'lucide-react' +import { useTheme } from 'next-themes' +import { useRouter } from 'next/navigation' +import { useEffect, useState } from 'react' /** * Parent component for the onboarding flow @@ -19,26 +19,26 @@ export default function CreateProjectFlow() { const router = useRouter() const { resolvedTheme } = useTheme() const [mounted, setMounted] = useState(false) - + const { currentStep, setCurrentStep, resetOnboarding } = useOnboarding() - + // Handle hydration mismatch by waiting for mount useEffect(() => { setMounted(true) }, []) - + // Reset onboarding state when the component unmounts (optional) useEffect(() => { return () => { // Optional cleanup actions } }, [resetOnboarding]) - + // Handle closing the modal const handleClose = () => { router.push('/projects') } - + // Navigate directly to a specific step const navigateToStep = (step: 'connect' | 'configure' | 'deploy') => { setCurrentStep(step) @@ -48,102 +48,216 @@ export default function CreateProjectFlow() { if (!mounted) { return null } - + // Determine if dark mode is active const isDarkMode = resolvedTheme === 'dark' - + return (
{/* Fixed dimensions modal container */} -
+
{/* Left sidebar with fixed width */} -
+
{/* Laconic logo */}
- LACONIC + + LACONIC +
- + {/* Steps - clickable */}
{/* Connect step */} - - + {/* Configure step */} - - + {/* Deploy step */} -
- + {/* Laconic mark (larger, bottom left) */}
- +
- + {/* Main content with fixed dimensions and scrolling */} -
+
{/* Close button */} - - + {/* Scrollable content container */}
{currentStep === 'connect' && } @@ -189,15 +325,21 @@ export default function CreateProjectFlow() { {currentStep === 'deploy' && } {currentStep === 'success' && }
- + {/* Progress indicator */}
-
-
-
+
+
+
) -} \ No newline at end of file +} diff --git a/apps/deploy-fe/src/app/layout.tsx b/apps/deploy-fe/src/app/layout.tsx index 5838072..92c46d9 100644 --- a/apps/deploy-fe/src/app/layout.tsx +++ b/apps/deploy-fe/src/app/layout.tsx @@ -1,10 +1,10 @@ import { Providers } from '@/components/providers' import { ClerkProvider } from '@clerk/nextjs' import '@workspace/ui/globals.css' +import { AutoSignInIFrameModal } from '@/components/iframe/auto-sign-in' +import { CheckBalanceWrapper } from '@/components/iframe/check-balance-iframe/CheckBalanceWrapper' import type { Metadata } from 'next' import { Inter } from 'next/font/google' -import { CheckBalanceWrapper } from '@/components/iframe/check-balance-iframe/CheckBalanceWrapper' -import { AutoSignInIFrameModal } from '@/components/iframe/auto-sign-in' // Add root metadata with template pattern export const metadata: Metadata = { @@ -25,14 +25,17 @@ export default function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) { return ( - +
{children}
-
+
diff --git a/apps/deploy-fe/src/components/onboarding/Onboarding.tsx b/apps/deploy-fe/src/components/onboarding/Onboarding.tsx index 089face..c7af0e5 100644 --- a/apps/deploy-fe/src/components/onboarding/Onboarding.tsx +++ b/apps/deploy-fe/src/components/onboarding/Onboarding.tsx @@ -10,7 +10,7 @@ import { ConfigureStep } from '@/components/onboarding/configure-step' import { ConnectStep } from '@/components/onboarding/connect-step' import { DeployStep } from '@/components/onboarding/deploy-step' -import { useOnboarding } from '@/components/onboarding/store' +import { useOnboarding } from '@/components/onboarding/useOnboarding' import { ScrollArea } from '@workspace/ui/components/scroll-area' import { SidebarNav } from './sidebar' 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 998176e..bbf2f62 100644 --- a/apps/deploy-fe/src/components/onboarding/configure-step/configure-step.tsx +++ b/apps/deploy-fe/src/components/onboarding/configure-step/configure-step.tsx @@ -1,22 +1,33 @@ // src/components/onboarding/configure-step/configure-step.tsx 'use client' -import { useState, useEffect } from 'react' -import { PlusCircle, Loader2, AlertTriangle, Info } from 'lucide-react' -import { useTheme } from 'next-themes' -import { useOnboarding } from '@/components/onboarding/store' +import { useOnboarding } from '@/components/onboarding/useOnboarding' import { useGQLClient } from '@/context' import { useWallet } from '@/context/WalletContext' -import { Button } from '@workspace/ui/components/button' -import { Input } from '@workspace/ui/components/input' -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@workspace/ui/components/select' -import { Checkbox } from '@workspace/ui/components/checkbox' -import { Label } from '@workspace/ui/components/label' 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 { Button } from '@workspace/ui/components/button' +import { + Card, + CardContent, + CardHeader, + CardTitle +} from '@workspace/ui/components/card' +import { Checkbox } from '@workspace/ui/components/checkbox' +import { Input } from '@workspace/ui/components/input' +import { Label } from '@workspace/ui/components/label' +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue +} from '@workspace/ui/components/select' +import { AlertTriangle, Info, Loader2, PlusCircle } from 'lucide-react' +import { useTheme } from 'next-themes' +import { useEffect, useState } from 'react' import { toast } from 'sonner' -import { adaptDeployers } from '../../../utils/typeAdapters'; +import { adaptDeployers } from '../../../utils/typeAdapters' interface Deployer { deployerLrn: string @@ -35,42 +46,40 @@ export function ConfigureStep() { const { nextStep, previousStep, setFormData, formData } = useOnboarding() const { resolvedTheme } = useTheme() const [mounted, setMounted] = useState(false) - + // Backend data const [deployers, setDeployers] = useState([]) const [organizations, setOrganizations] = useState([]) const [isLoadingDeployers, setIsLoadingDeployers] = useState(true) const [isLoadingOrgs, setIsLoadingOrgs] = useState(true) - + // Form state const [deployOption, setDeployOption] = useState<'auction' | 'lrn'>( - formData.deploymentType as ('auction' | 'lrn') || 'lrn' // Default to LRN for simplicity + (formData.deploymentType as 'auction' | 'lrn') || 'lrn' // Default to LRN for simplicity ) const [numberOfDeployers, setNumberOfDeployers] = useState( - formData.deployerCount || "1" - ) - const [maxPrice, setMaxPrice] = useState( - formData.maxPrice || "1000" + formData.deployerCount || '1' ) + const [maxPrice, setMaxPrice] = useState(formData.maxPrice || '1000') const [selectedLrn, setSelectedLrn] = useState( - formData.selectedLrn || "" + formData.selectedLrn || '' ) const [selectedOrg, setSelectedOrg] = useState( - formData.selectedOrg || "" + formData.selectedOrg || '' ) - const [envVars, setEnvVars] = useState<{ key: string; value: string; environments: string[] }[]>([ - { key: '', value: '', environments: ['Production'] } - ]) - + const [envVars, setEnvVars] = useState< + { key: string; value: string; environments: string[] }[] + >([{ key: '', value: '', environments: ['Production'] }]) + // Contexts const gqlClient = useGQLClient() const { wallet } = useWallet() - + // Handle hydration mismatch by waiting for mount useEffect(() => { setMounted(true) }, []) - + // Fetch deployers and organizations on mount useEffect(() => { if (mounted) { @@ -78,26 +87,35 @@ export function ConfigureStep() { fetchOrganizations() } }, [mounted]) - + // Initialize environment variables from formData if available useEffect(() => { - if (formData.environmentVariables && Array.isArray(formData.environmentVariables)) { - setEnvVars(formData.environmentVariables.length > 0 ? formData.environmentVariables : [ - { key: '', value: '', environments: ['Production'] } - ]) + if ( + formData.environmentVariables && + Array.isArray(formData.environmentVariables) + ) { + setEnvVars( + formData.environmentVariables.length > 0 + ? formData.environmentVariables + : [{ key: '', value: '', environments: ['Production'] }] + ) } }, [formData.environmentVariables]) - + // Fetch deployers from backend const fetchDeployers = async () => { try { setIsLoadingDeployers(true) const deployersData = await gqlClient.getDeployers() console.log('Available deployers:', deployersData) - setDeployers(adaptDeployers(deployersData.deployers || [])); - + setDeployers(adaptDeployers(deployersData.deployers || [])) + // Auto-select first deployer if available and none selected - if (deployersData.deployers && deployersData.deployers.length > 0 && !selectedLrn) { + if ( + deployersData.deployers && + deployersData.deployers.length > 0 && + !selectedLrn + ) { setSelectedLrn(deployersData.deployers[0]!.deployerLrn) } } catch (error) { @@ -107,7 +125,7 @@ export function ConfigureStep() { setIsLoadingDeployers(false) } } - + // Fetch organizations from backend const fetchOrganizations = async () => { try { @@ -115,9 +133,13 @@ export function ConfigureStep() { const orgsData = await gqlClient.getOrganizations() console.log('Available organizations:', orgsData) setOrganizations(orgsData.organizations || []) - + // Auto-select first organization if available and none selected - if (orgsData.organizations && orgsData.organizations.length > 0 && !selectedOrg) { + if ( + orgsData.organizations && + orgsData.organizations.length > 0 && + !selectedOrg + ) { setSelectedOrg(orgsData.organizations[0]!.slug) } } catch (error) { @@ -127,58 +149,66 @@ export function ConfigureStep() { setIsLoadingOrgs(false) } } - + // Add an empty environment variable row const addEnvVar = () => { - setEnvVars([...envVars, { key: '', value: '', environments: ['Production'] }]) + setEnvVars([ + ...envVars, + { key: '', value: '', environments: ['Production'] } + ]) } - + // Remove environment variable row const removeEnvVar = (index: number) => { if (envVars.length > 1) { setEnvVars(envVars.filter((_, i) => i !== index)) } } - + // Update environment variable - const updateEnvVar = (index: number, field: 'key' | 'value', value: string) => { + const updateEnvVar = ( + index: number, + field: 'key' | 'value', + value: string + ) => { const newEnvVars = [...envVars] if (newEnvVars[index]) { newEnvVars[index][field] = value } setEnvVars(newEnvVars) } - + // Toggle environment for variable const toggleEnvironment = (index: number, environment: string) => { const newEnvVars = [...envVars] if (newEnvVars[index]?.environments) { const currentEnvs = newEnvVars[index].environments - if (currentEnvs.includes(environment)) { - newEnvVars[index].environments = currentEnvs.filter(env => env !== 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) } } - + // Toggle deployment option const toggleDeployOption = (option: 'auction' | 'lrn') => { setDeployOption(option) } - + // Get selected deployer details - const selectedDeployer = deployers.find(d => d.deployerLrn === selectedLrn) - + const selectedDeployer = deployers.find((d) => d.deployerLrn === selectedLrn) + // Validate form const canProceed = () => { if (deployOption === 'lrn' && !selectedLrn) return false @@ -186,17 +216,19 @@ export function ConfigureStep() { if (!wallet?.address) return false return true } - + // Handle next step const handleNext = () => { if (!canProceed()) { toast.error('Please complete all required fields') return } - + // Filter out empty environment variables - const validEnvVars = envVars.filter(env => env.key.trim() && env.value.trim()) - + const validEnvVars = envVars.filter( + (env) => env.key.trim() && env.value.trim() + ) + // Save configuration to form data setFormData({ deploymentType: deployOption, @@ -207,33 +239,58 @@ export function ConfigureStep() { paymentAddress: wallet?.address, environmentVariables: validEnvVars }) - + nextStep() } - + // Don't render UI until after mount to prevent hydration mismatch if (!mounted) { return null } - + // Determine if dark mode is active const isDarkMode = resolvedTheme === 'dark' - + // Get deployment mode info const isTemplateMode = formData.deploymentMode === 'template' - const selectedItem = isTemplateMode ? formData.template?.name : formData.githubRepo - + const selectedItem = isTemplateMode + ? formData.template?.name + : formData.githubRepo + return (
{/* Configure icon and header */}
- - - + + +
-

Configure

+

+ Configure +

Define the deployment type

@@ -252,7 +309,9 @@ export function ConfigureStep() {
Type: - {isTemplateMode ? 'Template' : 'Repository'} + + {isTemplateMode ? 'Template' : 'Repository'} +
Source: @@ -268,24 +327,33 @@ export function ConfigureStep() { {/* Organization Selection */}
-