forked from cerc-io/snowballtools-base
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',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (Boolean(addProject)) {
|
||||||
|
setIsLoading(false);
|
||||||
navigate(`import?projectId=${addProject.id}`);
|
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>
|
||||||
|
{isLoading ? (
|
||||||
|
<Spinner className="h-4 w-4" />
|
||||||
|
) : (
|
||||||
<div className="hidden group-hover:block">
|
<div className="hidden group-hover:block">
|
||||||
<IconButton size="sm">{'>'}</IconButton>
|
<IconButton size="sm">{'>'}</IconButton>
|
||||||
</div>
|
</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',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (Boolean(addProject)) {
|
||||||
|
setIsLoading(true);
|
||||||
navigate(
|
navigate(
|
||||||
`/${orgSlug}/projects/create/template/deploy?projectId=${addProject.id}`,
|
`/${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