diff --git a/packages/backend/src/registry.ts b/packages/backend/src/registry.ts index 45305c57..904376ed 100644 --- a/packages/backend/src/registry.ts +++ b/packages/backend/src/registry.ts @@ -416,11 +416,11 @@ export class Registry { }; } - async getCompletedAuctionIds(auctionIds: (string | null | undefined)[]): Promise { - const validAuctionIds = auctionIds.filter((id): id is string => id !== null && id !== undefined); + async getCompletedAuctionIds(auctionIds: string[]): Promise { + const validAuctionIds = auctionIds.filter((id): id is string => id !== ''); if (!validAuctionIds.length) { - return null; + return []; } const auctions = await this.registry.getAuctionsByIds(validAuctionIds); diff --git a/packages/backend/src/service.ts b/packages/backend/src/service.ts index 5a5286c9..d6d7604a 100644 --- a/packages/backend/src/service.ts +++ b/packages/backend/src/service.ts @@ -313,10 +313,9 @@ export class Service { return project.deployments.length === 0; }); - const auctionIds = projects.map((project) => project.auctionId); - const completedAuctionIds = await this.laconicRegistry.getCompletedAuctionIds(auctionIds); - - if (completedAuctionIds) { + if (projects.length > 0) { + const auctionIds = projects.map((project) => project.auctionId!); + const completedAuctionIds = await this.laconicRegistry.getCompletedAuctionIds(auctionIds); const projectsToBedeployed = projects.filter((project) => completedAuctionIds.includes(project.auctionId!) ); diff --git a/packages/frontend/src/components/projects/create/Configure.tsx b/packages/frontend/src/components/projects/create/Configure.tsx index d1361788..4f622326 100644 --- a/packages/frontend/src/components/projects/create/Configure.tsx +++ b/packages/frontend/src/components/projects/create/Configure.tsx @@ -1,5 +1,6 @@ import { useCallback, useState } from 'react'; -import { useForm, Controller, SubmitHandler } from 'react-hook-form'; +import { useForm, Controller } from 'react-hook-form'; +import { FormProvider, FieldValues } from 'react-hook-form'; import { useNavigate, useSearchParams } from 'react-router-dom'; import { useMediaQuery } from 'usehooks-ts'; import { AuctionParams } from 'gql-client'; @@ -14,15 +15,20 @@ import { Select, SelectOption } from 'components/shared/Select'; import { Input } from 'components/shared/Input'; import { useToast } from 'components/shared/Toast'; import { useGQLClient } from '../../../context/GQLClientContext'; +import EnvironmentVariablesForm from 'pages/org-slug/projects/id/settings/EnvironmentVariablesForm'; +import { EnvironmentVariablesFormValues } from 'types/types'; -type ConfigureFormValues = { +type ConfigureDeploymentFormValues = { option: string; lrn?: string; numProviders?: number; maxPrice?: string; }; +type ConfigureFormValues = ConfigureDeploymentFormValues & EnvironmentVariablesFormValues; + const Configure = () => { + const [isLoading, setIsLoading] = useState(false); const [searchParams] = useSearchParams(); const templateId = searchParams.get('templateId'); const queryParams = new URLSearchParams(location.search); @@ -40,19 +46,18 @@ const Configure = () => { const { toast, dismiss } = useToast(); const client = useGQLClient(); - const [isLoading, setIsLoading] = useState(false); - const { handleSubmit, control, watch } = useForm({ + const methods = useForm({ defaultValues: { option: 'LRN' }, }); - const selectedOption = watch('option'); + const selectedOption = methods.watch('option'); const isTabletView = useMediaQuery('(min-width: 720px)'); // md: const buttonSize = isTabletView ? { size: 'lg' as const } : {}; - const onSubmit: SubmitHandler = useCallback( - async (data) => { - setIsLoading(true); + const createProject = async (data: FieldValues): Promise => { + setIsLoading(true); + let projectId: string | null = null; try { let lrn: string | undefined; @@ -67,69 +72,118 @@ const Configure = () => { }; } - if (templateId) { - // Template-based project creation - const projectData: any = { - templateOwner, - templateRepo, - owner, - name, - isPrivate, - }; + if (templateId) { + const projectData: any = { + templateOwner, + templateRepo, + owner, + name, + isPrivate, + }; - const { addProjectFromTemplate } = await client.addProjectFromTemplate( - orgSlug!, - projectData, - lrn, - auctionParams - ); + const { addProjectFromTemplate } = await client.addProjectFromTemplate( + orgSlug!, + projectData, + lrn, + auctionParams + ); + projectId = addProjectFromTemplate.id; + } else { + const { addProject } = await client.addProject( + orgSlug!, + { + name: fullName!, + prodBranch: defaultBranch!, + repository: fullName!, + template: 'webapp', + }, + lrn, + auctionParams + ); - data.option === 'Auction' - ? navigate( - `/${orgSlug}/projects/create/success/${addProjectFromTemplate.id}?isAuction=true`, - ) - : navigate( - `/${orgSlug}/projects/create/template/deploy?projectId=${addProjectFromTemplate.id}&templateId=${templateId}` - ); - } else { - const { addProject } = await client.addProject( - orgSlug!, - { - name: fullName!, - prodBranch: defaultBranch!, - repository: fullName!, - template: 'webapp', - }, - lrn, - auctionParams - ); + projectId = addProject.id; + } + } catch (error) { + console.error('Error creating project:', error); + toast({ + id: 'error-creating-project', + title: 'Error creating project', + variant: 'error', + onDismiss: dismiss, + }); + } finally { + setIsLoading(false); + } - data.option === 'Auction' - ? navigate( - `/${orgSlug}/projects/create/success/${addProject.id}?isAuction=true` - ) - : navigate( - `/${orgSlug}/projects/create/deploy?projectId=${addProject.id}` - ); - } - } catch (error) { - console.error('Error creating project:', error); + if (projectId) { + return projectId; + } else { + throw new Error('Project creation failed'); + } + }; + + const createEnvironmentVariablesHandler = useCallback( + async (createFormData: FieldValues) => { + const environmentVariables = createFormData.variables.map((variable: any) => { + return { + key: variable.key, + value: variable.value, + environments: Object.entries(createFormData.environment) + .filter(([, value]) => value === true) + .map(([key]) => key.charAt(0).toUpperCase() + key.slice(1)), + }; + }); + + const projectId = await createProject(createFormData); + + const { addEnvironmentVariables: isEnvironmentVariablesAdded } = + await client.addEnvironmentVariables(projectId, environmentVariables); + + if (isEnvironmentVariablesAdded) { toast({ - id: 'error-creating-project', - title: 'Error creating project', + id: + createFormData.variables.length > 1 + ? 'env_variable_added' + : 'env_variables_added', + title: + createFormData.variables.length > 1 + ? `${createFormData.variables.length} variables added` + : `Variable added`, + variant: 'success', + onDismiss: dismiss, + }); + } else { + toast({ + id: 'env_variables_not_added', + title: 'Environment variables not added', variant: 'error', onDismiss: dismiss, }); - } finally { - setIsLoading(false); + } + if (templateId) { + createFormData.option === 'Auction' + ? navigate( + `/${orgSlug}/projects/create/success/${projectId}?isAuction=true`, + ) + : navigate( + `/${orgSlug}/projects/create/template/deploy?projectId=${projectId}&templateId=${templateId}` + ); + } else { + createFormData.option === 'Auction' + ? navigate( + `/${orgSlug}/projects/create/success/${projectId}?isAuction=true` + ) + : navigate( + `/${orgSlug}/projects/create/deploy?projectId=${projectId}` + ); } }, - [client, isPrivate, templateId, navigate, dismiss, toast] + [client, createProject, dismiss, toast] ); return ( -
-
+
+
Configure deployment @@ -142,99 +196,108 @@ const Configure = () => {
-
-
-
- ( - + + )} />
-
- - Maximum Price (alnt) - - ( - - )} - /> -
- - )} + )} -
- -
-
-
+ {selectedOption === 'Auction' && ( + <> +
+ + Set the number of deployers and maximum price for each deployment + + + Number of Deployers + + ( + + )} + /> +
+
+ + Maximum Price (alnt) + + ( + + )} + /> +
+ + )} + + + Environment Variables + +
+ +
+ +
+ +
+ + +
); }; diff --git a/packages/frontend/src/pages/org-slug/projects/id/settings/EnvironmentVariables.tsx b/packages/frontend/src/pages/org-slug/projects/id/settings/EnvironmentVariables.tsx index b3c9314d..459c4f15 100644 --- a/packages/frontend/src/pages/org-slug/projects/id/settings/EnvironmentVariables.tsx +++ b/packages/frontend/src/pages/org-slug/projects/id/settings/EnvironmentVariables.tsx @@ -14,6 +14,8 @@ import { ProjectSettingContainer } from 'components/projects/project/settings/Pr import { useToast } from 'components/shared/Toast'; import { Environment, EnvironmentVariable } from 'gql-client'; import EnvironmentVariablesForm from './EnvironmentVariablesForm'; +import { FieldValues, FormProvider, useForm } from 'react-hook-form'; +import { Button } from 'components/shared'; export const EnvironmentVariablesTabPanel = () => { const { id } = useParams(); @@ -26,6 +28,17 @@ export const EnvironmentVariablesTabPanel = () => { const [createNewVariable, setCreateNewVariable] = useState(false); + const methods = useForm({ + defaultValues: { + variables: [{ key: '', value: '' }], + environment: { + development: false, + preview: false, + production: false, + }, + }, + }); + const getEnvironmentVariables = useCallback( (environment: Environment) => { return environmentVariables.filter( @@ -51,8 +64,8 @@ export const EnvironmentVariablesTabPanel = () => { }, [id]); const createEnvironmentVariablesHandler = useCallback( - async (createFormData: EnvironmentVariablesFormValues, reset: () => void) => { - const environmentVariables = createFormData.variables.map((variable) => { + async (createFormData: FieldValues) => { + const environmentVariables = createFormData.variables.map((variable: any) => { return { key: variable.key, value: variable.value, @@ -66,7 +79,7 @@ export const EnvironmentVariablesTabPanel = () => { await client.addEnvironmentVariables(id!, environmentVariables); if (isEnvironmentVariablesAdded) { - reset(); + // reset(); setCreateNewVariable((cur) => !cur); fetchEnvironmentVariables(id); @@ -111,10 +124,18 @@ export const EnvironmentVariablesTabPanel = () => {
-
- createEnvironmentVariablesHandler(data, reset)} /> -
+ +
createEnvironmentVariablesHandler(data))}> +
+ +
+
+ +
+
+
diff --git a/packages/frontend/src/pages/org-slug/projects/id/settings/EnvironmentVariablesForm.tsx b/packages/frontend/src/pages/org-slug/projects/id/settings/EnvironmentVariablesForm.tsx index 5c70c1f1..c5860522 100644 --- a/packages/frontend/src/pages/org-slug/projects/id/settings/EnvironmentVariablesForm.tsx +++ b/packages/frontend/src/pages/org-slug/projects/id/settings/EnvironmentVariablesForm.tsx @@ -1,35 +1,20 @@ -import React, { useEffect, useMemo } from 'react'; -import { useForm, useFieldArray } from 'react-hook-form'; +import { useEffect } from 'react'; +import { useFieldArray, useFormContext } from 'react-hook-form'; import { Checkbox } from '@snowballtools/material-tailwind-react-fork'; import { Button } from 'components/shared/Button'; -import { InlineNotification } from 'components/shared/InlineNotification'; +// import { InlineNotification } from 'components/shared/InlineNotification'; import AddEnvironmentVariableRow from 'components/projects/project/settings/AddEnvironmentVariableRow'; import { EnvironmentVariablesFormValues } from 'types/types'; -interface EnvironmentVariablesFormProps { - onSubmit: (data: EnvironmentVariablesFormValues, reset: () => void) => void; -} - -const EnvironmentVariablesForm: React.FC = ({ onSubmit }) => { +const EnvironmentVariablesForm = () => { const { - handleSubmit, register, control, reset, - formState: { isSubmitSuccessful, errors }, - } = useForm({ - defaultValues: { - variables: [{ key: '', value: '' }], - environment: { - development: false, - preview: false, - production: false, - }, - }, - }); - + formState: { isSubmitSuccessful }, + } = useFormContext(); const { fields, append, remove } = useFieldArray({ name: 'variables', control, @@ -43,24 +28,24 @@ const EnvironmentVariablesForm: React.FC = ({ onS reset(); } }, [isSubmitSuccessful, reset]); - - const isFieldEmpty = useMemo(() => { - if (errors.variables) { - return fields.some((_, index) => { - if ( - errors.variables![index]?.value?.type === 'required' || - errors.variables![index]?.key?.type === 'required' - ) { - return true; - } - }); - } - return false; - }, [fields, errors.variables]); + // const isFieldEmpty = useMemo(() => { + // if (errors.variables) { + // return fields.some((_, index) => { + // if ( + // errors.variables![index]?.value?.type === 'required' || + // errors.variables![index]?.key?.type === 'required' + // ) { + // return true; + // } + // }); + // } + + // return false; + // }, [fields, errors.variables]); return ( -
onSubmit(data, reset))}> + <> {fields.map((field, index) => ( = ({ onS + Add variable
- {isFieldEmpty && ( + {/* {isFieldEmpty && ( - )} + )} */}
-
- -
- + ); };