UI fixes in Snowball frontend app (#93)
* Fix alignment of deployment status chip * Use template name from env * Use env for git template link * Add loading spinner for create project * Display user name * Format the displayed user name --------- Co-authored-by: neeraj <neeraj.rtly@gmail.com>
This commit is contained in:
		
							parent
							
								
									e816c596ca
								
							
						
					
					
						commit
						6b17dce2ae
					
				
							
								
								
									
										6
									
								
								packages/backend/test/fixtures/users.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								packages/backend/test/fixtures/users.json
									
									
									
									
										vendored
									
									
								
							| @ -1,19 +1,19 @@ | |||||||
| [ | [ | ||||||
|   { |   { | ||||||
|     "id": "59f4355d-9549-4aac-9b54-eeefceeabef0", |     "id": "59f4355d-9549-4aac-9b54-eeefceeabef0", | ||||||
|     "name": "Snowball", |     "name": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", | ||||||
|     "email": "snowball@snowballtools.xyz", |     "email": "snowball@snowballtools.xyz", | ||||||
|     "isVerified": true |     "isVerified": true | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     "id": "e505b212-8da6-48b2-9614-098225dab34b", |     "id": "e505b212-8da6-48b2-9614-098225dab34b", | ||||||
|     "name": "Alice Anderson", |     "name": "0xbe0eb53f46cd790cd13851d5eff43d12404d33e8", | ||||||
|     "email": "alice@snowballtools.xyz", |     "email": "alice@snowballtools.xyz", | ||||||
|     "isVerified": true |     "isVerified": true | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     "id": "cd892fad-9138-4aa2-a62c-414a32776ea7", |     "id": "cd892fad-9138-4aa2-a62c-414a32776ea7", | ||||||
|     "name": "Bob Banner", |     "name": "0x8315177ab297ba92a06054ce80a67ed4dbd7ed3a", | ||||||
|     "email": "bob@snowballtools.xyz", |     "email": "bob@snowballtools.xyz", | ||||||
|     "isVerified": true |     "isVerified": true | ||||||
|   } |   } | ||||||
|  | |||||||
| @ -1,7 +1,8 @@ | |||||||
| import React, { useCallback } from 'react'; | import React, { useCallback } from 'react'; | ||||||
|  | import toast from 'react-hot-toast'; | ||||||
| import { useNavigate, useParams } from 'react-router-dom'; | import { useNavigate, useParams } from 'react-router-dom'; | ||||||
| 
 | 
 | ||||||
| import { Chip, IconButton } from '@material-tailwind/react'; | import { Chip, IconButton, Spinner } from '@material-tailwind/react'; | ||||||
| 
 | 
 | ||||||
| import { relativeTimeISO } from '../../../utils/time'; | import { relativeTimeISO } from '../../../utils/time'; | ||||||
| import { GitRepositoryDetails } from '../../../types'; | import { GitRepositoryDetails } from '../../../types'; | ||||||
| @ -14,6 +15,7 @@ interface ProjectRepoCardProps { | |||||||
| const ProjectRepoCard: React.FC<ProjectRepoCardProps> = ({ repository }) => { | const ProjectRepoCard: React.FC<ProjectRepoCardProps> = ({ repository }) => { | ||||||
|   const client = useGQLClient(); |   const client = useGQLClient(); | ||||||
|   const navigate = useNavigate(); |   const navigate = useNavigate(); | ||||||
|  |   const [isLoading, setIsLoading] = React.useState(false); | ||||||
| 
 | 
 | ||||||
|   const { orgSlug } = useParams(); |   const { orgSlug } = useParams(); | ||||||
| 
 | 
 | ||||||
| @ -22,6 +24,7 @@ const ProjectRepoCard: React.FC<ProjectRepoCardProps> = ({ repository }) => { | |||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     setIsLoading(true); | ||||||
|     const { addProject } = await client.addProject(orgSlug!, { |     const { addProject } = await client.addProject(orgSlug!, { | ||||||
|       name: `${repository.owner!.login}-${repository.name}`, |       name: `${repository.owner!.login}-${repository.name}`, | ||||||
|       prodBranch: repository.default_branch!, |       prodBranch: repository.default_branch!, | ||||||
| @ -30,7 +33,13 @@ const ProjectRepoCard: React.FC<ProjectRepoCardProps> = ({ repository }) => { | |||||||
|       template: 'webapp', |       template: 'webapp', | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     navigate(`import?projectId=${addProject.id}`); |     if (Boolean(addProject)) { | ||||||
|  |       setIsLoading(false); | ||||||
|  |       navigate(`import?projectId=${addProject.id}`); | ||||||
|  |     } else { | ||||||
|  |       setIsLoading(false); | ||||||
|  |       toast.error('Failed to create project'); | ||||||
|  |     } | ||||||
|   }, [client, repository]); |   }, [client, repository]); | ||||||
| 
 | 
 | ||||||
|   return ( |   return ( | ||||||
| @ -53,9 +62,13 @@ const ProjectRepoCard: React.FC<ProjectRepoCardProps> = ({ repository }) => { | |||||||
|         </div> |         </div> | ||||||
|         <p>{repository.updated_at && relativeTimeISO(repository.updated_at)}</p> |         <p>{repository.updated_at && relativeTimeISO(repository.updated_at)}</p> | ||||||
|       </div> |       </div> | ||||||
|       <div className="hidden group-hover:block"> |       {isLoading ? ( | ||||||
|         <IconButton size="sm">{'>'}</IconButton> |         <Spinner className="h-4 w-4" /> | ||||||
|       </div> |       ) : ( | ||||||
|  |         <div className="hidden group-hover:block"> | ||||||
|  |           <IconButton size="sm">{'>'}</IconButton> | ||||||
|  |         </div> | ||||||
|  |       )} | ||||||
|     </div> |     </div> | ||||||
|   ); |   ); | ||||||
| }; | }; | ||||||
|  | |||||||
| @ -87,18 +87,12 @@ const DeploymentDetailsCard = ({ | |||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   return ( |   return ( | ||||||
|     <div className="grid grid-cols-4 gap-2 border-b border-gray-300 p-3 my-2"> |     <div className="grid grid-cols-8 gap-2 border-b border-gray-300 p-3 my-2"> | ||||||
|       <div className="col-span-2"> |       <div className="col-span-3"> | ||||||
|         <div className="flex"> |         <div className="flex"> | ||||||
|           {deployment.url && ( |           {deployment.url && ( | ||||||
|             <Typography className=" basis-3/4">{deployment.url}</Typography> |             <Typography className=" basis-3/4">{deployment.url}</Typography> | ||||||
|           )} |           )} | ||||||
|           <Chip |  | ||||||
|             value={deployment.status} |  | ||||||
|             color={STATUS_COLORS[deployment.status] ?? 'gray'} |  | ||||||
|             variant="ghost" |  | ||||||
|             icon={<i>^</i>} |  | ||||||
|           /> |  | ||||||
|         </div> |         </div> | ||||||
|         <Typography color="gray"> |         <Typography color="gray"> | ||||||
|           {deployment.environment === Environment.Production |           {deployment.environment === Environment.Production | ||||||
| @ -107,13 +101,21 @@ const DeploymentDetailsCard = ({ | |||||||
|         </Typography> |         </Typography> | ||||||
|       </div> |       </div> | ||||||
|       <div className="col-span-1"> |       <div className="col-span-1"> | ||||||
|  |         <Chip | ||||||
|  |           value={deployment.status} | ||||||
|  |           color={STATUS_COLORS[deployment.status] ?? 'gray'} | ||||||
|  |           variant="ghost" | ||||||
|  |           icon={<i>^</i>} | ||||||
|  |         /> | ||||||
|  |       </div> | ||||||
|  |       <div className="col-span-2"> | ||||||
|         <Typography color="gray">^ {deployment.branch}</Typography> |         <Typography color="gray">^ {deployment.branch}</Typography> | ||||||
|         <Typography color="gray"> |         <Typography color="gray"> | ||||||
|           ^ {deployment.commitHash.substring(0, SHORT_COMMIT_HASH_LENGTH)}{' '} |           ^ {deployment.commitHash.substring(0, SHORT_COMMIT_HASH_LENGTH)}{' '} | ||||||
|           {deployment.commitMessage} |           {deployment.commitMessage} | ||||||
|         </Typography> |         </Typography> | ||||||
|       </div> |       </div> | ||||||
|       <div className="col-span-1 flex items-center"> |       <div className="col-span-2 flex items-center"> | ||||||
|         <Typography color="gray" className="grow"> |         <Typography color="gray" className="grow"> | ||||||
|           ^ {relativeTimeMs(deployment.createdAt)} ^ {deployment.createdBy.name} |           ^ {relativeTimeMs(deployment.createdAt)} ^ {deployment.createdBy.name} | ||||||
|         </Typography> |         </Typography> | ||||||
|  | |||||||
| @ -44,18 +44,18 @@ const FilterForm = ({ value, onChange }: FilterFormProps) => { | |||||||
|   }, [value]); |   }, [value]); | ||||||
| 
 | 
 | ||||||
|   return ( |   return ( | ||||||
|     <div className="grid grid-cols-4 gap-2 text-sm text-gray-600"> |     <div className="grid grid-cols-8 gap-2 text-sm text-gray-600"> | ||||||
|       <div className="col-span-2"> |       <div className="col-span-4"> | ||||||
|         <SearchBar |         <SearchBar | ||||||
|           placeholder="Search branches" |           placeholder="Search branches" | ||||||
|           value={searchedBranch} |           value={searchedBranch} | ||||||
|           onChange={(event) => setSearchedBranch(event.target.value)} |           onChange={(event) => setSearchedBranch(event.target.value)} | ||||||
|         /> |         /> | ||||||
|       </div> |       </div> | ||||||
|       <div className="col-span-1"> |       <div className="col-span-2"> | ||||||
|         <DatePicker mode="range" selected={dateRange} onSelect={setDateRange} /> |         <DatePicker mode="range" selected={dateRange} onSelect={setDateRange} /> | ||||||
|       </div> |       </div> | ||||||
|       <div className="col-span-1 relative"> |       <div className="col-span-2 relative"> | ||||||
|         <Select |         <Select | ||||||
|           value={selectedStatus} |           value={selectedStatus} | ||||||
|           onChange={(value) => setSelectedStatus(value as StatusOptions)} |           onChange={(value) => setSelectedStatus(value as StatusOptions)} | ||||||
|  | |||||||
| @ -1,4 +1,3 @@ | |||||||
| export const GIT_TEMPLATE_LINK = | export const GIT_TEMPLATE_LINK = `https://github.com/${process.env.REACT_APP_GITHUB_TEMPLATE_REPO}`; | ||||||
|   'https://git.vdb.to/cerc-io/test-progressive-web-app'; |  | ||||||
| 
 | 
 | ||||||
| export const SHORT_COMMIT_HASH_LENGTH = 8; | export const SHORT_COMMIT_HASH_LENGTH = 8; | ||||||
|  | |||||||
| @ -1,13 +1,40 @@ | |||||||
| import React from 'react'; | import React, { useCallback, useEffect, useMemo, useState } from 'react'; | ||||||
| import { Outlet, useNavigate } from 'react-router-dom'; | import { Outlet, useNavigate } from 'react-router-dom'; | ||||||
|  | import { User } from 'gql-client'; | ||||||
| 
 | 
 | ||||||
| import { IconButton, Typography } from '@material-tailwind/react'; | import { IconButton, Tooltip, Typography } from '@material-tailwind/react'; | ||||||
| 
 | 
 | ||||||
| import HorizontalLine from '../components/HorizontalLine'; | import HorizontalLine from '../components/HorizontalLine'; | ||||||
| import ProjectSearchBar from '../components/projects/ProjectSearchBar'; | import ProjectSearchBar from '../components/projects/ProjectSearchBar'; | ||||||
|  | import { useGQLClient } from '../context/GQLClientContext'; | ||||||
| 
 | 
 | ||||||
| const ProjectSearch = () => { | const ProjectSearch = () => { | ||||||
|   const navigate = useNavigate(); |   const navigate = useNavigate(); | ||||||
|  |   const client = useGQLClient(); | ||||||
|  |   const [user, setUser] = useState<User>(); | ||||||
|  | 
 | ||||||
|  |   const fetchUser = useCallback(async () => { | ||||||
|  |     const { user } = await client.getUser(); | ||||||
|  |     setUser(user); | ||||||
|  |   }, []); | ||||||
|  | 
 | ||||||
|  |   const formattedAddress = useMemo(() => { | ||||||
|  |     const address = user?.name || ''; | ||||||
|  | 
 | ||||||
|  |     if (address.length <= 8) { | ||||||
|  |       return address; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (address.startsWith('0x')) { | ||||||
|  |       return address.slice(0, 4) + '..' + address.slice(-4); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return address; | ||||||
|  |   }, [user?.name]); | ||||||
|  | 
 | ||||||
|  |   useEffect(() => { | ||||||
|  |     fetchUser(); | ||||||
|  |   }, []); | ||||||
| 
 | 
 | ||||||
|   return ( |   return ( | ||||||
|     <div> |     <div> | ||||||
| @ -28,8 +55,10 @@ const ProjectSearch = () => { | |||||||
|           <div className="mr-2 flex items-center"> |           <div className="mr-2 flex items-center"> | ||||||
|             <Typography>^</Typography> |             <Typography>^</Typography> | ||||||
|           </div> |           </div> | ||||||
|           <div className="px-2 py-1 bg-blue-gray-50 rounded-lg"> |           <div className="px-2 py-1 bg-blue-gray-50 rounded-lg flex items-center"> | ||||||
|             <Typography variant="lead">SY</Typography> |             {user?.name && ( | ||||||
|  |               <Tooltip content={user.name}>{formattedAddress}</Tooltip> | ||||||
|  |             )} | ||||||
|           </div> |           </div> | ||||||
|         </div> |         </div> | ||||||
|         <HorizontalLine /> |         <HorizontalLine /> | ||||||
|  | |||||||
| @ -50,7 +50,7 @@ const CreateWithTemplate = () => { | |||||||
|         <div className="grow px-2">{template?.name}</div> |         <div className="grow px-2">{template?.name}</div> | ||||||
|         <div> |         <div> | ||||||
|           <a href={GIT_TEMPLATE_LINK} target="_blank" rel="noreferrer"> |           <a href={GIT_TEMPLATE_LINK} target="_blank" rel="noreferrer"> | ||||||
|             ^ cerc-io/test-progressive-web-app |             ^ {process.env.REACT_APP_GITHUB_TEMPLATE_REPO} | ||||||
|           </a> |           </a> | ||||||
|         </div> |         </div> | ||||||
|       </div> |       </div> | ||||||
|  | |||||||
| @ -4,7 +4,7 @@ import { useNavigate, useParams } from 'react-router-dom'; | |||||||
| import toast from 'react-hot-toast'; | import toast from 'react-hot-toast'; | ||||||
| import assert from 'assert'; | import assert from 'assert'; | ||||||
| 
 | 
 | ||||||
| import { Option, Typography } from '@material-tailwind/react'; | import { Button, Option, Typography } from '@material-tailwind/react'; | ||||||
| 
 | 
 | ||||||
| import { useOctokit } from '../../../../../context/OctokitContext'; | import { useOctokit } from '../../../../../context/OctokitContext'; | ||||||
| import { useGQLClient } from '../../../../../context/GQLClientContext'; | import { useGQLClient } from '../../../../../context/GQLClientContext'; | ||||||
| @ -27,10 +27,12 @@ const CreateRepo = () => { | |||||||
|   const navigate = useNavigate(); |   const navigate = useNavigate(); | ||||||
| 
 | 
 | ||||||
|   const [gitAccounts, setGitAccounts] = useState<string[]>([]); |   const [gitAccounts, setGitAccounts] = useState<string[]>([]); | ||||||
|  |   const [isLoading, setIsLoading] = useState(false); | ||||||
| 
 | 
 | ||||||
|   const submitRepoHandler: SubmitHandler<SubmitRepoValues> = useCallback( |   const submitRepoHandler: SubmitHandler<SubmitRepoValues> = useCallback( | ||||||
|     async (data) => { |     async (data) => { | ||||||
|       assert(data.account); |       assert(data.account); | ||||||
|  |       setIsLoading(true); | ||||||
| 
 | 
 | ||||||
|       try { |       try { | ||||||
|         assert( |         assert( | ||||||
| @ -62,11 +64,17 @@ const CreateRepo = () => { | |||||||
|           template: 'webapp', |           template: 'webapp', | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|         navigate( |         if (Boolean(addProject)) { | ||||||
|           `/${orgSlug}/projects/create/template/deploy?projectId=${addProject.id}`, |           setIsLoading(true); | ||||||
|         ); |           navigate( | ||||||
|  |             `/${orgSlug}/projects/create/template/deploy?projectId=${addProject.id}`, | ||||||
|  |           ); | ||||||
|  |         } else { | ||||||
|  |           setIsLoading(false); | ||||||
|  |         } | ||||||
|       } catch (err) { |       } catch (err) { | ||||||
|         console.error(err); |         console.error(err); | ||||||
|  |         setIsLoading(false); | ||||||
|         toast.error('Error deploying project'); |         toast.error('Error deploying project'); | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
| @ -174,9 +182,14 @@ const CreateRepo = () => { | |||||||
|         </label> |         </label> | ||||||
|       </div> |       </div> | ||||||
|       <div className="mb-2"> |       <div className="mb-2"> | ||||||
|         <button className="bg-blue-500 rounded-xl p-2" type="submit"> |         <Button | ||||||
|  |           className="bg-blue-500 rounded-xl p-2" | ||||||
|  |           type="submit" | ||||||
|  |           disabled={isLoading} | ||||||
|  |           loading={isLoading} | ||||||
|  |         > | ||||||
|           Deploy ^ |           Deploy ^ | ||||||
|         </button> |         </Button> | ||||||
|       </div> |       </div> | ||||||
|     </form> |     </form> | ||||||
|   ); |   ); | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user