mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
style/projects_page
This commit is contained in:
parent
1b369feb6a
commit
76b8b9eaef
@ -1,122 +0,0 @@
|
||||
import React from "react";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
// ui
|
||||
// icons
|
||||
import {
|
||||
CalendarDaysIcon,
|
||||
CheckIcon,
|
||||
PencilIcon,
|
||||
PlusIcon,
|
||||
TrashIcon,
|
||||
ClipboardDocumentListIcon,
|
||||
} from "@heroicons/react/24/outline";
|
||||
// types
|
||||
// ui
|
||||
import { Button } from "components/ui";
|
||||
// hooks
|
||||
import useProjectMembers from "hooks/use-project-members";
|
||||
// helpers
|
||||
import { renderShortNumericDateFormat } from "helpers/date-time.helper";
|
||||
// types
|
||||
import type { IProject } from "types";
|
||||
|
||||
export type ProjectCardProps = {
|
||||
workspaceSlug: string;
|
||||
project: IProject;
|
||||
setToJoinProject: (id: string | null) => void;
|
||||
setDeleteProject: (id: string | null) => void;
|
||||
};
|
||||
|
||||
export const ProjectCard: React.FC<ProjectCardProps> = (props) => {
|
||||
const { workspaceSlug, project, setToJoinProject, setDeleteProject } = props;
|
||||
// router
|
||||
const router = useRouter();
|
||||
// fetching project members information
|
||||
const { members, isMember, canDelete, canEdit } = useProjectMembers(workspaceSlug, project.id);
|
||||
|
||||
if (!members) {
|
||||
return (
|
||||
<div className="flex h-36 w-full flex-col rounded-md bg-white px-4 py-3">
|
||||
<div className="h-full w-full animate-pulse bg-gray-50" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex h-full w-full flex-col rounded-md border bg-white px-4 py-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex gap-2 text-lg font-medium">
|
||||
<Link href={`/${workspaceSlug}/projects/${project.id}/issues`}>
|
||||
<a className="flex items-center gap-x-3">
|
||||
{project.icon && (
|
||||
<span className="text-base">{String.fromCodePoint(parseInt(project.icon))}</span>
|
||||
)}
|
||||
<span className=" w-auto max-w-[220px] text-ellipsis whitespace-nowrap overflow-hidden">
|
||||
{project.name}
|
||||
</span>
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
{isMember ? (
|
||||
<div className="flex">
|
||||
{canEdit && (
|
||||
<Link href={`/${workspaceSlug}/projects/${project.id}/settings`}>
|
||||
<a className="grid h-7 w-7 cursor-pointer place-items-center rounded p-1 duration-300 hover:bg-gray-100">
|
||||
<PencilIcon className="h-4 w-4" />
|
||||
</a>
|
||||
</Link>
|
||||
)}
|
||||
{canDelete && (
|
||||
<button
|
||||
type="button"
|
||||
className="grid h-7 w-7 place-items-center rounded p-1 outline-none duration-300 hover:bg-gray-100"
|
||||
onClick={() => setDeleteProject(project.id)}
|
||||
>
|
||||
<TrashIcon className="h-4 w-4 text-red-500" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="mt-2">
|
||||
<p className="text-sm">{project.description}</p>
|
||||
</div>
|
||||
<div className="mt-3 flex h-full items-end justify-between">
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
theme="secondary"
|
||||
className="flex items-center gap-1"
|
||||
onClick={() => router.push(`/${workspaceSlug}/projects/${project.id}/issues`)}
|
||||
>
|
||||
<ClipboardDocumentListIcon className="h-3 w-3" />
|
||||
Open Project
|
||||
</Button>
|
||||
{!isMember ? (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setToJoinProject(project.id);
|
||||
}}
|
||||
className="flex cursor-pointer items-center gap-1 rounded border p-2 text-xs font-medium duration-300 hover:bg-gray-100"
|
||||
>
|
||||
<PlusIcon className="h-3 w-3" />
|
||||
<span>Select to Join</span>
|
||||
</button>
|
||||
) : (
|
||||
<div className="flex items-center gap-1 text-xs">
|
||||
<CheckIcon className="h-3 w-3" />
|
||||
Member
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="mb-1 flex items-center gap-1 text-xs">
|
||||
<CalendarDaysIcon className="h-4 w-4" />
|
||||
{renderShortNumericDateFormat(project.created_at)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
@ -1,4 +1,4 @@
|
||||
export * from "./card";
|
||||
export * from "./single-project-card";
|
||||
export * from "./create-project-modal";
|
||||
export * from "./join-project";
|
||||
export * from "./sidebar-list";
|
||||
|
131
apps/app/components/project/single-project-card.tsx
Normal file
131
apps/app/components/project/single-project-card.tsx
Normal file
@ -0,0 +1,131 @@
|
||||
import React from "react";
|
||||
|
||||
import { useRouter } from "next/router";
|
||||
import Link from "next/link";
|
||||
|
||||
// hooks
|
||||
import useProjectMembers from "hooks/use-project-members";
|
||||
import useToast from "hooks/use-toast";
|
||||
// ui
|
||||
import { CustomMenu, Loader } from "components/ui";
|
||||
// icons
|
||||
import { CalendarDaysIcon, PencilIcon, PlusIcon } from "@heroicons/react/24/outline";
|
||||
import { StarIcon } from "@heroicons/react/20/solid";
|
||||
// helpers
|
||||
import { renderShortNumericDateFormat } from "helpers/date-time.helper";
|
||||
import { copyTextToClipboard } from "helpers/string.helper";
|
||||
// types
|
||||
import type { IProject } from "types";
|
||||
|
||||
export type ProjectCardProps = {
|
||||
project: IProject;
|
||||
setToJoinProject: (id: string | null) => void;
|
||||
setDeleteProject: (id: string | null) => void;
|
||||
};
|
||||
|
||||
export const SingleProjectCard: React.FC<ProjectCardProps> = ({
|
||||
project,
|
||||
setToJoinProject,
|
||||
setDeleteProject,
|
||||
}) => {
|
||||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query;
|
||||
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
// fetching project members information
|
||||
const { members, isMember, canDelete, canEdit } = useProjectMembers(
|
||||
workspaceSlug as string,
|
||||
project.id
|
||||
);
|
||||
|
||||
const handleCopyText = () => {
|
||||
const originURL =
|
||||
typeof window !== "undefined" && window.location.origin ? window.location.origin : "";
|
||||
|
||||
copyTextToClipboard(`${originURL}/${workspaceSlug}/projects/${project.id}/issues`).then(() => {
|
||||
setToastAlert({
|
||||
type: "success",
|
||||
title: "Link Copied!",
|
||||
message: "Project link copied to clipboard.",
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{members ? (
|
||||
<Link href={`/${workspaceSlug as string}/projects/${project.id}/issues`}>
|
||||
<a className="shadow rounded-[10px]">
|
||||
<div
|
||||
className="relative h-32 bg-center bg-cover bg-no-repeat rounded-t-[10px]"
|
||||
style={{
|
||||
backgroundImage: `url(${
|
||||
project.cover_image ??
|
||||
"https://images.unsplash.com/photo-1469474968028-56623f02e42e?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=874&q=80"
|
||||
})`,
|
||||
}}
|
||||
>
|
||||
<div className="absolute left-7 bottom-4 flex items-center gap-3 text-white">
|
||||
{!isMember ? (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setToJoinProject(project.id);
|
||||
}}
|
||||
className="flex cursor-pointer items-center gap-1 rounded border p-2 text-xs font-medium duration-300 hover:bg-gray-100"
|
||||
>
|
||||
<PlusIcon className="h-3 w-3" />
|
||||
<span>Select to Join</span>
|
||||
</button>
|
||||
) : (
|
||||
<span className="bg-[#09A953] px-2 py-1 rounded text-xs">Member</span>
|
||||
)}
|
||||
<span className="bg-[#f7ae59] h-6 w-9 grid place-items-center rounded">
|
||||
<StarIcon className="h-3 w-3" />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="px-7 py-4 rounded-b-[10px]">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="text-1.5xl font-semibold">{project.name}</div>
|
||||
</div>
|
||||
<p className="mt-3.5 mb-7">{project.description}</p>
|
||||
<div className="flex justify-between">
|
||||
<div className="flex items-center gap-1.5 text-xs">
|
||||
<CalendarDaysIcon className="h-4 w-4" />
|
||||
{renderShortNumericDateFormat(project.created_at)}
|
||||
</div>
|
||||
{isMember ? (
|
||||
<div className="flex items-center">
|
||||
{canEdit && (
|
||||
<Link href={`/${workspaceSlug}/projects/${project.id}/settings`}>
|
||||
<a className="grid cursor-pointer place-items-center rounded p-1 duration-300 hover:bg-gray-100">
|
||||
<PencilIcon className="h-4 w-4" />
|
||||
</a>
|
||||
</Link>
|
||||
)}
|
||||
{canDelete && (
|
||||
<CustomMenu width="auto" verticalEllipsis>
|
||||
<CustomMenu.MenuItem onClick={() => setDeleteProject(project.id)}>
|
||||
Delete project
|
||||
</CustomMenu.MenuItem>
|
||||
<CustomMenu.MenuItem onClick={handleCopyText}>
|
||||
Copy project link
|
||||
</CustomMenu.MenuItem>
|
||||
</CustomMenu>
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</Link>
|
||||
) : (
|
||||
<Loader>
|
||||
<Loader.Item height="144px" />
|
||||
</Loader>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
@ -11,6 +11,7 @@ type Props = {
|
||||
label?: string | JSX.Element;
|
||||
className?: string;
|
||||
ellipsis?: boolean;
|
||||
verticalEllipsis?: boolean;
|
||||
width?: "sm" | "md" | "lg" | "xl" | "auto";
|
||||
textAlignment?: "left" | "center" | "right";
|
||||
noBorder?: boolean;
|
||||
@ -30,6 +31,7 @@ const CustomMenu = ({
|
||||
label,
|
||||
className = "",
|
||||
ellipsis = false,
|
||||
verticalEllipsis = false,
|
||||
width = "auto",
|
||||
textAlignment,
|
||||
noBorder = false,
|
||||
@ -37,9 +39,9 @@ const CustomMenu = ({
|
||||
}: Props) => (
|
||||
<Menu as="div" className={`relative w-min whitespace-nowrap text-left ${className}`}>
|
||||
<div>
|
||||
{ellipsis ? (
|
||||
{ellipsis || verticalEllipsis ? (
|
||||
<Menu.Button className="relative grid place-items-center rounded p-1 hover:bg-gray-100 focus:outline-none">
|
||||
<EllipsisHorizontalIcon className="h-4 w-4" />
|
||||
<EllipsisHorizontalIcon className={`h-4 w-4 ${verticalEllipsis ? "rotate-90" : ""}`} />
|
||||
</Menu.Button>
|
||||
) : (
|
||||
<Menu.Button
|
||||
|
@ -10,7 +10,7 @@ import useWorkspaces from "hooks/use-workspaces";
|
||||
import AppLayout from "layouts/app-layout";
|
||||
// components
|
||||
import { JoinProjectModal } from "components/project/join-project-modal";
|
||||
import { ProjectCard } from "components/project";
|
||||
import { SingleProjectCard } from "components/project";
|
||||
import ConfirmProjectDeletion from "components/project/confirm-project-deletion";
|
||||
// ui
|
||||
import { HeaderButton, EmptySpace, EmptySpaceItem, Loader } from "components/ui";
|
||||
@ -101,18 +101,15 @@ const ProjectsPage: NextPage = () => {
|
||||
</EmptySpace>
|
||||
</div>
|
||||
) : (
|
||||
<div className="h-full w-full space-y-5">
|
||||
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3">
|
||||
{projects.map((item) => (
|
||||
<ProjectCard
|
||||
key={item.id}
|
||||
project={item}
|
||||
workspaceSlug={(activeWorkspace as any)?.slug}
|
||||
setToJoinProject={setSelectedProjectToJoin}
|
||||
setDeleteProject={setDeleteProject}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-9">
|
||||
{projects.map((project) => (
|
||||
<SingleProjectCard
|
||||
key={project.id}
|
||||
project={project}
|
||||
setToJoinProject={setSelectedProjectToJoin}
|
||||
setDeleteProject={setDeleteProject}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
|
@ -4,6 +4,13 @@
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer components {
|
||||
.text-1\.5xl {
|
||||
font-size: 1.375rem;
|
||||
line-height: 1.875rem;
|
||||
}
|
||||
}
|
||||
|
||||
@layer base {
|
||||
html {
|
||||
font-family: "Inter", sans-serif;
|
||||
@ -24,7 +31,7 @@
|
||||
}
|
||||
|
||||
.scrollbar-enable::-webkit-scrollbar {
|
||||
display: block ;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Scrollbar style */
|
||||
|
1
apps/app/types/projects.d.ts
vendored
1
apps/app/types/projects.d.ts
vendored
@ -1,6 +1,7 @@
|
||||
import type { IUserLite, IWorkspace } from "./";
|
||||
|
||||
export interface IProject {
|
||||
cover_image: string | null;
|
||||
created_at: Date;
|
||||
created_by: string;
|
||||
cycle_view: boolean;
|
||||
|
Loading…
Reference in New Issue
Block a user