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:
Nabarun Gogoi 2024-02-22 11:25:17 +05:30 committed by GitHub
parent e816c596ca
commit 6b17dce2ae
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 90 additions and 34 deletions

View File

@ -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
} }

View File

@ -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>
); );
}; };

View File

@ -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>

View File

@ -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)}

View File

@ -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;

View File

@ -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 />

View File

@ -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>

View File

@ -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>
); );