Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 8 additions & 17 deletions web/components/auth-screens/project/join-project.tsx
Original file line number Diff line number Diff line change
@@ -1,40 +1,31 @@
import { useState } from "react";
import Image from "next/image";
import { useRouter } from "next/router";
import { mutate } from "swr";
// services
import { ProjectService } from "services/project";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// ui
import { Button } from "@plane/ui";
// icons
import { ClipboardList } from "lucide-react";
// images
import JoinProjectImg from "public/auth/project-not-authorized.svg";
// fetch-keys
import { USER_PROJECT_VIEW } from "constants/fetch-keys";

const projectService = new ProjectService();

export const JoinProject: React.FC = () => {
const [isJoiningProject, setIsJoiningProject] = useState(false);

const { project: projectStore } = useMobxStore();

const router = useRouter();
const { workspaceSlug, projectId } = router.query;

const handleJoin = () => {
if (!workspaceSlug || !projectId) return;

setIsJoiningProject(true);
projectService
.joinProject(workspaceSlug as string, [projectId as string])
.then(async () => {
await mutate(USER_PROJECT_VIEW(projectId.toString()));
setIsJoiningProject(false);
})
.catch((err) => {
console.error(err);
setIsJoiningProject(false);
});

projectStore.joinProject(workspaceSlug.toString(), [projectId.toString()]).finally(() => {
setIsJoiningProject(false);
});
};

return (
Expand Down
247 changes: 123 additions & 124 deletions web/components/project/card.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React, { useState } from "react";
import { useRouter } from "next/router";
import Link from "next/link";
import { observer } from "mobx-react-lite";
import { useMobxStore } from "lib/mobx/store-provider";
import { RootStore } from "store/root";
Expand Down Expand Up @@ -95,136 +94,136 @@ export const ProjectCard: React.FC<ProjectCardProps> = observer((props) => {
)}

{/* Card Information */}
<div className="flex flex-col rounded bg-custom-background-100 border border-custom-border-200">
<Link href={`/${workspaceSlug as string}/projects/${project.id}/issues`}>
<a>
<div className="relative h-[118px] w-full rounded-t ">
<div className="absolute z-[1] inset-0 bg-gradient-to-t from-black/60 to-transparent" />

<img
src={
project.cover_image ??
"https://images.unsplash.com/photo-1672243775941-10d763d9adef?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1170&q=80"
}
alt={project.name}
className="absolute top-0 left-0 h-full w-full object-cover rounded-t"
/>

<div className="absolute h-9 w-full bottom-4 z-10 flex items-center justify-between px-4">
<div className="flex items-center gap-2.5">
<div className="h-9 w-9 flex item-center justify-center rounded bg-white/90 flex-shrink-0">
<span className="flex items-center justify-center">
{project.emoji
? renderEmoji(project.emoji)
: project.icon_prop
? renderEmoji(project.icon_prop)
: null}
</span>
</div>

<div className="flex flex-col gap-0.5 justify-center h-9">
<h3 className="text-white font-semibold line-clamp-1">{project.name}</h3>
<span className="flex items-center gap-1.5">
<p className="text-xs font-medium text-white">{project.identifier} </p>
{project.network === 0 && <Lock className="h-2.5 w-2.5 text-white " />}
</span>
</div>
</div>
<div
onClick={() => {
if (project.is_member) router.push(`/${workspaceSlug?.toString()}/projects/${project.id}/issues`);
else setJoinProjectModal(true);
}}
className="flex flex-col rounded bg-custom-background-100 border border-custom-border-200 cursor-pointer"
>
<div className="relative h-[118px] w-full rounded-t ">
<div className="absolute z-[1] inset-0 bg-gradient-to-t from-black/60 to-transparent" />

<img
src={
project.cover_image ??
"https://images.unsplash.com/photo-1672243775941-10d763d9adef?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1170&q=80"
}
alt={project.name}
className="absolute top-0 left-0 h-full w-full object-cover rounded-t"
/>

<div className="absolute h-9 w-full bottom-4 z-10 flex items-center justify-between px-4">
<div className="flex items-center gap-2.5">
<div className="h-9 w-9 flex item-center justify-center rounded bg-white/90 flex-shrink-0">
<span className="flex items-center justify-center">
{project.emoji
? renderEmoji(project.emoji)
: project.icon_prop
? renderEmoji(project.icon_prop)
: null}
</span>
</div>

<div className="flex items-center h-full gap-2">
<button
className="flex items-center justify-center h-6 w-6 rounded bg-white/10"
onClick={(e) => {
e.stopPropagation();
e.preventDefault();
handleCopyText();
}}
>
<LinkIcon className="h-3 w-3 text-white" />
</button>
<button
className="flex items-center justify-center h-6 w-6 rounded bg-white/10"
onClick={(e) => {
if (project.is_favorite) {
e.preventDefault();
e.stopPropagation();
handleRemoveFromFavorites();
} else {
e.preventDefault();
e.stopPropagation();
handleAddToFavorites();
}
}}
>
<Star
className={`h-3 w-3 ${project.is_favorite ? "fill-amber-400 text-transparent" : "text-white"} `}
/>
</button>
</div>
<div className="flex flex-col gap-0.5 justify-center h-9">
<h3 className="text-white font-semibold line-clamp-1">{project.name}</h3>
<span className="flex items-center gap-1.5">
<p className="text-xs font-medium text-white">{project.identifier} </p>
{project.network === 0 && <Lock className="h-2.5 w-2.5 text-white " />}
</span>
</div>
</div>

<div className="h-[104px] w-full flex flex-col justify-between p-4 rounded-b">
<p className="text-sm text-custom-text-300 font-medium break-words line-clamp-2">{project.description}</p>
<div className="flex item-center justify-between">
<Tooltip
tooltipHeading="Members"
tooltipContent={
project.members && project.members.length > 0 ? `${project.members.length} Members` : "No Member"
<div className="flex items-center h-full gap-2">
<button
className="flex items-center justify-center h-6 w-6 rounded bg-white/10"
onClick={(e) => {
e.stopPropagation();
e.preventDefault();
handleCopyText();
}}
>
<LinkIcon className="h-3 w-3 text-white" />
</button>
<button
className="flex items-center justify-center h-6 w-6 rounded bg-white/10"
onClick={(e) => {
if (project.is_favorite) {
e.preventDefault();
e.stopPropagation();
handleRemoveFromFavorites();
} else {
e.preventDefault();
e.stopPropagation();
handleAddToFavorites();
}
position="top"
}}
>
<Star
className={`h-3 w-3 ${project.is_favorite ? "fill-amber-400 text-transparent" : "text-white"} `}
/>
</button>
</div>
</div>
</div>

<div className="h-[104px] w-full flex flex-col justify-between p-4 rounded-b">
<p className="text-sm text-custom-text-300 font-medium break-words line-clamp-2">{project.description}</p>
<div className="flex item-center justify-between">
<Tooltip
tooltipHeading="Members"
tooltipContent={
project.members && project.members.length > 0 ? `${project.members.length} Members` : "No Member"
}
position="top"
>
{projectMembersIds.length > 0 ? (
<div className="flex items-center cursor-pointer gap-2 text-custom-text-200">
<AvatarGroup showTooltip={false}>
{projectMembersIds.map((memberId) => {
const member = project.members?.find((m) => m.id === memberId);

if (!member) return null;

return <Avatar key={member.id} name={member.member__display_name} src={member.member__avatar} />;
})}
</AvatarGroup>
</div>
) : (
<span className="text-sm italic text-custom-text-400">No Member Yet</span>
)}
</Tooltip>
{(isOwner || isMember) && (
<button
className="flex items-center justify-center p-1 text-custom-text-400 hover:bg-custom-background-80 hover:text-custom-text-200 rounded"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();

router.push(`/${workspaceSlug}/projects/${project.id}/settings`);
}}
>
<Pencil className="h-3.5 w-3.5" />
</button>
)}

{!project.is_member ? (
<div className="flex items-center">
<Button
variant="link-primary"
className="!p-0"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
setJoinProjectModal(true);
}}
>
{projectMembersIds.length > 0 ? (
<div className="flex items-center cursor-pointer gap-2 text-custom-text-200">
<AvatarGroup showTooltip={false}>
{projectMembersIds.map((memberId) => {
const member = project.members?.find((m) => m.id === memberId);

if (!member) return null;

return (
<Avatar key={member.id} name={member.member__display_name} src={member.member__avatar} />
);
})}
</AvatarGroup>
</div>
) : (
<span className="text-sm italic text-custom-text-400">No Member Yet</span>
)}
</Tooltip>
{(isOwner || isMember) && (
<button
className="flex items-center justify-center p-1 text-custom-text-400 hover:bg-custom-background-80 hover:text-custom-text-200 rounded"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();

router.push(`/${workspaceSlug}/projects/${project.id}/settings`);
}}
>
<Pencil className="h-3.5 w-3.5" />
</button>
)}

{!project.is_member ? (
<div className="flex items-center">
<Button
variant="link-primary"
className="!p-0"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
setJoinProjectModal(true);
}}
>
Join
</Button>
</div>
) : null}
Join
</Button>
</div>
</div>
</a>
</Link>
) : null}
</div>
</div>
</div>
</>
);
Expand Down
48 changes: 25 additions & 23 deletions web/layouts/auth-layout/project-wrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -104,41 +104,43 @@ export const ProjectAuthWrapper: FC<IProjectAuthWrapper> = observer((props) => {
}
);

const projectsList = workspaceSlug ? projectStore.projects[workspaceSlug.toString()] : null;
const projectExists = projectId ? projectsList?.find((project) => project.id === projectId.toString()) : null;

// check if the project member apis is loading
if (!userStore.projectMemberInfo && userStore.hasPermissionToProject === null) {
if (!userStore.projectMemberInfo && projectId && userStore.hasPermissionToProject[projectId.toString()] === null)
return (
<div className="grid h-screen place-items-center p-4 bg-custom-background-100">
<div className="flex flex-col items-center gap-3 text-center">
<Spinner />
</div>
</div>
);
}

// check if the user don't have permission to access the project
if (userStore.hasPermissionToProject === false && !userStore.projectNotFound) {
<JoinProject />;
}
if (projectExists && projectId && userStore.hasPermissionToProject[projectId.toString()] === false)
return <JoinProject />;

// check if the project info is not found.
if (userStore.hasPermissionToProject === false && userStore.projectNotFound) {
<div className="container grid h-screen place-items-center bg-custom-background-100">
<EmptyState
title="No such project exists"
description="Try creating a new project"
image={emptyProject}
primaryButton={{
text: "Create Project",
onClick: () => {
const e = new KeyboardEvent("keydown", {
key: "p",
});
document.dispatchEvent(e);
},
}}
/>
</div>;
}
if (!projectExists && projectId && userStore.hasPermissionToProject[projectId.toString()] === false)
return (
<div className="container grid h-screen place-items-center bg-custom-background-100">
<EmptyState
title="No such project exists"
description="Try creating a new project"
image={emptyProject}
primaryButton={{
text: "Create Project",
onClick: () => {
const e = new KeyboardEvent("keydown", {
key: "p",
});
document.dispatchEvent(e);
},
}}
/>
</div>
);

return <>{children}</>;
});
Loading