Remove organization switcher from side bar #9
| @ -1,7 +1,7 @@ | |||||||
| #!/bin/bash | #!/bin/bash | ||||||
| 
 | 
 | ||||||
| # Repository URL | # Repository URL | ||||||
| REPO_URL="https://github.com/snowball-tools/snowballtools-base" | REPO_URL="https://git.vdb.to/cerc-io/snowballtools-base" | ||||||
| 
 | 
 | ||||||
| # Get the latest commit hash from the repository | # Get the latest commit hash from the repository | ||||||
| LATEST_HASH=$(git ls-remote $REPO_URL HEAD | awk '{print $1}') | LATEST_HASH=$(git ls-remote $REPO_URL HEAD | awk '{print $1}') | ||||||
|  | |||||||
| @ -14,5 +14,5 @@ record: | |||||||
|       LACONIC_HOSTED_CONFIG_app_wallet_connect_id: eda9ba18042a5ea500f358194611ece2 |       LACONIC_HOSTED_CONFIG_app_wallet_connect_id: eda9ba18042a5ea500f358194611ece2 | ||||||
|   meta: |   meta: | ||||||
|     note: Added by Snowball @ Thu Apr  4 14:49:41 UTC 2024 |     note: Added by Snowball @ Thu Apr  4 14:49:41 UTC 2024 | ||||||
|     repository: "https://github.com/snowball-tools/snowballtools-base" |     repository: "https://git.vdb.to/cerc-io/snowballtools-base" | ||||||
|     repository_ref: 351db16336eacc3e1f9119ceb8d1282b8e27a27e |     repository_ref: 351db16336eacc3e1f9119ceb8d1282b8e27a27e | ||||||
|  | |||||||
| @ -2,7 +2,7 @@ record: | |||||||
|   type: ApplicationRecord |   type: ApplicationRecord | ||||||
|   version: 0.0.2 |   version: 0.0.2 | ||||||
|   repository_ref: 351db16336eacc3e1f9119ceb8d1282b8e27a27e |   repository_ref: 351db16336eacc3e1f9119ceb8d1282b8e27a27e | ||||||
|   repository: ["https://github.com/snowball-tools/snowballtools-base"] |   repository: ["https://git.vdb.to/cerc-io/snowballtools-base"] | ||||||
|   app_type: webapp |   app_type: webapp | ||||||
|   name: snowballtools-base-frontend |   name: snowballtools-base-frontend | ||||||
|   app_version: 0.1.8 |   app_version: 0.1.8 | ||||||
|  | |||||||
| @ -20,5 +20,5 @@ record: | |||||||
|       LACONIC_HOSTED_CONFIG_turnkey_organization_id: 5049ae99-5bca-40b3-8317-504384d4e591 |       LACONIC_HOSTED_CONFIG_turnkey_organization_id: 5049ae99-5bca-40b3-8317-504384d4e591 | ||||||
|   meta: |   meta: | ||||||
|     note: Added by Snowball @ Mon Jun 24 23:51:48 UTC 2024 |     note: Added by Snowball @ Mon Jun 24 23:51:48 UTC 2024 | ||||||
|     repository: "https://github.com/snowball-tools/snowballtools-base" |     repository: "https://git.vdb.to/cerc-io/snowballtools-base" | ||||||
|     repository_ref: 61e3e88a6c9d57e95441059369ee5a46f5c07601 |     repository_ref: 61e3e88a6c9d57e95441059369ee5a46f5c07601 | ||||||
|  | |||||||
| @ -2,7 +2,7 @@ record: | |||||||
|   type: ApplicationRecord |   type: ApplicationRecord | ||||||
|   version: 0.0.1 |   version: 0.0.1 | ||||||
|   repository_ref: 61e3e88a6c9d57e95441059369ee5a46f5c07601 |   repository_ref: 61e3e88a6c9d57e95441059369ee5a46f5c07601 | ||||||
|   repository: ["https://github.com/snowball-tools/snowballtools-base"] |   repository: ["https://git.vdb.to/cerc-io/snowballtools-base"] | ||||||
|   app_type: webapp |   app_type: webapp | ||||||
|   name: staging-snowballtools-base-frontend |   name: staging-snowballtools-base-frontend | ||||||
|   app_version: 0.0.0 |   app_version: 0.0.0 | ||||||
|  | |||||||
| @ -10,11 +10,16 @@ import { | |||||||
|   LinkChainIcon, |   LinkChainIcon, | ||||||
| } from 'components/shared/CustomIcon'; | } from 'components/shared/CustomIcon'; | ||||||
| import { TagProps } from 'components/shared/Tag'; | import { TagProps } from 'components/shared/Tag'; | ||||||
|  | import { | ||||||
|  |   ArrowRightCircleFilledIcon, | ||||||
|  |   LoadingIcon, | ||||||
|  | } from 'components/shared/CustomIcon'; | ||||||
| 
 | 
 | ||||||
| interface ChangeStateToProductionDialogProps extends ConfirmDialogProps { | interface ChangeStateToProductionDialogProps extends ConfirmDialogProps { | ||||||
|   deployment: Deployment; |   deployment: Deployment; | ||||||
|   newDeployment?: Deployment; |   newDeployment?: Deployment; | ||||||
|   domains: Domain[]; |   domains: Domain[]; | ||||||
|  |   isConfirmButtonLoading?: boolean; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export const ChangeStateToProductionDialog = ({ | export const ChangeStateToProductionDialog = ({ | ||||||
| @ -24,6 +29,7 @@ export const ChangeStateToProductionDialog = ({ | |||||||
|   open, |   open, | ||||||
|   handleCancel, |   handleCancel, | ||||||
|   handleConfirm, |   handleConfirm, | ||||||
|  |   isConfirmButtonLoading, | ||||||
|   ...props |   ...props | ||||||
| }: ChangeStateToProductionDialogProps) => { | }: ChangeStateToProductionDialogProps) => { | ||||||
|   const currentChip = { |   const currentChip = { | ||||||
| @ -41,6 +47,14 @@ export const ChangeStateToProductionDialog = ({ | |||||||
|       handleCancel={handleCancel} |       handleCancel={handleCancel} | ||||||
|       open={open} |       open={open} | ||||||
|       handleConfirm={handleConfirm} |       handleConfirm={handleConfirm} | ||||||
|  |       confirmButtonProps={{ | ||||||
|  |         disabled: isConfirmButtonLoading, | ||||||
|  |         rightIcon: isConfirmButtonLoading ? ( | ||||||
|  |           <LoadingIcon className="animate-spin" /> | ||||||
|  |         ) : ( | ||||||
|  |           <ArrowRightCircleFilledIcon /> | ||||||
|  |         ), | ||||||
|  |       }} | ||||||
|     > |     > | ||||||
|       <div className="flex flex-col gap-7"> |       <div className="flex flex-col gap-7"> | ||||||
|         <div className="flex flex-col gap-3"> |         <div className="flex flex-col gap-3"> | ||||||
|  | |||||||
| @ -25,7 +25,8 @@ type ConfigureDeploymentFormValues = { | |||||||
|   maxPrice?: string; |   maxPrice?: string; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| type ConfigureFormValues = ConfigureDeploymentFormValues & EnvironmentVariablesFormValues; | type ConfigureFormValues = ConfigureDeploymentFormValues & | ||||||
|  |   EnvironmentVariablesFormValues; | ||||||
| 
 | 
 | ||||||
| const Configure = () => { | const Configure = () => { | ||||||
|   const [isLoading, setIsLoading] = useState(false); |   const [isLoading, setIsLoading] = useState(false); | ||||||
| @ -55,7 +56,10 @@ const Configure = () => { | |||||||
|   const isTabletView = useMediaQuery('(min-width: 720px)'); // md:
 |   const isTabletView = useMediaQuery('(min-width: 720px)'); // md:
 | ||||||
|   const buttonSize = isTabletView ? { size: 'lg' as const } : {}; |   const buttonSize = isTabletView ? { size: 'lg' as const } : {}; | ||||||
| 
 | 
 | ||||||
|   const createProject = async (data: FieldValues, envVariables: AddEnvironmentVariableInput[]): Promise<string> => { |   const createProject = async ( | ||||||
|  |     data: FieldValues, | ||||||
|  |     envVariables: AddEnvironmentVariableInput[], | ||||||
|  |   ): Promise<string> => { | ||||||
|     setIsLoading(true); |     setIsLoading(true); | ||||||
|     let projectId: string | null = null; |     let projectId: string | null = null; | ||||||
| 
 | 
 | ||||||
| @ -68,7 +72,7 @@ const Configure = () => { | |||||||
|       } else if (data.option === 'Auction') { |       } else if (data.option === 'Auction') { | ||||||
|         auctionParams = { |         auctionParams = { | ||||||
|           numProviders: Number(data.numProviders!), |           numProviders: Number(data.numProviders!), | ||||||
|           maxPrice: (data.maxPrice!).toString(), |           maxPrice: data.maxPrice!.toString(), | ||||||
|         }; |         }; | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
| @ -86,7 +90,7 @@ const Configure = () => { | |||||||
|           projectData, |           projectData, | ||||||
|           lrn, |           lrn, | ||||||
|           auctionParams, |           auctionParams, | ||||||
|           envVariables |           envVariables, | ||||||
|         ); |         ); | ||||||
| 
 | 
 | ||||||
|         projectId = addProjectFromTemplate.id; |         projectId = addProjectFromTemplate.id; | ||||||
| @ -94,14 +98,14 @@ const Configure = () => { | |||||||
|         const { addProject } = await client.addProject( |         const { addProject } = await client.addProject( | ||||||
|           orgSlug!, |           orgSlug!, | ||||||
|           { |           { | ||||||
|             name: fullName!, |             name: `${owner}-${name}`, | ||||||
|             prodBranch: defaultBranch!, |             prodBranch: defaultBranch!, | ||||||
|             repository: fullName!, |             repository: fullName!, | ||||||
|             template: 'webapp', |             template: 'webapp', | ||||||
|           }, |           }, | ||||||
|           lrn, |           lrn, | ||||||
|           auctionParams, |           auctionParams, | ||||||
|           envVariables |           envVariables, | ||||||
|         ); |         ); | ||||||
| 
 | 
 | ||||||
|         projectId = addProject.id; |         projectId = addProject.id; | ||||||
| @ -127,7 +131,8 @@ const Configure = () => { | |||||||
| 
 | 
 | ||||||
|   const handleFormSubmit = useCallback( |   const handleFormSubmit = useCallback( | ||||||
|     async (createFormData: FieldValues) => { |     async (createFormData: FieldValues) => { | ||||||
|       const environmentVariables = createFormData.variables.map((variable: any) => { |       const environmentVariables = createFormData.variables.map( | ||||||
|  |         (variable: any) => { | ||||||
|           return { |           return { | ||||||
|             key: variable.key, |             key: variable.key, | ||||||
|             value: variable.value, |             value: variable.value, | ||||||
| @ -135,9 +140,13 @@ const Configure = () => { | |||||||
|               .filter(([, value]) => value === true) |               .filter(([, value]) => value === true) | ||||||
|               .map(([key]) => key.charAt(0).toUpperCase() + key.slice(1)), |               .map(([key]) => key.charAt(0).toUpperCase() + key.slice(1)), | ||||||
|           }; |           }; | ||||||
|       }); |         }, | ||||||
|  |       ); | ||||||
| 
 | 
 | ||||||
|       const projectId = await createProject(createFormData, environmentVariables); |       const projectId = await createProject( | ||||||
|  |         createFormData, | ||||||
|  |         environmentVariables, | ||||||
|  |       ); | ||||||
| 
 | 
 | ||||||
|       await client.getEnvironmentVariables(projectId); |       await client.getEnvironmentVariables(projectId); | ||||||
| 
 | 
 | ||||||
| @ -147,19 +156,19 @@ const Configure = () => { | |||||||
|               `/${orgSlug}/projects/create/success/${projectId}?isAuction=true`, |               `/${orgSlug}/projects/create/success/${projectId}?isAuction=true`, | ||||||
|             ) |             ) | ||||||
|           : navigate( |           : navigate( | ||||||
|             `/${orgSlug}/projects/create/template/deploy?projectId=${projectId}&templateId=${templateId}` |               `/${orgSlug}/projects/create/template/deploy?projectId=${projectId}&templateId=${templateId}`, | ||||||
|             ); |             ); | ||||||
|       } else { |       } else { | ||||||
|         createFormData.option === 'Auction' |         createFormData.option === 'Auction' | ||||||
|           ? navigate( |           ? navigate( | ||||||
|             `/${orgSlug}/projects/create/success/${projectId}?isAuction=true` |               `/${orgSlug}/projects/create/success/${projectId}?isAuction=true`, | ||||||
|             ) |             ) | ||||||
|           : navigate( |           : navigate( | ||||||
|             `/${orgSlug}/projects/create/deploy?projectId=${projectId}` |               `/${orgSlug}/projects/create/deploy?projectId=${projectId}`, | ||||||
|             ); |             ); | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     [client, createProject, dismiss, toast] |     [client, createProject, dismiss, toast], | ||||||
|   ); |   ); | ||||||
| 
 | 
 | ||||||
|   return ( |   return ( | ||||||
| @ -190,10 +199,15 @@ const Configure = () => { | |||||||
|                     value={ |                     value={ | ||||||
|                       { |                       { | ||||||
|                         value: value || 'LRN', |                         value: value || 'LRN', | ||||||
|                         label: value === 'Auction' ? 'Create Auction' : 'Deployer LRN', |                         label: | ||||||
|  |                           value === 'Auction' | ||||||
|  |                             ? 'Create Auction' | ||||||
|  |                             : 'Deployer LRN', | ||||||
|                       } as SelectOption |                       } as SelectOption | ||||||
|                     } |                     } | ||||||
|                     onChange={(value) => onChange((value as SelectOption).value)} |                     onChange={(value) => | ||||||
|  |                       onChange((value as SelectOption).value) | ||||||
|  |                     } | ||||||
|                     options={[ |                     options={[ | ||||||
|                       { value: 'LRN', label: 'Deployer LRN' }, |                       { value: 'LRN', label: 'Deployer LRN' }, | ||||||
|                       { value: 'Auction', label: 'Create Auction' }, |                       { value: 'Auction', label: 'Create Auction' }, | ||||||
| @ -205,7 +219,10 @@ const Configure = () => { | |||||||
| 
 | 
 | ||||||
|             {selectedOption === 'LRN' && ( |             {selectedOption === 'LRN' && ( | ||||||
|               <div className="flex flex-col justify-start gap-4 mb-6"> |               <div className="flex flex-col justify-start gap-4 mb-6"> | ||||||
|                 <Heading as="h5" className="text-sm font-sans text-elements-low-em"> |                 <Heading | ||||||
|  |                   as="h5" | ||||||
|  |                   className="text-sm font-sans text-elements-low-em" | ||||||
|  |                 > | ||||||
|                   The app will be deployed by the configured deployer |                   The app will be deployed by the configured deployer | ||||||
|                 </Heading> |                 </Heading> | ||||||
|                 <span className="text-sm text-elements-high-em"> |                 <span className="text-sm text-elements-high-em"> | ||||||
| @ -225,8 +242,12 @@ const Configure = () => { | |||||||
|             {selectedOption === 'Auction' && ( |             {selectedOption === 'Auction' && ( | ||||||
|               <> |               <> | ||||||
|                 <div className="flex flex-col justify-start gap-4 mb-6"> |                 <div className="flex flex-col justify-start gap-4 mb-6"> | ||||||
|                   <Heading as="h5" className="text-sm font-sans text-elements-low-em"> |                   <Heading | ||||||
|                     Set the number of deployers and maximum price for each deployment |                     as="h5" | ||||||
|  |                     className="text-sm font-sans text-elements-low-em" | ||||||
|  |                   > | ||||||
|  |                     Set the number of deployers and maximum price for each | ||||||
|  |                     deployment | ||||||
|                   </Heading> |                   </Heading> | ||||||
|                   <span className="text-sm text-elements-high-em"> |                   <span className="text-sm text-elements-high-em"> | ||||||
|                     Number of Deployers |                     Number of Deployers | ||||||
|  | |||||||
| @ -39,7 +39,7 @@ export const ProjectRepoCard: React.FC<ProjectRepoCardProps> = ({ | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     navigate( |     navigate( | ||||||
|       `configure?owner=${repository.owner?.login}&name=${repository.name}&defaultBranch=${repository.default_branch}&fullName=${repository.full_name}&orgSlug=${orgSlug}` |       `configure?owner=${repository.owner?.login}&name=${repository.name}&defaultBranch=${repository.default_branch}&fullName=${repository.full_name}&orgSlug=${orgSlug}`, | ||||||
|     ); |     ); | ||||||
|   }, [client, repository, orgSlug, setIsLoading, navigate, toast]); |   }, [client, repository, orgSlug, setIsLoading, navigate, toast]); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -34,13 +34,13 @@ import { formatAddress } from '../../../../utils/format'; | |||||||
| import { DeploymentMenu } from './DeploymentMenu'; | import { DeploymentMenu } from './DeploymentMenu'; | ||||||
| 
 | 
 | ||||||
| const DEPLOYMENT_LOGS_STYLE = { | const DEPLOYMENT_LOGS_STYLE = { | ||||||
|   backgroundColor: "rgba(0,0,0, .9)", |   backgroundColor: 'rgba(0,0,0, .9)', | ||||||
|   padding: "2em", |   padding: '2em', | ||||||
|   borderRadius: "0.5em", |   borderRadius: '0.5em', | ||||||
|   marginLeft: "0.5em", |   marginLeft: '0.5em', | ||||||
|   marginRight: "0.5em", |   marginRight: '0.5em', | ||||||
|   color: "gray", |   color: 'gray', | ||||||
|   fontSize: "small", |   fontSize: 'small', | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| interface DeployDetailsCardProps { | interface DeployDetailsCardProps { | ||||||
| @ -95,7 +95,11 @@ const DeploymentDetailsCard = ({ | |||||||
|     const logs = await res.text(); |     const logs = await res.text(); | ||||||
|     setDeploymentLogs(logs); |     setDeploymentLogs(logs); | ||||||
|     handleOpenDialog(); |     handleOpenDialog(); | ||||||
|   }, [deployment.deployer.deployerApiUrl, deployment.applicationDeploymentRequestId, handleOpenDialog]); |   }, [ | ||||||
|  |     deployment.deployer.deployerApiUrl, | ||||||
|  |     deployment.applicationDeploymentRequestId, | ||||||
|  |     handleOpenDialog, | ||||||
|  |   ]); | ||||||
| 
 | 
 | ||||||
|   const renderDeploymentStatus = useCallback( |   const renderDeploymentStatus = useCallback( | ||||||
|     (className?: string) => { |     (className?: string) => { | ||||||
| @ -201,10 +205,15 @@ const DeploymentDetailsCard = ({ | |||||||
|           prodBranchDomains={prodBranchDomains} |           prodBranchDomains={prodBranchDomains} | ||||||
|         /> |         /> | ||||||
|       </div> |       </div> | ||||||
|       <Dialog open={openDialog} onClose={handleCloseDialog} fullWidth maxWidth="md"> |       <Dialog | ||||||
|  |         open={openDialog} | ||||||
|  |         onClose={handleCloseDialog} | ||||||
|  |         fullWidth | ||||||
|  |         maxWidth="md" | ||||||
|  |       > | ||||||
|         <DialogTitle>Deployment logs</DialogTitle> |         <DialogTitle>Deployment logs</DialogTitle> | ||||||
|         <DialogContent style={DEPLOYMENT_LOGS_STYLE} > |         <DialogContent style={DEPLOYMENT_LOGS_STYLE}> | ||||||
|           {deploymentLogs && <pre >{deploymentLogs}</pre>} |           {deploymentLogs && <pre>{deploymentLogs}</pre>} | ||||||
|         </DialogContent> |         </DialogContent> | ||||||
|         <DialogActions> |         <DialogActions> | ||||||
|           <Button onClick={handleCloseDialog}>Close</Button> |           <Button onClick={handleCloseDialog}>Close</Button> | ||||||
|  | |||||||
| @ -48,6 +48,8 @@ export const DeploymentMenu = ({ | |||||||
|   const [redeployToProduction, setRedeployToProduction] = useState(false); |   const [redeployToProduction, setRedeployToProduction] = useState(false); | ||||||
|   const [rollbackDeployment, setRollbackDeployment] = useState(false); |   const [rollbackDeployment, setRollbackDeployment] = useState(false); | ||||||
|   const [assignDomainDialog, setAssignDomainDialog] = useState(false); |   const [assignDomainDialog, setAssignDomainDialog] = useState(false); | ||||||
|  |   const [isConfirmButtonLoading, setConfirmButtonLoadingLoading] = | ||||||
|  |     useState(false); | ||||||
| 
 | 
 | ||||||
|   const updateDeployment = async () => { |   const updateDeployment = async () => { | ||||||
|     const isUpdated = await client.updateDeploymentToProd(deployment.id); |     const isUpdated = await client.updateDeploymentToProd(deployment.id); | ||||||
| @ -71,6 +73,7 @@ export const DeploymentMenu = ({ | |||||||
| 
 | 
 | ||||||
|   const redeployToProd = async () => { |   const redeployToProd = async () => { | ||||||
|     const isRedeployed = await client.redeployToProd(deployment.id); |     const isRedeployed = await client.redeployToProd(deployment.id); | ||||||
|  |     setConfirmButtonLoadingLoading(false); | ||||||
|     if (isRedeployed) { |     if (isRedeployed) { | ||||||
|       await onUpdate(); |       await onUpdate(); | ||||||
|       toast({ |       toast({ | ||||||
| @ -113,12 +116,19 @@ export const DeploymentMenu = ({ | |||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   const deleteDeployment = async () => { |   const deleteDeployment = async () => { | ||||||
|  |     toast({ | ||||||
|  |       id: 'deleting_deployment', | ||||||
|  |       title: 'Deleting deployment....', | ||||||
|  |       variant: 'success', | ||||||
|  |       onDismiss: dismiss, | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|     const isDeleted = await client.deleteDeployment(deployment.id); |     const isDeleted = await client.deleteDeployment(deployment.id); | ||||||
|     if (isDeleted) { |     if (isDeleted) { | ||||||
|       await onUpdate(); |       await onUpdate(); | ||||||
|       toast({ |       toast({ | ||||||
|         id: 'deployment_deleted', |         id: 'deployment_removal_requested', | ||||||
|         title: 'Deployment deleted', |         title: 'Deployment removal requested', | ||||||
|         variant: 'success', |         variant: 'success', | ||||||
|         onDismiss: dismiss, |         onDismiss: dismiss, | ||||||
|       }); |       }); | ||||||
| @ -228,11 +238,13 @@ export const DeploymentMenu = ({ | |||||||
|         open={redeployToProduction} |         open={redeployToProduction} | ||||||
|         confirmButtonTitle="Redeploy" |         confirmButtonTitle="Redeploy" | ||||||
|         handleConfirm={async () => { |         handleConfirm={async () => { | ||||||
|  |           setConfirmButtonLoadingLoading(true); | ||||||
|           await redeployToProd(); |           await redeployToProd(); | ||||||
|           setRedeployToProduction((preVal) => !preVal); |           setRedeployToProduction((preVal) => !preVal); | ||||||
|         }} |         }} | ||||||
|         deployment={deployment} |         deployment={deployment} | ||||||
|         domains={deployment.domain ? [deployment.domain] : []} |         domains={deployment.domain ? [deployment.domain] : []} | ||||||
|  |         isConfirmButtonLoading={isConfirmButtonLoading} | ||||||
|       /> |       /> | ||||||
|       {Boolean(currentDeployment) && ( |       {Boolean(currentDeployment) && ( | ||||||
|         <ChangeStateToProductionDialog |         <ChangeStateToProductionDialog | ||||||
|  | |||||||
| @ -8,7 +8,10 @@ import { | |||||||
|   DialogActions, |   DialogActions, | ||||||
| } from '@mui/material'; | } from '@mui/material'; | ||||||
| 
 | 
 | ||||||
| import { CheckRoundFilledIcon, LoadingIcon } from 'components/shared/CustomIcon'; | import { | ||||||
|  |   CheckRoundFilledIcon, | ||||||
|  |   LoadingIcon, | ||||||
|  | } from 'components/shared/CustomIcon'; | ||||||
| import { useGQLClient } from 'context/GQLClientContext'; | import { useGQLClient } from 'context/GQLClientContext'; | ||||||
| import { Button, Heading, Tag } from 'components/shared'; | import { Button, Heading, Tag } from 'components/shared'; | ||||||
| 
 | 
 | ||||||
| @ -23,7 +26,11 @@ export const AuctionCard = ({ project }: { project: Project }) => { | |||||||
|   const client = useGQLClient(); |   const client = useGQLClient(); | ||||||
| 
 | 
 | ||||||
|   const getIconByAuctionStatus = (status: string) => |   const getIconByAuctionStatus = (status: string) => | ||||||
|     status === 'completed' ? <CheckRoundFilledIcon /> : <LoadingIcon className="animate-spin" />; |     status === 'completed' ? ( | ||||||
|  |       <CheckRoundFilledIcon /> | ||||||
|  |     ) : ( | ||||||
|  |       <LoadingIcon className="animate-spin" /> | ||||||
|  |     ); | ||||||
| 
 | 
 | ||||||
|   const checkAuctionStatus = useCallback(async () => { |   const checkAuctionStatus = useCallback(async () => { | ||||||
|     const result = await client.getAuctionData(project.auctionId); |     const result = await client.getAuctionData(project.auctionId); | ||||||
| @ -61,7 +68,7 @@ export const AuctionCard = ({ project }: { project: Project }) => { | |||||||
|         {auctionStatus.toUpperCase()} |         {auctionStatus.toUpperCase()} | ||||||
|       </Tag> |       </Tag> | ||||||
|     ), |     ), | ||||||
|     [auctionStatus] |     [auctionStatus], | ||||||
|   ); |   ); | ||||||
| 
 | 
 | ||||||
|   const handleOpenDialog = () => setOpenDialog(true); |   const handleOpenDialog = () => setOpenDialog(true); | ||||||
| @ -71,19 +78,25 @@ export const AuctionCard = ({ project }: { project: Project }) => { | |||||||
|     <> |     <> | ||||||
|       <div className="p-3 gap-2 rounded-xl border border-gray-200 transition-colors hover:bg-base-bg-alternate flex flex-col mt-8"> |       <div className="p-3 gap-2 rounded-xl border border-gray-200 transition-colors hover:bg-base-bg-alternate flex flex-col mt-8"> | ||||||
|         <div className="flex justify-between items-center"> |         <div className="flex justify-between items-center"> | ||||||
|           <Heading className="text-lg leading-6 font-medium">Auction details</Heading> |           <Heading className="text-lg leading-6 font-medium"> | ||||||
|  |             Auction details | ||||||
|  |           </Heading> | ||||||
|           <Button onClick={handleOpenDialog} variant="tertiary" size="sm"> |           <Button onClick={handleOpenDialog} variant="tertiary" size="sm"> | ||||||
|             View details |             View details | ||||||
|           </Button> |           </Button> | ||||||
|         </div> |         </div> | ||||||
| 
 | 
 | ||||||
|         <div className="flex justify-between items-center mt-1"> |         <div className="flex justify-between items-center mt-1"> | ||||||
|           <span className="text-elements-high-em text-sm font-medium tracking-tight">Auction Status</span> |           <span className="text-elements-high-em text-sm font-medium tracking-tight"> | ||||||
|  |             Auction Status | ||||||
|  |           </span> | ||||||
|           <div className="ml-2">{renderAuctionStatus()}</div> |           <div className="ml-2">{renderAuctionStatus()}</div> | ||||||
|         </div> |         </div> | ||||||
| 
 | 
 | ||||||
|         <div className="flex justify-between items-center mt-2"> |         <div className="flex justify-between items-center mt-2"> | ||||||
|           <span className="text-elements-high-em text-sm font-medium tracking-tight">Auction Id</span> |           <span className="text-elements-high-em text-sm font-medium tracking-tight"> | ||||||
|  |             Auction Id | ||||||
|  |           </span> | ||||||
|           <span className="text-elements-mid-em text-sm text-right"> |           <span className="text-elements-mid-em text-sm text-right"> | ||||||
|             {project.auctionId} |             {project.auctionId} | ||||||
|           </span> |           </span> | ||||||
| @ -91,7 +104,9 @@ export const AuctionCard = ({ project }: { project: Project }) => { | |||||||
| 
 | 
 | ||||||
|         {deployerLrns?.length > 0 && ( |         {deployerLrns?.length > 0 && ( | ||||||
|           <div className="mt-3"> |           <div className="mt-3"> | ||||||
|             <span className="text-elements-high-em text-sm font-medium tracking-tight">Deployer LRNs</span> |             <span className="text-elements-high-em text-sm font-medium tracking-tight"> | ||||||
|  |               Deployer LRNs | ||||||
|  |             </span> | ||||||
|             {deployerLrns.map((lrn, index) => ( |             {deployerLrns.map((lrn, index) => ( | ||||||
|               <p key={index} className="text-elements-mid-em text-sm"> |               <p key={index} className="text-elements-mid-em text-sm"> | ||||||
|                 {'\u2022'} {lrn} |                 {'\u2022'} {lrn} | ||||||
| @ -101,22 +116,28 @@ export const AuctionCard = ({ project }: { project: Project }) => { | |||||||
|         )} |         )} | ||||||
| 
 | 
 | ||||||
|         <div className="flex justify-between items-center mt-1"> |         <div className="flex justify-between items-center mt-1"> | ||||||
|           <span className="text-elements-high-em text-sm font-medium tracking-tight">Deployer Funds Status</span> |           <span className="text-elements-high-em text-sm font-medium tracking-tight"> | ||||||
|  |             Deployer Funds Status | ||||||
|  |           </span> | ||||||
|           <div className="ml-2"> |           <div className="ml-2"> | ||||||
|             <Tag |             <Tag size="xs" type={fundsStatus ? 'positive' : 'emphasized'}> | ||||||
|               size="xs" |  | ||||||
|               type={fundsStatus ? 'positive' : 'emphasized'} |  | ||||||
|             > |  | ||||||
|               {fundsStatus ? 'RELEASED' : 'LOCKED'} |               {fundsStatus ? 'RELEASED' : 'LOCKED'} | ||||||
|             </Tag> |             </Tag> | ||||||
|           </div> |           </div> | ||||||
|         </div> |         </div> | ||||||
|       </div> |       </div> | ||||||
| 
 | 
 | ||||||
|       <Dialog open={openDialog} onClose={handleCloseDialog} fullWidth maxWidth="md"> |       <Dialog | ||||||
|  |         open={openDialog} | ||||||
|  |         onClose={handleCloseDialog} | ||||||
|  |         fullWidth | ||||||
|  |         maxWidth="md" | ||||||
|  |       > | ||||||
|         <DialogTitle>Auction Details</DialogTitle> |         <DialogTitle>Auction Details</DialogTitle> | ||||||
|         <DialogContent> |         <DialogContent> | ||||||
|           {auctionDetails && <pre>{JSON.stringify(auctionDetails, null, 2)}</pre>} |           {auctionDetails && ( | ||||||
|  |             <pre>{JSON.stringify(auctionDetails, null, 2)}</pre> | ||||||
|  |           )} | ||||||
|         </DialogContent> |         </DialogContent> | ||||||
|         <DialogActions> |         <DialogActions> | ||||||
|           <Button onClick={handleCloseDialog}>Close</Button> |           <Button onClick={handleCloseDialog}>Close</Button> | ||||||
|  | |||||||
| @ -60,8 +60,10 @@ const DeleteProjectDialog = ({ | |||||||
|           <Modal.Body> |           <Modal.Body> | ||||||
|             <Input |             <Input | ||||||
|               label={ |               label={ | ||||||
|                 "Deleting your project is irreversible. Enter your project's name " + '"' + |                 "Deleting your project is irreversible. Enter your project's name " + | ||||||
|                 project.name + '"' + |                 '"' + | ||||||
|  |                 project.name + | ||||||
|  |                 '"' + | ||||||
|                 ' below to confirm you want to permanently delete it:' |                 ' below to confirm you want to permanently delete it:' | ||||||
|               } |               } | ||||||
|               id="input" |               id="input" | ||||||
|  | |||||||
| @ -1,6 +1,6 @@ | |||||||
| import { useCallback, useEffect, useMemo, useState } from 'react'; | import { useCallback, useEffect, useState } from 'react'; | ||||||
| import { NavLink, useNavigate, useParams } from 'react-router-dom'; | import { useNavigate, useParams } from 'react-router-dom'; | ||||||
| import { Organization, User } from 'gql-client'; | import { User } from 'gql-client'; | ||||||
| import { motion } from 'framer-motion'; | import { motion } from 'framer-motion'; | ||||||
| import { useDisconnect } from 'wagmi'; | import { useDisconnect } from 'wagmi'; | ||||||
| 
 | 
 | ||||||
| @ -19,8 +19,6 @@ import { getInitials } from 'utils/geInitials'; | |||||||
| import { Button } from 'components/shared/Button'; | import { Button } from 'components/shared/Button'; | ||||||
| import { cn } from 'utils/classnames'; | import { cn } from 'utils/classnames'; | ||||||
| import { useMediaQuery } from 'usehooks-ts'; | import { useMediaQuery } from 'usehooks-ts'; | ||||||
| import { SIDEBAR_MENU } from './constants'; |  | ||||||
| import { UserSelect } from 'components/shared/UserSelect'; |  | ||||||
| import { BASE_URL } from 'utils/constants'; | import { BASE_URL } from 'utils/constants'; | ||||||
| 
 | 
 | ||||||
| interface SidebarProps { | interface SidebarProps { | ||||||
| @ -45,46 +43,6 @@ export const Sidebar = ({ mobileOpen }: SidebarProps) => { | |||||||
|     fetchUser(); |     fetchUser(); | ||||||
|   }, []); |   }, []); | ||||||
| 
 | 
 | ||||||
|   const [selectedOrgSlug, setSelectedOrgSlug] = useState(orgSlug); |  | ||||||
|   const [organizations, setOrganizations] = useState<Organization[]>([]); |  | ||||||
| 
 |  | ||||||
|   const fetchUserOrganizations = useCallback(async () => { |  | ||||||
|     const { organizations } = await client.getOrganizations(); |  | ||||||
|     setOrganizations(organizations); |  | ||||||
|   }, [orgSlug]); |  | ||||||
| 
 |  | ||||||
|   useEffect(() => { |  | ||||||
|     fetchUserOrganizations(); |  | ||||||
|     setSelectedOrgSlug(orgSlug); |  | ||||||
|   }, [orgSlug]); |  | ||||||
| 
 |  | ||||||
|   const formattedSelected = useMemo(() => { |  | ||||||
|     const selected = organizations.find((org) => org.slug === selectedOrgSlug); |  | ||||||
|     return { |  | ||||||
|       value: selected?.slug ?? '', |  | ||||||
|       label: selected?.name ?? '', |  | ||||||
|       imgSrc: '/logo.svg', |  | ||||||
|     }; |  | ||||||
|   }, [organizations, selectedOrgSlug, orgSlug]); |  | ||||||
| 
 |  | ||||||
|   const formattedSelectOptions = useMemo(() => { |  | ||||||
|     return organizations.map((org) => ({ |  | ||||||
|       value: org.slug, |  | ||||||
|       label: org.name, |  | ||||||
|       imgSrc: '/logo.svg', |  | ||||||
|     })); |  | ||||||
|   }, [organizations, selectedOrgSlug, orgSlug]); |  | ||||||
| 
 |  | ||||||
|   const renderMenu = useMemo(() => { |  | ||||||
|     return SIDEBAR_MENU(orgSlug).map(({ title, icon, url }, index) => ( |  | ||||||
|       <NavLink to={url} key={index}> |  | ||||||
|         <Tabs.Trigger icon={icon} value={title}> |  | ||||||
|           {title} |  | ||||||
|         </Tabs.Trigger> |  | ||||||
|       </NavLink> |  | ||||||
|     )); |  | ||||||
|   }, [orgSlug]); |  | ||||||
| 
 |  | ||||||
|   const handleLogOut = useCallback(async () => { |   const handleLogOut = useCallback(async () => { | ||||||
|     await fetch(`${BASE_URL}/auth/logout`, { |     await fetch(`${BASE_URL}/auth/logout`, { | ||||||
|       method: 'POST', |       method: 'POST', | ||||||
| @ -117,16 +75,8 @@ export const Sidebar = ({ mobileOpen }: SidebarProps) => { | |||||||
|         <div className="hidden lg:flex"> |         <div className="hidden lg:flex"> | ||||||
|           <Logo orgSlug={orgSlug} /> |           <Logo orgSlug={orgSlug} /> | ||||||
|         </div> |         </div> | ||||||
|         {/* Switch organization */} |         {/* This element ensures the space between logo and navigation */} | ||||||
|         <div className="flex flex-1 flex-col gap-4"> |         <div className="flex-1"></div> | ||||||
|           <UserSelect |  | ||||||
|             value={formattedSelected} |  | ||||||
|             options={formattedSelectOptions} |  | ||||||
|           /> |  | ||||||
|           <Tabs defaultValue="Projects" orientation="vertical"> |  | ||||||
|             <Tabs.List>{renderMenu}</Tabs.List> |  | ||||||
|           </Tabs> |  | ||||||
|         </div> |  | ||||||
|         {/* Bottom navigation */} |         {/* Bottom navigation */} | ||||||
|         <div className="flex flex-col gap-5 justify-end"> |         <div className="flex flex-col gap-5 justify-end"> | ||||||
|           <Tabs defaultValue="Projects" orientation="vertical"> |           <Tabs defaultValue="Projects" orientation="vertical"> | ||||||
|  | |||||||
| @ -55,7 +55,7 @@ const siweConfig = createSIWEConfig({ | |||||||
|       statement: 'Sign in With Ethereum.', |       statement: 'Sign in With Ethereum.', | ||||||
|     }).prepareMessage(), |     }).prepareMessage(), | ||||||
|   getNonce: async () => { |   getNonce: async () => { | ||||||
|     return generateNonce() |     return generateNonce(); | ||||||
|   }, |   }, | ||||||
|   getSession: async () => { |   getSession: async () => { | ||||||
|     try { |     try { | ||||||
|  | |||||||
| @ -17,7 +17,6 @@ import { LogErrorBoundary } from 'utils/log-error'; | |||||||
| import { BASE_URL } from 'utils/constants'; | import { BASE_URL } from 'utils/constants'; | ||||||
| import Web3ModalProvider from './context/Web3Provider'; | import Web3ModalProvider from './context/Web3Provider'; | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| console.log(`v-0.0.9`); | console.log(`v-0.0.9`); | ||||||
| 
 | 
 | ||||||
| const root = ReactDOM.createRoot( | const root = ReactDOM.createRoot( | ||||||
|  | |||||||
| @ -39,7 +39,7 @@ const deployment: Deployment = { | |||||||
|   deployer: { |   deployer: { | ||||||
|     deployerApiUrl: 'https://webapp-deployer-api.example.com', |     deployerApiUrl: 'https://webapp-deployer-api.example.com', | ||||||
|     deployerId: 'bafyreicrtgmkir4evvvysxdqxddf2ftdq2wrzuodgvwnxr4rmubi4obdfu', |     deployerId: 'bafyreicrtgmkir4evvvysxdqxddf2ftdq2wrzuodgvwnxr4rmubi4obdfu', | ||||||
|     deployerLrn:'lrn://example/deployers/webapp-deployer-api.example.com' |     deployerLrn: 'lrn://example/deployers/webapp-deployer-api.example.com', | ||||||
|   }, |   }, | ||||||
|   status: DeploymentStatus.Ready, |   status: DeploymentStatus.Ready, | ||||||
|   createdBy: { |   createdBy: { | ||||||
| @ -53,7 +53,8 @@ const deployment: Deployment = { | |||||||
|   }, |   }, | ||||||
|   createdAt: '1677676800', // 2023-03-01T12:00:00Z
 |   createdAt: '1677676800', // 2023-03-01T12:00:00Z
 | ||||||
|   updatedAt: '1677680400', // 2023-03-01T13:00:00Z
 |   updatedAt: '1677680400', // 2023-03-01T13:00:00Z
 | ||||||
|   applicationDeploymentRequestId: 'bafyreiaycvq6imoppnpwdve4smj6t6ql5svt5zl3x6rimu4qwyzgjorize', |   applicationDeploymentRequestId: | ||||||
|  |     'bafyreiaycvq6imoppnpwdve4smj6t6ql5svt5zl3x6rimu4qwyzgjorize', | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| const domains: Domain[] = [ | const domains: Domain[] = [ | ||||||
|  | |||||||
| @ -48,7 +48,9 @@ const Id = () => { | |||||||
|           {/* Heading */} |           {/* Heading */} | ||||||
|           <div className="flex flex-col items-center gap-1.5"> |           <div className="flex flex-col items-center gap-1.5"> | ||||||
|             <Heading as="h3" className="font-medium text-xl"> |             <Heading as="h3" className="font-medium text-xl"> | ||||||
|               {isAuction? 'Auction created successfully.' : 'Project deployment created successfully.'} |               {isAuction | ||||||
|  |                 ? 'Auction created successfully.' | ||||||
|  |                 : 'Project deployment created successfully.'} | ||||||
|             </Heading> |             </Heading> | ||||||
|           </div> |           </div> | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -54,7 +54,7 @@ const CreateRepo = () => { | |||||||
|         setIsLoading(true); |         setIsLoading(true); | ||||||
| 
 | 
 | ||||||
|         navigate( |         navigate( | ||||||
|           `configure?templateId=${template.id}&templateOwner=${owner}&templateRepo=${repo}&owner=${data.account}&name=${data.repoName}&isPrivate=false&orgSlug=${orgSlug}` |           `configure?templateId=${template.id}&templateOwner=${owner}&templateRepo=${repo}&owner=${data.account}&name=${data.repoName}&isPrivate=false&orgSlug=${orgSlug}`, | ||||||
|         ); |         ); | ||||||
|       } catch (err) { |       } catch (err) { | ||||||
|         setIsLoading(false); |         setIsLoading(false); | ||||||
| @ -173,7 +173,7 @@ const CreateRepo = () => { | |||||||
|           <Controller |           <Controller | ||||||
|             name="isPrivate" |             name="isPrivate" | ||||||
|             control={control} |             control={control} | ||||||
|             render={({ }) => ( |             render={({}) => ( | ||||||
|               <Checkbox label="Make this repo private" disabled={true} /> |               <Checkbox label="Make this repo private" disabled={true} /> | ||||||
|             )} |             )} | ||||||
|           /> |           /> | ||||||
|  | |||||||
| @ -129,14 +129,18 @@ const OverviewTabPanel = () => { | |||||||
|             <Heading className="text-lg leading-6 font-medium truncate"> |             <Heading className="text-lg leading-6 font-medium truncate"> | ||||||
|               {project.name} |               {project.name} | ||||||
|             </Heading> |             </Heading> | ||||||
|             {project.baseDomains && project.baseDomains.length > 0 && project.baseDomains.map((baseDomain, index) => ( |             {project.baseDomains && | ||||||
|  |               project.baseDomains.length > 0 && | ||||||
|  |               project.baseDomains.map((baseDomain, index) => ( | ||||||
|  |                 <p> | ||||||
|                   <a |                   <a | ||||||
|                     key={index} |                     key={index} | ||||||
|                 href={`https://${project.name}.${baseDomain}`} |                     href={`https://${project.name.toLowerCase()}.${baseDomain}`} | ||||||
|                     className="text-sm text-elements-low-em tracking-tight truncate" |                     className="text-sm text-elements-low-em tracking-tight truncate" | ||||||
|                   > |                   > | ||||||
|                     {baseDomain} |                     {baseDomain} | ||||||
|                   </a> |                   </a> | ||||||
|  |                 </p> | ||||||
|               ))} |               ))} | ||||||
|           </div> |           </div> | ||||||
|         </div> |         </div> | ||||||
| @ -209,7 +213,7 @@ const OverviewTabPanel = () => { | |||||||
|             No current deployment found. |             No current deployment found. | ||||||
|           </p> |           </p> | ||||||
|         )} |         )} | ||||||
|         {project.auctionId && <AuctionCard project={project}/>} |         {project.auctionId && <AuctionCard project={project} />} | ||||||
|       </div> |       </div> | ||||||
|       <Activity activities={activities} isLoading={fetchingActivities} /> |       <Activity activities={activities} isLoading={fetchingActivities} /> | ||||||
|     </div> |     </div> | ||||||
|  | |||||||
| @ -64,7 +64,8 @@ export const EnvironmentVariablesTabPanel = () => { | |||||||
| 
 | 
 | ||||||
|   const createEnvironmentVariablesHandler = useCallback( |   const createEnvironmentVariablesHandler = useCallback( | ||||||
|     async (createFormData: FieldValues) => { |     async (createFormData: FieldValues) => { | ||||||
|       const environmentVariables = createFormData.variables.map((variable: any) => { |       const environmentVariables = createFormData.variables.map( | ||||||
|  |         (variable: any) => { | ||||||
|           return { |           return { | ||||||
|             key: variable.key, |             key: variable.key, | ||||||
|             value: variable.value, |             value: variable.value, | ||||||
| @ -72,7 +73,8 @@ export const EnvironmentVariablesTabPanel = () => { | |||||||
|               .filter(([, value]) => value === true) |               .filter(([, value]) => value === true) | ||||||
|               .map(([key]) => key.charAt(0).toUpperCase() + key.slice(1)), |               .map(([key]) => key.charAt(0).toUpperCase() + key.slice(1)), | ||||||
|           }; |           }; | ||||||
|       }); |         }, | ||||||
|  |       ); | ||||||
| 
 | 
 | ||||||
|       const { addEnvironmentVariables: isEnvironmentVariablesAdded } = |       const { addEnvironmentVariables: isEnvironmentVariablesAdded } = | ||||||
|         await client.addEnvironmentVariables(id!, environmentVariables); |         await client.addEnvironmentVariables(id!, environmentVariables); | ||||||
| @ -124,7 +126,11 @@ export const EnvironmentVariablesTabPanel = () => { | |||||||
|         </Heading> |         </Heading> | ||||||
|         <Collapse open={createNewVariable}> |         <Collapse open={createNewVariable}> | ||||||
|           <FormProvider {...methods}> |           <FormProvider {...methods}> | ||||||
|             <form onSubmit={methods.handleSubmit((data) => createEnvironmentVariablesHandler(data))}> |             <form | ||||||
|  |               onSubmit={methods.handleSubmit((data) => | ||||||
|  |                 createEnvironmentVariablesHandler(data), | ||||||
|  |               )} | ||||||
|  |             > | ||||||
|               <div className="p-4 bg-slate-100"> |               <div className="p-4 bg-slate-100"> | ||||||
|                 <EnvironmentVariablesForm /> |                 <EnvironmentVariablesForm /> | ||||||
|               </div> |               </div> | ||||||
|  | |||||||
| @ -67,7 +67,10 @@ const EnvironmentVariablesForm = () => { | |||||||
|       <div className="flex gap-2 p-2"> |       <div className="flex gap-2 p-2"> | ||||||
|         <Checkbox label="Production" {...register('environment.production')} /> |         <Checkbox label="Production" {...register('environment.production')} /> | ||||||
|         <Checkbox label="Preview" {...register('environment.preview')} /> |         <Checkbox label="Preview" {...register('environment.preview')} /> | ||||||
|         <Checkbox label="Development" {...register('environment.development')} /> |         <Checkbox | ||||||
|  |           label="Development" | ||||||
|  |           {...register('environment.development')} | ||||||
|  |         /> | ||||||
|       </div> |       </div> | ||||||
|     </> |     </> | ||||||
|   ); |   ); | ||||||
|  | |||||||
| @ -105,9 +105,10 @@ export const deployment0: Deployment = { | |||||||
|   deployer: { |   deployer: { | ||||||
|     deployerApiUrl: 'https://webapp-deployer-api.example.com', |     deployerApiUrl: 'https://webapp-deployer-api.example.com', | ||||||
|     deployerId: 'bafyreicrtgmkir4evvvysxdqxddf2ftdq2wrzuodgvwnxr4rmubi4obdfu', |     deployerId: 'bafyreicrtgmkir4evvvysxdqxddf2ftdq2wrzuodgvwnxr4rmubi4obdfu', | ||||||
|     deployerLrn:'lrn://deployer.apps.snowballtools.com ' |     deployerLrn: 'lrn://deployer.apps.snowballtools.com ', | ||||||
|   }, |   }, | ||||||
|   applicationDeploymentRequestId: 'bafyreiaycvq6imoppnpwdve4smj6t6ql5svt5zl3x6rimu4qwyzgjorize', |   applicationDeploymentRequestId: | ||||||
|  |     'bafyreiaycvq6imoppnpwdve4smj6t6ql5svt5zl3x6rimu4qwyzgjorize', | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| export const project: Project = { | export const project: Project = { | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user