⚡️ feat: create project card component
This commit is contained in:
parent
bdd892ebe8
commit
239df3661e
@ -1,71 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { Link } from 'react-router-dom';
|
|
||||||
import { Project } from 'gql-client';
|
|
||||||
|
|
||||||
import {
|
|
||||||
Menu,
|
|
||||||
MenuHandler,
|
|
||||||
MenuList,
|
|
||||||
MenuItem,
|
|
||||||
Typography,
|
|
||||||
Avatar,
|
|
||||||
} from '@material-tailwind/react';
|
|
||||||
|
|
||||||
import { relativeTimeMs } from '../../utils/time';
|
|
||||||
|
|
||||||
interface ProjectCardProps {
|
|
||||||
project: Project;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ProjectCard: React.FC<ProjectCardProps> = ({ project }) => {
|
|
||||||
return (
|
|
||||||
<div className="bg-white border border-gray-200 rounded-lg shadow">
|
|
||||||
<div className="flex gap-2 p-2 items-center">
|
|
||||||
<Avatar
|
|
||||||
variant="rounded"
|
|
||||||
src={project.icon || '/gray.png'}
|
|
||||||
placeholder={''}
|
|
||||||
/>
|
|
||||||
<div className="grow">
|
|
||||||
<Link to={`projects/${project.id}`}>
|
|
||||||
<Typography placeholder={''}>{project.name}</Typography>
|
|
||||||
<Typography color="gray" variant="small" placeholder={''}>
|
|
||||||
{project.deployments[0]?.domain?.name ??
|
|
||||||
'No Production Deployment'}
|
|
||||||
</Typography>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
<Menu placement="bottom-end">
|
|
||||||
<MenuHandler>
|
|
||||||
<button>...</button>
|
|
||||||
</MenuHandler>
|
|
||||||
<MenuList placeholder={''}>
|
|
||||||
<MenuItem placeholder={''}>^ Project settings</MenuItem>
|
|
||||||
<MenuItem className="text-red-500" placeholder={''}>
|
|
||||||
^ Delete project
|
|
||||||
</MenuItem>
|
|
||||||
</MenuList>
|
|
||||||
</Menu>
|
|
||||||
</div>
|
|
||||||
<div className="border-t-2 border-solid p-4 bg-gray-50">
|
|
||||||
{project.deployments.length > 0 ? (
|
|
||||||
<>
|
|
||||||
<Typography variant="small" color="gray" placeholder={''}>
|
|
||||||
^ {project.deployments[0].commitMessage}
|
|
||||||
</Typography>
|
|
||||||
<Typography variant="small" color="gray" placeholder={''}>
|
|
||||||
{relativeTimeMs(project.deployments[0].createdAt)} on ^
|
|
||||||
{project.deployments[0].branch}
|
|
||||||
</Typography>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<Typography variant="small" color="gray" placeholder={''}>
|
|
||||||
No Production deployment
|
|
||||||
</Typography>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ProjectCard;
|
|
@ -0,0 +1,70 @@
|
|||||||
|
import { VariantProps, tv } from 'tailwind-variants';
|
||||||
|
|
||||||
|
export const projectCardTheme = tv({
|
||||||
|
slots: {
|
||||||
|
wrapper: [
|
||||||
|
'bg-surface-card',
|
||||||
|
'shadow-card',
|
||||||
|
'rounded-2xl',
|
||||||
|
'flex',
|
||||||
|
'flex-col',
|
||||||
|
],
|
||||||
|
upperContent: ['px-4', 'py-4', 'flex', 'items-start', 'gap-3', 'relative'],
|
||||||
|
content: ['flex', 'flex-col', 'gap-1', 'flex-1'],
|
||||||
|
title: [
|
||||||
|
'text-sm',
|
||||||
|
'font-medium',
|
||||||
|
'text-elements-high-em',
|
||||||
|
'tracking-[-0.006em]',
|
||||||
|
],
|
||||||
|
description: ['text-xs', 'text-elements-low-em'],
|
||||||
|
lowerContent: [
|
||||||
|
'bg-surface-card-hovered',
|
||||||
|
'px-4',
|
||||||
|
'py-4',
|
||||||
|
'flex',
|
||||||
|
'flex-col',
|
||||||
|
'gap-2',
|
||||||
|
'rounded-b-2xl',
|
||||||
|
],
|
||||||
|
latestDeployment: ['flex', 'items-center', 'gap-2'],
|
||||||
|
deploymentStatusContainer: [
|
||||||
|
'h-3',
|
||||||
|
'w-3',
|
||||||
|
'flex',
|
||||||
|
'items-center',
|
||||||
|
'justify-center',
|
||||||
|
],
|
||||||
|
deploymentStatus: ['w-1', 'h-1', 'rounded-full'],
|
||||||
|
deploymentName: ['text-xs', 'text-elements-low-em'],
|
||||||
|
deploymentText: [
|
||||||
|
'text-xs',
|
||||||
|
'text-elements-low-em',
|
||||||
|
'font-jetbrains-mono',
|
||||||
|
'flex',
|
||||||
|
'items-center',
|
||||||
|
'gap-2',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
variants: {
|
||||||
|
status: {
|
||||||
|
success: {
|
||||||
|
deploymentStatus: ['bg-emerald-500'],
|
||||||
|
},
|
||||||
|
'in-progress': {
|
||||||
|
deploymentStatus: ['bg-orange-400'],
|
||||||
|
},
|
||||||
|
failure: {
|
||||||
|
deploymentStatus: ['bg-rose-500'],
|
||||||
|
},
|
||||||
|
pending: {
|
||||||
|
deploymentStatus: ['bg-gray-500'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
status: 'pending',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export type ProjectCardTheme = VariantProps<typeof projectCardTheme>;
|
@ -0,0 +1,119 @@
|
|||||||
|
import React, { ComponentPropsWithoutRef, MouseEvent } from 'react';
|
||||||
|
import { ProjectCardTheme, projectCardTheme } from './ProjectCard.theme';
|
||||||
|
import { Project } from 'gql-client';
|
||||||
|
import { Button } from 'components/shared/Button';
|
||||||
|
import { WavyBorder } from 'components/shared/WavyBorder';
|
||||||
|
import {
|
||||||
|
BranchIcon,
|
||||||
|
ClockIcon,
|
||||||
|
GitHubLogo,
|
||||||
|
HorizontalDotIcon,
|
||||||
|
} from 'components/shared/CustomIcon';
|
||||||
|
import { relativeTimeMs } from 'utils/time';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import { Avatar } from 'components/shared/Avatar';
|
||||||
|
import { getInitials } from 'utils/geInitials';
|
||||||
|
import {
|
||||||
|
Menu,
|
||||||
|
MenuHandler,
|
||||||
|
MenuItem,
|
||||||
|
MenuList,
|
||||||
|
} from '@material-tailwind/react';
|
||||||
|
|
||||||
|
export interface ProjectCardProps
|
||||||
|
extends ComponentPropsWithoutRef<'div'>,
|
||||||
|
ProjectCardTheme {
|
||||||
|
project: Project;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ProjectCard = ({
|
||||||
|
className,
|
||||||
|
project,
|
||||||
|
status,
|
||||||
|
...props
|
||||||
|
}: ProjectCardProps) => {
|
||||||
|
const theme = projectCardTheme();
|
||||||
|
const hasDeployment = project.deployments.length > 0;
|
||||||
|
console.log(project);
|
||||||
|
const handleOptionsClick = (
|
||||||
|
e: MouseEvent<HTMLButtonElement, globalThis.MouseEvent>,
|
||||||
|
) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div {...props} className={theme.wrapper({ className })}>
|
||||||
|
{/* Upper content */}
|
||||||
|
<div className={theme.upperContent()}>
|
||||||
|
{/* Icon container */}
|
||||||
|
<Avatar
|
||||||
|
size={48}
|
||||||
|
imageSrc={project.icon}
|
||||||
|
initials={getInitials(project.name)}
|
||||||
|
/>
|
||||||
|
{/* </div> */}
|
||||||
|
{/* Title and website */}
|
||||||
|
<Link to={`projects/${project.id}`} className={theme.content()}>
|
||||||
|
<p className={theme.title()}>{project.name}</p>
|
||||||
|
<p className={theme.description()}>
|
||||||
|
{project.deployments[0]?.domain?.name ?? 'No domain'}
|
||||||
|
</p>
|
||||||
|
</Link>
|
||||||
|
{/* Dot icon */}
|
||||||
|
{/* // TODO: Add popover menu here */}
|
||||||
|
<Menu placement="bottom-end">
|
||||||
|
<MenuHandler>
|
||||||
|
<Button
|
||||||
|
shape="default"
|
||||||
|
size="xs"
|
||||||
|
variant="ghost"
|
||||||
|
iconOnly
|
||||||
|
onClick={handleOptionsClick}
|
||||||
|
>
|
||||||
|
<HorizontalDotIcon />
|
||||||
|
</Button>
|
||||||
|
</MenuHandler>
|
||||||
|
<MenuList placeholder={''}>
|
||||||
|
<MenuItem placeholder={''}>Project settings</MenuItem>
|
||||||
|
<MenuItem className="text-red-500" placeholder={''}>
|
||||||
|
Delete project
|
||||||
|
</MenuItem>
|
||||||
|
</MenuList>
|
||||||
|
</Menu>
|
||||||
|
</div>
|
||||||
|
{/* Wave */}
|
||||||
|
<WavyBorder />
|
||||||
|
{/* Lower content */}
|
||||||
|
<div className={theme.lowerContent()}>
|
||||||
|
{/* Latest deployment */}
|
||||||
|
<div className={theme.latestDeployment()}>
|
||||||
|
{/* Dot icon */}
|
||||||
|
<div className={theme.deploymentStatusContainer()}>
|
||||||
|
<div className={theme.deploymentStatus({ status })} />
|
||||||
|
</div>
|
||||||
|
<p className={theme.deploymentText()}>
|
||||||
|
{hasDeployment
|
||||||
|
? project.deployments[0]?.commitMessage
|
||||||
|
: 'No production deployment'}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{/* Deployment and branch name */}
|
||||||
|
<div className={theme.deploymentText()}>
|
||||||
|
{hasDeployment ? (
|
||||||
|
<>
|
||||||
|
<GitHubLogo />
|
||||||
|
<span>{relativeTimeMs(project.deployments[0].createdAt)} on</span>
|
||||||
|
<BranchIcon />
|
||||||
|
<span>{project.deployments[0].branch}</span>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<ClockIcon />
|
||||||
|
<span>Created {relativeTimeMs(project.createdAt)}</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1 @@
|
|||||||
|
export * from './ProjectCard';
|
@ -5,9 +5,9 @@ import { Button } from 'components/shared/Button';
|
|||||||
|
|
||||||
import { Typography, Chip } from '@material-tailwind/react';
|
import { Typography, Chip } from '@material-tailwind/react';
|
||||||
|
|
||||||
import ProjectCard from '../../components/projects/ProjectCard';
|
|
||||||
import { useGQLClient } from '../../context/GQLClientContext';
|
import { useGQLClient } from '../../context/GQLClientContext';
|
||||||
import { PlusIcon } from 'components/shared/CustomIcon';
|
import { PlusIcon } from 'components/shared/CustomIcon';
|
||||||
|
import { ProjectCard } from 'components/projects/ProjectCard';
|
||||||
|
|
||||||
const Projects = () => {
|
const Projects = () => {
|
||||||
const client = useGQLClient();
|
const client = useGQLClient();
|
||||||
|
Loading…
Reference in New Issue
Block a user