Update UI to take environment variables from user #6
@ -416,11 +416,11 @@ export class Registry {
|
||||
};
|
||||
}
|
||||
|
||||
async getCompletedAuctionIds(auctionIds: (string | null | undefined)[]): Promise<string[] | null> {
|
||||
const validAuctionIds = auctionIds.filter((id): id is string => id !== null && id !== undefined);
|
||||
async getCompletedAuctionIds(auctionIds: string[]): Promise<string[]> {
|
||||
const validAuctionIds = auctionIds.filter((id): id is string => id !== '');
|
||||
|
||||
if (!validAuctionIds.length) {
|
||||
return null;
|
||||
return [];
|
||||
}
|
||||
|
||||
const auctions = await this.registry.getAuctionsByIds(validAuctionIds);
|
||||
|
@ -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!)
|
||||
);
|
||||
|
@ -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<ConfigureFormValues>({
|
||||
const methods = useForm<ConfigureFormValues>({
|
||||
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<ConfigureFormValues> = useCallback(
|
||||
async (data) => {
|
||||
setIsLoading(true);
|
||||
const createProject = async (data: FieldValues): Promise<string> => {
|
||||
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 (
|
||||
<div className="space-y-7">
|
||||
<div className="flex justify-between">
|
||||
<div className="space-y-7 px-4 py-6">
|
||||
<div className="flex justify-between mb-6">
|
||||
<div className="space-y-1.5">
|
||||
<Heading as="h4" className="md:text-lg font-medium">
|
||||
Configure deployment
|
||||
@ -142,99 +196,108 @@ const Configure = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<div className="flex flex-col gap-4 lg:gap-7 w-full">
|
||||
<div className="flex flex-col justify-start gap-3">
|
||||
<Controller
|
||||
name="option"
|
||||
control={control}
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<Select
|
||||
label="Configuration Options"
|
||||
value={
|
||||
{
|
||||
value: value || 'LRN',
|
||||
label: value === 'Auction' ? 'Create Auction' : 'Deployer LRN',
|
||||
} as SelectOption
|
||||
}
|
||||
onChange={(value) => onChange((value as SelectOption).value)}
|
||||
options={[
|
||||
{ value: 'LRN', label: 'Deployer LRN' },
|
||||
{ value: 'Auction', label: 'Create Auction' },
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{selectedOption === 'LRN' && (
|
||||
<div className="flex flex-col justify-start gap-3">
|
||||
<Heading as="h5" className="text-sm font-sans text-elements-low-em">
|
||||
The app will be deployed by the configured deployer
|
||||
</Heading>
|
||||
<span className="text-sm text-elements-high-em">
|
||||
Enter LRN for deployer
|
||||
</span>
|
||||
<div className="flex flex-col gap-6 lg:gap-8 w-full">
|
||||
<FormProvider {...methods}>
|
||||
<form onSubmit={methods.handleSubmit(createEnvironmentVariablesHandler)}>
|
||||
<div className="flex flex-col justify-start gap-4 mb-6">
|
||||
<Controller
|
||||
name="lrn"
|
||||
control={control}
|
||||
name="option"
|
||||
control={methods.control}
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<Input value={value} onChange={onChange} />
|
||||
<Select
|
||||
label="Configuration Options"
|
||||
value={
|
||||
{
|
||||
value: value || 'LRN',
|
||||
label: value === 'Auction' ? 'Create Auction' : 'Deployer LRN',
|
||||
} as SelectOption
|
||||
}
|
||||
onChange={(value) => onChange((value as SelectOption).value)}
|
||||
options={[
|
||||
{ value: 'LRN', label: 'Deployer LRN' },
|
||||
{ value: 'Auction', label: 'Create Auction' },
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{selectedOption === 'Auction' && (
|
||||
<>
|
||||
<div className="flex flex-col justify-start gap-3">
|
||||
{selectedOption === 'LRN' && (
|
||||
<div className="flex flex-col justify-start gap-4 mb-6">
|
||||
<Heading as="h5" className="text-sm font-sans text-elements-low-em">
|
||||
Set the number of deployers and maximum price for each deployment
|
||||
The app will be deployed by the configured deployer
|
||||
</Heading>
|
||||
<span className="text-sm text-elements-high-em">
|
||||
Number of Deployers
|
||||
Enter LRN for deployer
|
||||
</span>
|
||||
<Controller
|
||||
name="numProviders"
|
||||
control={control}
|
||||
name="lrn"
|
||||
control={methods.control}
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<Input type="number" value={value} onChange={onChange} />
|
||||
<Input value={value} onChange={onChange} />
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col justify-start gap-3">
|
||||
<span className="text-sm text-elements-high-em">
|
||||
Maximum Price (alnt)
|
||||
</span>
|
||||
<Controller
|
||||
name="maxPrice"
|
||||
control={control}
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<Input type="number" value={value} onChange={onChange} />
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
)}
|
||||
|
||||
<div>
|
||||
<Button
|
||||
{...buttonSize}
|
||||
type="submit"
|
||||
disabled={isLoading}
|
||||
rightIcon={
|
||||
isLoading ? (
|
||||
<LoadingIcon className="animate-spin" />
|
||||
) : (
|
||||
<ArrowRightCircleFilledIcon />
|
||||
)
|
||||
}
|
||||
>
|
||||
{isLoading ? 'Deploying repo' : 'Deploy repo'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{selectedOption === 'Auction' && (
|
||||
<>
|
||||
<div className="flex flex-col justify-start gap-4 mb-6">
|
||||
<Heading as="h5" className="text-sm font-sans text-elements-low-em">
|
||||
Set the number of deployers and maximum price for each deployment
|
||||
</Heading>
|
||||
<span className="text-sm text-elements-high-em">
|
||||
Number of Deployers
|
||||
</span>
|
||||
<Controller
|
||||
name="numProviders"
|
||||
control={methods.control}
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<Input type="number" value={value} onChange={onChange} />
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col justify-start gap-4 mb-6">
|
||||
<span className="text-sm text-elements-high-em">
|
||||
Maximum Price (alnt)
|
||||
</span>
|
||||
<Controller
|
||||
name="maxPrice"
|
||||
control={methods.control}
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<Input type="number" value={value} onChange={onChange} />
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
<Heading as="h4" className="md:text-lg font-medium mb-3">
|
||||
Environment Variables
|
||||
</Heading>
|
||||
<div className="p-4 bg-slate-100 rounded-lg mb-6">
|
||||
<EnvironmentVariablesForm />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Button
|
||||
{...buttonSize}
|
||||
type="submit"
|
||||
disabled={isLoading}
|
||||
rightIcon={
|
||||
isLoading ? (
|
||||
<LoadingIcon className="animate-spin" />
|
||||
) : (
|
||||
<ArrowRightCircleFilledIcon />
|
||||
)
|
||||
}
|
||||
>
|
||||
{isLoading ? 'Deploying repo' : 'Deploy repo'}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</FormProvider>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -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<EnvironmentVariablesFormValues>({
|
||||
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 = () => {
|
||||
</div>
|
||||
</Heading>
|
||||
<Collapse open={createNewVariable}>
|
||||
<div className="p-4 bg-slate-100">
|
||||
<EnvironmentVariablesForm
|
||||
onSubmit={(data, reset) => createEnvironmentVariablesHandler(data, reset)} />
|
||||
</div>
|
||||
<FormProvider {...methods}>
|
||||
<form onSubmit={methods.handleSubmit((data) => createEnvironmentVariablesHandler(data))}>
|
||||
<div className="p-4 bg-slate-100">
|
||||
<EnvironmentVariablesForm />
|
||||
</div>
|
||||
<div className="p-2">
|
||||
<Button size="md" type="submit">
|
||||
Save changes
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</FormProvider>
|
||||
</Collapse>
|
||||
</div>
|
||||
<div className="p-2">
|
||||
|
@ -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<EnvironmentVariablesFormProps> = ({ onSubmit }) => {
|
||||
const EnvironmentVariablesForm = () => {
|
||||
const {
|
||||
handleSubmit,
|
||||
register,
|
||||
control,
|
||||
reset,
|
||||
formState: { isSubmitSuccessful, errors },
|
||||
} = useForm<EnvironmentVariablesFormValues>({
|
||||
defaultValues: {
|
||||
variables: [{ key: '', value: '' }],
|
||||
environment: {
|
||||
development: false,
|
||||
preview: false,
|
||||
production: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
formState: { isSubmitSuccessful },
|
||||
} = useFormContext<EnvironmentVariablesFormValues>();
|
||||
const { fields, append, remove } = useFieldArray({
|
||||
name: 'variables',
|
||||
control,
|
||||
@ -44,23 +29,23 @@ const EnvironmentVariablesForm: React.FC<EnvironmentVariablesFormProps> = ({ onS
|
||||
}
|
||||
}, [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;
|
||||
}
|
||||
});
|
||||
}
|
||||
// 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 false;
|
||||
// }, [fields, errors.variables]);
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit((data) => onSubmit(data, reset))}>
|
||||
<>
|
||||
{fields.map((field, index) => (
|
||||
<AddEnvironmentVariableRow
|
||||
key={field.id}
|
||||
@ -75,23 +60,18 @@ const EnvironmentVariablesForm: React.FC<EnvironmentVariablesFormProps> = ({ onS
|
||||
+ Add variable
|
||||
</Button>
|
||||
</div>
|
||||
{isFieldEmpty && (
|
||||
{/* {isFieldEmpty && (
|
||||
<InlineNotification
|
||||
title="Please ensure no fields are empty before saving."
|
||||
variant="danger"
|
||||
/>
|
||||
)}
|
||||
)} */}
|
||||
<div className="flex gap-2 p-2">
|
||||
<Checkbox label="Production" {...register('environment.production')} />
|
||||
<Checkbox label="Preview" {...register('environment.preview')} />
|
||||
<Checkbox label="Development" {...register('environment.development')} />
|
||||
</div>
|
||||
<div className="p-2">
|
||||
<Button size="md" type="submit">
|
||||
Save changes
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user