From 76b8b9eaefc87ac2da1f5e9fe209e119d53b1b20 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal Date: Tue, 28 Feb 2023 23:50:21 +0530 Subject: [PATCH 1/3] style/projects_page --- apps/app/components/project/card.tsx | 122 ---------------- apps/app/components/project/index.ts | 2 +- .../project/single-project-card.tsx | 131 ++++++++++++++++++ apps/app/components/ui/custom-menu.tsx | 6 +- .../pages/[workspaceSlug]/projects/index.tsx | 23 ++- apps/app/styles/globals.css | 9 +- apps/app/types/projects.d.ts | 1 + 7 files changed, 155 insertions(+), 139 deletions(-) delete mode 100644 apps/app/components/project/card.tsx create mode 100644 apps/app/components/project/single-project-card.tsx diff --git a/apps/app/components/project/card.tsx b/apps/app/components/project/card.tsx deleted file mode 100644 index 8100379d4..000000000 --- a/apps/app/components/project/card.tsx +++ /dev/null @@ -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 = (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 ( -
-
-
- ); - } - - return ( - <> -
-
- - {isMember ? ( -
- {canEdit && ( - - - - - - )} - {canDelete && ( - - )} -
- ) : null} -
-
-

{project.description}

-
-
-
- - {!isMember ? ( - - ) : ( -
- - Member -
- )} -
-
- - {renderShortNumericDateFormat(project.created_at)} -
-
-
- - ); -}; diff --git a/apps/app/components/project/index.ts b/apps/app/components/project/index.ts index c36630d8f..bf18fe07d 100644 --- a/apps/app/components/project/index.ts +++ b/apps/app/components/project/index.ts @@ -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"; diff --git a/apps/app/components/project/single-project-card.tsx b/apps/app/components/project/single-project-card.tsx new file mode 100644 index 000000000..4090d8fcd --- /dev/null +++ b/apps/app/components/project/single-project-card.tsx @@ -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 = ({ + 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 ? ( + + +
+
+ {!isMember ? ( + + ) : ( + Member + )} + + + +
+
+
+
+
{project.name}
+
+

{project.description}

+
+
+ + {renderShortNumericDateFormat(project.created_at)} +
+ {isMember ? ( +
+ {canEdit && ( + + + + + + )} + {canDelete && ( + + setDeleteProject(project.id)}> + Delete project + + + Copy project link + + + )} +
+ ) : null} +
+
+ + + ) : ( + + + + )} + + ); +}; diff --git a/apps/app/components/ui/custom-menu.tsx b/apps/app/components/ui/custom-menu.tsx index e34390eff..b46a87863 100644 --- a/apps/app/components/ui/custom-menu.tsx +++ b/apps/app/components/ui/custom-menu.tsx @@ -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) => (
- {ellipsis ? ( + {ellipsis || verticalEllipsis ? ( - + ) : ( {
) : ( -
-
- {projects.map((item) => ( - - ))} -
+
+ {projects.map((project) => ( + + ))}
)} diff --git a/apps/app/styles/globals.css b/apps/app/styles/globals.css index ccb9ea2f1..abc50d9dc 100644 --- a/apps/app/styles/globals.css +++ b/apps/app/styles/globals.css @@ -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 */ diff --git a/apps/app/types/projects.d.ts b/apps/app/types/projects.d.ts index 5abb32d2a..7364682a8 100644 --- a/apps/app/types/projects.d.ts +++ b/apps/app/types/projects.d.ts @@ -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; From 6afcf1f0e355f45a77de8d422af763bb2692cbc6 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal Date: Wed, 1 Mar 2023 14:11:27 +0530 Subject: [PATCH 2/3] style: projects list page --- .../components/project/join-project-modal.tsx | 12 +- .../project/single-project-card.tsx | 224 +++++++++++++----- apps/app/components/ui/tooltip.tsx | 16 +- apps/app/hooks/use-project-members.tsx | 18 +- apps/app/hooks/use-projects.tsx | 6 +- apps/app/next.config.js | 1 + .../pages/[workspaceSlug]/projects/index.tsx | 1 + apps/app/services/project.service.ts | 25 +- apps/app/types/projects.d.ts | 1 + 9 files changed, 214 insertions(+), 90 deletions(-) diff --git a/apps/app/components/project/join-project-modal.tsx b/apps/app/components/project/join-project-modal.tsx index df05f1309..60530e8a7 100644 --- a/apps/app/components/project/join-project-modal.tsx +++ b/apps/app/components/project/join-project-modal.tsx @@ -13,9 +13,7 @@ type TJoinProjectModalProps = { onJoin: () => Promise; }; -export const JoinProjectModal: React.FC = (props) => { - const { onClose, onJoin, data } = props; - +export const JoinProjectModal: React.FC = ({ onClose, onJoin, data }) => { const [isJoiningLoading, setIsJoiningLoading] = useState(false); const handleJoin = () => { @@ -36,7 +34,7 @@ export const JoinProjectModal: React.FC = (props) => { return ( - + = (props) => {
-
+
= (props) => {

-
- - ) : ( - Member - )} - - - + -
-
-
{project.name}
-
-

{project.description}

-
+ + +
+ + +
+

{project.name}

+ {project.icon && ( + + {String.fromCodePoint(parseInt(project.icon))} + + )} +
+

{truncateText(project.description ?? "", 100)}

+
+ +
+
{renderShortNumericDateFormat(project.created_at)}
- {isMember ? ( -
- {canEdit && ( - - - - - + + {hasJoined ? ( +
+ {(isOwner || isMember) && ( + + + + + + )} + + {isOwner && ( + setDeleteProject(project.id)}> + Delete project + )} - {canDelete && ( - - setDeleteProject(project.id)}> - Delete project - - - Copy project link - - + {project.is_favourite ? ( + + Remove from favourites + + ) : ( + + Add to favourites + )} -
- ) : null} -
+ + Copy project link + + +
+ ) : null}
- - +
+
) : ( diff --git a/apps/app/components/ui/tooltip.tsx b/apps/app/components/ui/tooltip.tsx index 1b7f40fea..f6a32a563 100644 --- a/apps/app/components/ui/tooltip.tsx +++ b/apps/app/components/ui/tooltip.tsx @@ -24,6 +24,7 @@ type Props = { children: JSX.Element; disabled?: boolean; className?: string; + theme?: "light" | "dark"; }; export const Tooltip: React.FC = ({ @@ -33,21 +34,18 @@ export const Tooltip: React.FC = ({ children, disabled = false, className = "", + theme = "light", }) => ( - {tooltipHeading ? ( - <> -
{tooltipHeading}
-

{tooltipContent}

- - ) : ( -

{tooltipContent}

- )} + {tooltipHeading &&
{tooltipHeading}
} +

{tooltipContent}

} position={position} diff --git a/apps/app/hooks/use-project-members.tsx b/apps/app/hooks/use-project-members.tsx index 5f3519610..9764ade75 100644 --- a/apps/app/hooks/use-project-members.tsx +++ b/apps/app/hooks/use-project-members.tsx @@ -13,20 +13,24 @@ const useProjectMembers = (workspaceSlug: string, projectId: string) => { projectService.projectMembers(workspaceSlug, projectId) ); - const isMember = members?.some((item: any) => item.member.id === (user as any)?.id); + const hasJoined = members?.some((item: any) => item.member.id === (user as any)?.id); - const canEdit = members?.some( - (item) => (item.member.id === (user as any)?.id && item.role === 20) || item.role === 15 + const isOwner = members?.some((item) => item.member.id === (user as any)?.id && item.role === 20); + const isMember = members?.some( + (item) => item.member.id === (user as any)?.id && item.role === 15 ); - const canDelete = members?.some( - (item) => item.member.id === (user as any)?.id && item.role === 20 + const isViewer = members?.some( + (item) => item.member.id === (user as any)?.id && item.role === 10 ); + const isGuest = members?.some((item) => item.member.id === (user as any)?.id && item.role === 5); return { members, + hasJoined, + isOwner, isMember, - canEdit, - canDelete, + isViewer, + isGuest, }; }; diff --git a/apps/app/hooks/use-projects.tsx b/apps/app/hooks/use-projects.tsx index f9583eea8..6b4a8b413 100644 --- a/apps/app/hooks/use-projects.tsx +++ b/apps/app/hooks/use-projects.tsx @@ -1,10 +1,8 @@ import useSWR from "swr"; import { useRouter } from "next/router"; -// types -import { IProject } from "types"; // services import projectService from "services/project.service"; -// constants +// fetch-keys import { PROJECTS_LIST } from "constants/fetch-keys"; const useProjects = () => { @@ -12,7 +10,7 @@ const useProjects = () => { const router = useRouter(); const { workspaceSlug } = router.query; // api fetching - const { data: projects, mutate: mutateProjects } = useSWR( + const { data: projects, mutate: mutateProjects } = useSWR( workspaceSlug ? PROJECTS_LIST(workspaceSlug as string) : null, workspaceSlug ? () => projectService.getProjects(workspaceSlug as string) : null ); diff --git a/apps/app/next.config.js b/apps/app/next.config.js index ed883e597..2b3583fa4 100644 --- a/apps/app/next.config.js +++ b/apps/app/next.config.js @@ -9,6 +9,7 @@ const nextConfig = { "vinci-web.s3.amazonaws.com", "planefs-staging.s3.ap-south-1.amazonaws.com", "planefs.s3.amazonaws.com", + "images.unsplash.com", ], }, output: "standalone", diff --git a/apps/app/pages/[workspaceSlug]/projects/index.tsx b/apps/app/pages/[workspaceSlug]/projects/index.tsx index adcce39a2..4569552ac 100644 --- a/apps/app/pages/[workspaceSlug]/projects/index.tsx +++ b/apps/app/pages/[workspaceSlug]/projects/index.tsx @@ -57,6 +57,7 @@ const ProjectsPage: NextPage = () => { onJoin={async () => { const project = projects?.find((item) => item.id === selectedProjectToJoin); if (!project) return; + await projectService .joinProject(workspaceSlug as string, { project_ids: [project.id], diff --git a/apps/app/services/project.service.ts b/apps/app/services/project.service.ts index 1172f8823..702298880 100644 --- a/apps/app/services/project.service.ts +++ b/apps/app/services/project.service.ts @@ -222,7 +222,7 @@ class ProjectServices extends APIService { } async syncGiuthubRepository( - slug: string, + workspaceSlug: string, projectId: string, workspaceIntegrationId: string, data: { @@ -233,7 +233,7 @@ class ProjectServices extends APIService { } ): Promise { return this.post( - `/api/workspaces/${slug}/projects/${projectId}/workspace-integrations/${workspaceIntegrationId}/github-repository-sync/`, + `/api/workspaces/${workspaceSlug}/projects/${projectId}/workspace-integrations/${workspaceIntegrationId}/github-repository-sync/`, data ) .then((response) => response?.data) @@ -255,6 +255,27 @@ class ProjectServices extends APIService { throw error?.response?.data; }); } + + async addProjectToFavourites( + workspaceSlug: string, + data: { + project: string; + } + ): Promise { + return this.post(`/api/workspaces/${workspaceSlug}/user-favourite-projects/`, data) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + + async removeProjectFromFavourites(workspaceSlug: string, projectId: string): Promise { + return this.delete(`/api/workspaces/${workspaceSlug}/user-favourite-projects/${projectId}/`) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } } export default new ProjectServices(); diff --git a/apps/app/types/projects.d.ts b/apps/app/types/projects.d.ts index 7364682a8..af5012f2c 100644 --- a/apps/app/types/projects.d.ts +++ b/apps/app/types/projects.d.ts @@ -10,6 +10,7 @@ export interface IProject { icon: string; id: string; identifier: string; + is_favourite: boolean; module_view: boolean; name: string; network: number; From 35af45ddd7142d16a6437226e32eb12f64e3a1c9 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal Date: Wed, 1 Mar 2023 14:18:02 +0530 Subject: [PATCH 3/3] fix: image aspect ratio --- apps/app/components/project/single-project-card.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/app/components/project/single-project-card.tsx b/apps/app/components/project/single-project-card.tsx index 676b572aa..b29c9e8a9 100644 --- a/apps/app/components/project/single-project-card.tsx +++ b/apps/app/components/project/single-project-card.tsx @@ -137,6 +137,7 @@ export const SingleProjectCard: React.FC = ({ } alt={project.name} layout="fill" + objectFit="cover" className="rounded-t-[10px]" />