managed how collections are viewed by members

This commit is contained in:
Daniel 2023-06-22 18:05:02 +03:30
parent 51c5615fea
commit 3abea1d1b7
13 changed files with 432 additions and 288 deletions

View File

@ -7,6 +7,7 @@ import { useState } from "react";
import ProfilePhoto from "./ProfilePhoto";
import { faCalendarDays } from "@fortawesome/free-regular-svg-icons";
import useModalStore from "@/store/modals";
import usePermissions from "@/hooks/usePermissions";
type Props = {
collection: CollectionIncludingMembersAndLinkCount;
@ -27,6 +28,8 @@ export default function CollectionCard({ collection, className }: Props) {
const [expandDropdown, setExpandDropdown] = useState(false);
const permissions = usePermissions(collection.id as number);
return (
<div
className={`bg-gradient-to-tr from-sky-100 from-10% via-gray-100 via-20% to-white to-100% self-stretch min-h-[12rem] rounded-2xl shadow duration-100 hover:shadow-none group relative ${className}`}
@ -58,7 +61,7 @@ export default function CollectionCard({ collection, className }: Props) {
<ProfilePhoto
key={i}
src={`/api/avatar/${e.userId}`}
className="-mr-3"
className="-mr-3 border-[3px]"
/>
);
})
@ -72,7 +75,7 @@ export default function CollectionCard({ collection, className }: Props) {
<div className="text-right w-40">
<div className="text-sky-500 font-bold text-sm flex justify-end gap-1 items-center">
<FontAwesomeIcon icon={faLink} className="w-5 h-5 text-sky-600" />
{collection._count.links}
{collection._count && collection._count.links}
</div>
<div className="flex items-center justify-end gap-1 text-gray-600">
<FontAwesomeIcon icon={faCalendarDays} className="w-4 h-4" />
@ -91,6 +94,7 @@ export default function CollectionCard({ collection, className }: Props) {
modal: "COLLECTION",
state: true,
method: "UPDATE",
isOwner: permissions === true,
active: collection,
});
setExpandDropdown(false);
@ -103,6 +107,7 @@ export default function CollectionCard({ collection, className }: Props) {
modal: "COLLECTION",
state: true,
method: "UPDATE",
isOwner: permissions === true,
active: collection,
defaultIndex: 1,
});
@ -116,6 +121,7 @@ export default function CollectionCard({ collection, className }: Props) {
modal: "COLLECTION",
state: true,
method: "UPDATE",
isOwner: permissions === true,
active: collection,
defaultIndex: 2,
});

View File

@ -1,7 +1,6 @@
import {
CollectionIncludingMembersAndLinkCount,
LinkIncludingShortenedCollectionAndTags,
Member,
} from "@/types/global";
import { faFolder, faEllipsis } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
@ -61,7 +60,7 @@ export default function LinkCard({ link, count, className }: Props) {
return (
<div
className={`bg-gradient-to-tr from-slate-200 from-10% to-gray-50 via-20% shadow hover:shadow-none cursor-pointer duration-100 p-5 rounded-2xl relative group ${className}`}
className={`bg-gradient-to-tr from-slate-200 from-10% to-gray-50 via-20% shadow hover:shadow-none cursor-pointer duration-100 rounded-2xl relative group ${className}`}
>
{permissions && (
<div
@ -87,7 +86,7 @@ export default function LinkCard({ link, count, className }: Props) {
active: link,
});
}}
className="flex items-start gap-5 sm:gap-10 h-full w-full"
className="flex items-start gap-5 sm:gap-10 h-full w-full p-5"
>
<Image
src={`https://t2.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=${url.origin}&size=32`}

View File

@ -1,9 +1,13 @@
import React, { useState } from "react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faTrashCan } from "@fortawesome/free-solid-svg-icons";
import {
faRightFromBracket,
faTrashCan,
} from "@fortawesome/free-solid-svg-icons";
import { CollectionIncludingMembersAndLinkCount } from "@/types/global";
import useCollectionStore from "@/store/collections";
import { useRouter } from "next/router";
import usePermissions from "@/hooks/usePermissions";
type Props = {
toggleDeleteCollectionModal: Function;
@ -21,77 +25,92 @@ export default function DeleteCollection({
const router = useRouter();
const submit = async () => {
if (!collection.id || collection.name !== inputField) return null;
if (permissions === true) if (collection.name !== inputField) return null;
const response = await removeCollection(collection.id);
const response = await removeCollection(collection.id as number);
if (response) {
toggleDeleteCollectionModal();
router.push("/collections");
}
};
const permissions = usePermissions(collection.id as number);
return (
<div className="flex flex-col gap-3 justify-between sm:w-[35rem] w-80">
<p className="text-red-500 font-bold text-center">Warning!</p>
{permissions === true ? (
<>
<p className="text-red-500 font-bold text-center">Warning!</p>
<div className="max-h-[20rem] overflow-y-auto">
<div className="text-gray-500">
<p>
Please note that deleting the collection will permanently remove all
its contents, including the following:
</p>
<div className="p-3">
<li className="list-inside">
Links: All links within the collection will be permanently
deleted.
</li>
<li className="list-inside">
Tags: All tags associated with the collection will be removed.
</li>
<li className="list-inside">
Screenshots/PDFs: Any screenshots or PDFs attached to links within
this collection will be permanently deleted.
</li>
<li className="list-inside">
Members: Any members who have been granted access to the
collection will lose their permissions and no longer be able to
view or interact with the content.
</li>
<div className="max-h-[20rem] overflow-y-auto">
<div className="text-gray-500">
<p>
Please note that deleting the collection will permanently remove
all its contents, including the following:
</p>
<div className="p-3">
<li className="list-inside">
Links: All links within the collection will be permanently
deleted.
</li>
<li className="list-inside">
Tags: All tags associated with the collection will be removed.
</li>
<li className="list-inside">
Screenshots/PDFs: Any screenshots or PDFs attached to links
within this collection will be permanently deleted.
</li>
<li className="list-inside">
Members: Any members who have been granted access to the
collection will lose their permissions and no longer be able
to view or interact with the content.
</li>
</div>
<p>
Please double-check that you have backed up any essential data
and have informed the relevant members about this action.
</p>
</div>
</div>
<p>
Please double-check that you have backed up any essential data and
have informed the relevant members about this action.
</p>
</div>
</div>
<div className="flex flex-col gap-3">
<p className="text-sky-900 select-none text-center">
To confirm, type &quot;
<span className="font-bold text-sky-500">{collection.name}</span>
&quot; in the box below:
<div className="flex flex-col gap-3">
<p className="text-sky-900 select-none text-center">
To confirm, type &quot;
<span className="font-bold text-sky-500">{collection.name}</span>
&quot; in the box below:
</p>
<input
autoFocus
value={inputField}
onChange={(e) => setInputField(e.target.value)}
type="text"
placeholder={`Type "${collection.name}" Here.`}
className="w-72 sm:w-96 rounded-md p-3 mx-auto border-sky-100 border-solid border outline-none focus:border-sky-500 duration-100"
/>
</div>
</>
) : (
<p className="text-gray-500">
Click the button below to leave the current collection:
</p>
<input
autoFocus
value={inputField}
onChange={(e) => setInputField(e.target.value)}
type="text"
placeholder={`Type "${collection.name}" Here.`}
className="w-72 sm:w-96 rounded-md p-3 mx-auto border-sky-100 border-solid border outline-none focus:border-sky-500 duration-100"
/>
</div>
)}
<div
className={`mx-auto mt-2 text-white flex items-center gap-2 py-2 px-5 rounded-md select-none font-bold duration-100 ${
inputField === collection.name
? "bg-red-500 hover:bg-red-400 cursor-pointer"
: "cursor-not-allowed bg-red-300"
permissions === true
? inputField === collection.name
? "bg-red-500 hover:bg-red-400 cursor-pointer"
: "cursor-not-allowed bg-red-300"
: "bg-red-500 hover:bg-red-400 cursor-pointer"
}`}
onClick={submit}
>
<FontAwesomeIcon icon={faTrashCan} className="h-5" />
Delete Collection
<FontAwesomeIcon
icon={permissions === true ? faTrashCan : faRightFromBracket}
className="h-5"
/>
{permissions === true ? "Delete" : "Leave"} Collection
</div>
</div>
);

View File

@ -13,6 +13,7 @@ import addMemberToCollection from "@/lib/client/addMemberToCollection";
import Checkbox from "../../Checkbox";
import SubmitButton from "@/components/SubmitButton";
import ProfilePhoto from "@/components/ProfilePhoto";
import usePermissions from "@/hooks/usePermissions";
type Props = {
toggleCollectionModal: Function;
@ -29,6 +30,8 @@ export default function TeamManagement({
collection,
method,
}: Props) {
const permissions = usePermissions(collection.id as number);
const currentURL = new URL(document.URL);
const publicCollectionURL = `${currentURL.origin}/public/collections/${collection.id}`;
@ -80,19 +83,23 @@ export default function TeamManagement({
return (
<div className="flex flex-col gap-3 sm:w-[35rem] w-80">
<p className="text-sm text-sky-500">Make Public</p>
{permissions === true && (
<>
<p className="text-sm text-sky-500">Make Public</p>
<Checkbox
label="Make this a public collection."
state={collection.isPublic}
onClick={() =>
setCollection({ ...collection, isPublic: !collection.isPublic })
}
/>
<Checkbox
label="Make this a public collection."
state={collection.isPublic}
onClick={() =>
setCollection({ ...collection, isPublic: !collection.isPublic })
}
/>
<p className="text-gray-500 text-sm">
This will let <b>Anyone</b> to view this collection.
</p>
<p className="text-gray-500 text-sm">
This will let <b>Anyone</b> to view this collection.
</p>
</>
)}
{collection.isPublic ? (
<div>
@ -116,54 +123,58 @@ export default function TeamManagement({
</div>
) : null}
<hr />
{permissions !== true && collection.isPublic && <hr />}
<p className="text-sm text-sky-500">Member Management</p>
{permissions === true && (
<>
<p className="text-sm text-sky-500">Member Management</p>
<div className="flex items-center gap-2">
<input
value={member.user.email}
onChange={(e) => {
setMember({
...member,
user: { ...member.user, email: e.target.value },
});
}}
onKeyDown={(e) =>
e.key === "Enter" &&
addMemberToCollection(
session.data?.user.email as string,
member.user.email,
collection,
setMemberState
)
}
type="text"
placeholder="Email"
className="w-full rounded-md p-3 border-sky-100 border-solid border outline-none focus:border-sky-500 duration-100"
/>
<div className="flex items-center gap-2">
<input
value={member.user.email}
onChange={(e) => {
setMember({
...member,
user: { ...member.user, email: e.target.value },
});
}}
onKeyDown={(e) =>
e.key === "Enter" &&
addMemberToCollection(
session.data?.user.email as string,
member.user.email,
collection,
setMemberState
)
}
type="text"
placeholder="Email"
className="w-full rounded-md p-3 border-sky-100 border-solid border outline-none focus:border-sky-500 duration-100"
/>
<div
onClick={() =>
addMemberToCollection(
session.data?.user.email as string,
member.user.email,
collection,
setMemberState
)
}
className="flex items-center justify-center bg-sky-500 hover:bg-sky-400 duration-100 text-white w-12 h-12 p-3 rounded-md cursor-pointer"
>
<FontAwesomeIcon icon={faUserPlus} className="w-5 h-5" />
</div>
</div>
<div
onClick={() =>
addMemberToCollection(
session.data?.user.email as string,
member.user.email,
collection,
setMemberState
)
}
className="flex items-center justify-center bg-sky-500 hover:bg-sky-400 duration-100 text-white w-12 h-12 p-3 rounded-md cursor-pointer"
>
<FontAwesomeIcon icon={faUserPlus} className="w-5 h-5" />
</div>
</div>
</>
)}
{collection?.members[0]?.user && (
<>
<p className="text-center text-gray-500 text-xs sm:text-sm">
(All Members have <b>Read</b> access to this collection.)
</p>
<div className="max-h-[20rem] overflow-auto flex flex-col gap-3 rounded-md shadow-inner">
<div className="max-h-[20rem] overflow-auto flex flex-col gap-3 rounded-md">
{collection.members
.sort((a, b) => (a.userId as number) - (b.userId as number))
.map((e, i) => {
@ -172,24 +183,29 @@ export default function TeamManagement({
key={i}
className="relative border p-2 rounded-md border-sky-100 flex flex-col sm:flex-row sm:items-center gap-2 justify-between"
>
<FontAwesomeIcon
icon={faClose}
className="absolute right-2 top-2 text-gray-500 h-4 hover:text-red-500 duration-100 cursor-pointer"
title="Remove Member"
onClick={() => {
const updatedMembers = collection.members.filter(
(member) => {
return member.user.email !== e.user.email;
}
);
setCollection({
...collection,
members: updatedMembers,
});
}}
/>
{permissions === true && (
<FontAwesomeIcon
icon={faClose}
className="absolute right-2 top-2 text-gray-500 h-4 hover:text-red-500 duration-100 cursor-pointer"
title="Remove Member"
onClick={() => {
const updatedMembers = collection.members.filter(
(member) => {
return member.user.email !== e.user.email;
}
);
setCollection({
...collection,
members: updatedMembers,
});
}}
/>
)}
<div className="flex items-center gap-2">
<ProfilePhoto src={`/api/avatar/${e.userId}`} />
<ProfilePhoto
src={`/api/avatar/${e.userId}`}
className="border-[3px]"
/>
<div>
<p className="text-sm font-bold text-sky-500">
{e.user.name}
@ -197,104 +213,161 @@ export default function TeamManagement({
<p className="text-sky-900">{e.user.email}</p>
</div>
</div>
<div className="flex sm:block items-center gap-5">
<div className="flex sm:block items-center gap-5 min-w-[10rem]">
<div>
<p className="font-bold text-sm text-sky-500">
<p
className={`font-bold text-sm text-sky-500 ${
permissions === true ? "" : "mb-2"
}`}
>
Permissions
</p>
<p className="text-xs text-gray-500 mb-2">
(Click to toggle.)
{permissions === true && (
<p className="text-xs text-gray-500 mb-2">
(Click to toggle.)
</p>
)}
</div>
{permissions !== true &&
!e.canCreate &&
!e.canUpdate &&
!e.canDelete ? (
<p className="text-sm text-gray-500">
Has no permissions.
</p>
</div>
<div>
<label className="cursor-pointer mr-1">
<input
type="checkbox"
id="canCreate"
className="peer sr-only"
checked={e.canCreate}
onChange={() => {
const updatedMembers = collection.members.map(
(member) => {
if (member.user.email === e.user.email) {
return {
...member,
canCreate: !e.canCreate,
};
}
return member;
) : (
<div>
<label
className={
permissions === true
? "cursor-pointer mr-1"
: "mr-1"
}
>
<input
type="checkbox"
id="canCreate"
className="peer sr-only"
checked={e.canCreate}
onChange={() => {
if (permissions === true) {
const updatedMembers = collection.members.map(
(member) => {
if (member.user.email === e.user.email) {
return {
...member,
canCreate: !e.canCreate,
};
}
return member;
}
);
setCollection({
...collection,
members: updatedMembers,
});
}
);
setCollection({
...collection,
members: updatedMembers,
});
}}
/>
<span className="text-sky-900 peer-checked:bg-sky-500 text-sm hover:bg-slate-200 duration-75 peer-checked:text-white rounded p-1 select-none">
Create
</span>
</label>
}}
/>
<span
className={`text-sky-900 peer-checked:bg-sky-500 text-sm ${
permissions === true
? "hover:bg-slate-200 duration-75"
: ""
} peer-checked:text-white rounded p-1 select-none`}
>
Create
</span>
</label>
<label className="cursor-pointer mr-1">
<input
type="checkbox"
id="canUpdate"
className="peer sr-only"
checked={e.canUpdate}
onChange={() => {
const updatedMembers = collection.members.map(
(member) => {
if (member.user.email === e.user.email) {
return {
...member,
canUpdate: !e.canUpdate,
};
}
return member;
<label
className={
permissions === true
? "cursor-pointer mr-1"
: "mr-1"
}
>
<input
type="checkbox"
id="canUpdate"
className="peer sr-only"
checked={e.canUpdate}
onChange={() => {
if (permissions === true) {
const updatedMembers = collection.members.map(
(member) => {
if (member.user.email === e.user.email) {
return {
...member,
canUpdate: !e.canUpdate,
};
}
return member;
}
);
setCollection({
...collection,
members: updatedMembers,
});
}
);
setCollection({
...collection,
members: updatedMembers,
});
}}
/>
<span className="text-sky-900 peer-checked:bg-sky-500 text-sm hover:bg-slate-200 duration-75 peer-checked:text-white rounded p-1 select-none">
Update
</span>
</label>
}}
/>
<span
className={`text-sky-900 peer-checked:bg-sky-500 text-sm ${
permissions === true
? "hover:bg-slate-200 duration-75"
: ""
} peer-checked:text-white rounded p-1 select-none`}
>
Update
</span>
</label>
<label className="cursor-pointer mr-1">
<input
type="checkbox"
id="canDelete"
className="peer sr-only"
checked={e.canDelete}
onChange={() => {
const updatedMembers = collection.members.map(
(member) => {
if (member.user.email === e.user.email) {
return {
...member,
canDelete: !e.canDelete,
};
}
return member;
<label
className={
permissions === true
? "cursor-pointer mr-1"
: "mr-1"
}
>
<input
type="checkbox"
id="canDelete"
className="peer sr-only"
checked={e.canDelete}
onChange={() => {
if (permissions === true) {
const updatedMembers = collection.members.map(
(member) => {
if (member.user.email === e.user.email) {
return {
...member,
canDelete: !e.canDelete,
};
}
return member;
}
);
setCollection({
...collection,
members: updatedMembers,
});
}
);
setCollection({
...collection,
members: updatedMembers,
});
}}
/>
<span className="text-sky-900 peer-checked:bg-sky-500 text-sm hover:bg-slate-200 duration-75 peer-checked:text-white rounded p-1 select-none">
Delete
</span>
</label>
</div>
}}
/>
<span
className={`text-sky-900 peer-checked:bg-sky-500 text-sm ${
permissions === true
? "hover:bg-slate-200 duration-75"
: ""
} peer-checked:text-white rounded p-1 select-none`}
>
Delete
</span>
</label>
</div>
)}
</div>
</div>
);
@ -303,12 +376,14 @@ export default function TeamManagement({
</>
)}
<SubmitButton
onClick={submit}
label={method === "CREATE" ? "Add" : "Save"}
icon={method === "CREATE" ? faPlus : faPenToSquare}
className="mx-auto mt-2"
/>
{permissions === true && (
<SubmitButton
onClick={submit}
label={method === "CREATE" ? "Add" : "Save"}
icon={method === "CREATE" ? faPlus : faPenToSquare}
className="mx-auto mt-2"
/>
)}
</div>
);
}

View File

@ -10,6 +10,7 @@ type Props =
toggleCollectionModal: Function;
activeCollection: CollectionIncludingMembersAndLinkCount;
method: "UPDATE";
isOwner: boolean;
className?: string;
defaultIndex?: number;
}
@ -17,6 +18,7 @@ type Props =
toggleCollectionModal: Function;
activeCollection?: CollectionIncludingMembersAndLinkCount;
method: "CREATE";
isOwner: boolean;
className?: string;
defaultIndex?: number;
};
@ -25,6 +27,7 @@ export default function CollectionModal({
className,
defaultIndex,
toggleCollectionModal,
isOwner,
activeCollection,
method,
}: Props) {
@ -48,6 +51,17 @@ export default function CollectionModal({
<Tab.List className="flex justify-center flex-col max-w-[15rem] sm:max-w-[30rem] mx-auto sm:flex-row gap-2 sm:gap-3 mb-5 text-sky-600">
{method === "UPDATE" && (
<>
{isOwner && (
<Tab
className={({ selected }) =>
selected
? "px-2 py-1 bg-sky-200 duration-100 rounded-md outline-none"
: "px-2 py-1 hover:bg-slate-200 rounded-md duration-100 outline-none"
}
>
Collection Info
</Tab>
)}
<Tab
className={({ selected }) =>
selected
@ -55,7 +69,7 @@ export default function CollectionModal({
: "px-2 py-1 hover:bg-slate-200 rounded-md duration-100 outline-none"
}
>
Collection Info
{isOwner ? "Share & Collaborate" : "View Team"}
</Tab>
<Tab
className={({ selected }) =>
@ -64,29 +78,22 @@ export default function CollectionModal({
: "px-2 py-1 hover:bg-slate-200 rounded-md duration-100 outline-none"
}
>
Share & Collaborate
</Tab>
<Tab
className={({ selected }) =>
selected
? "px-2 py-1 bg-sky-200 duration-100 rounded-md outline-none"
: "px-2 py-1 hover:bg-slate-200 rounded-md duration-100 outline-none"
}
>
Delete Collection
{isOwner ? "Delete Collection" : "Leave Collection"}
</Tab>
</>
)}
</Tab.List>
<Tab.Panels>
<Tab.Panel>
<CollectionInfo
toggleCollectionModal={toggleCollectionModal}
setCollection={setCollection}
collection={collection}
method={method}
/>
</Tab.Panel>
{(isOwner || method === "CREATE") && (
<Tab.Panel>
<CollectionInfo
toggleCollectionModal={toggleCollectionModal}
setCollection={setCollection}
collection={collection}
method={method}
/>
</Tab.Panel>
)}
{method === "UPDATE" && (
<>

View File

@ -77,10 +77,10 @@ export default function ProfileSettings({
<div className="grid sm:grid-cols-2 gap-3 auto-rows-auto">
<div className="sm:row-span-2 sm:justify-self-center mx-auto mb-3">
<p className="text-sm text-sky-500 mb-2 text-center">Profile Photo</p>
<div className="w-28 h-28 flex items-center justify-center border border-sky-100 rounded-full relative">
<div className="w-28 h-28 flex items-center justify-center rounded-full relative">
<ProfilePhoto
src={user.profilePic}
className="h-auto aspect-square w-28 border-[1px]"
className="h-auto w-28"
status={handleProfileStatus}
/>
{profileStatus && (

View File

@ -40,6 +40,7 @@ export default function ModalManagement() {
<CollectionModal
toggleCollectionModal={toggleModal}
method={modal.method}
isOwner={modal.isOwner}
defaultIndex={modal.defaultIndex}
activeCollection={
modal.active as CollectionIncludingMembersAndLinkCount

View File

@ -69,7 +69,7 @@ export default function Navbar() {
>
<ProfilePhoto
src={account.profilePic}
className="sm:group-hover:h-8 sm:group-hover:w-8 duration-100"
className="sm:group-hover:h-8 sm:group-hover:w-8 duration-100 border-[3px]"
/>
<p
id="profile-dropdown"

View File

@ -33,7 +33,7 @@ export default function ProfilePhoto({
return error || !src ? (
<div
className={`bg-sky-500 text-white h-10 w-10 aspect-square shadow rounded-full border-[3px] border-slate-200 flex items-center justify-center ${className}`}
className={`bg-sky-500 text-white h-10 w-10 aspect-square shadow rounded-full border border-slate-200 flex items-center justify-center ${className}`}
>
<FontAwesomeIcon icon={faUser} className="w-1/2 h-1/2 aspect-square" />
</div>
@ -43,7 +43,7 @@ export default function ProfilePhoto({
src={src}
height={112}
width={112}
className={`h-10 w-10 shadow rounded-full aspect-square border-[3px] border-slate-200 ${className}`}
className={`h-10 w-10 shadow rounded-full aspect-square border border-slate-200 ${className}`}
/>
);
}

View File

@ -1,7 +1,7 @@
import useAccountStore from "@/store/account";
import useCollectionStore from "@/store/collections";
import { CollectionIncludingMembersAndLinkCount, Member } from "@/types/global";
import React, { useEffect, useState } from "react";
import { Member } from "@/types/global";
import { useEffect, useState } from "react";
export default function usePermissions(collectionId: number) {
const { collections } = useCollectionStore();
@ -26,7 +26,7 @@ export default function usePermissions(collectionId: number) {
setPermissions(account.id === collection.ownerId || getPermission);
}
}, [collections]);
}, [account, collections, collectionId]);
return permissions;
}

View File

@ -1,24 +1,45 @@
import { prisma } from "@/lib/api/db";
import getPermission from "@/lib/api/getPermission";
import { UsersAndCollections } from "@prisma/client";
import fs from "fs";
export default async function deleteCollection(
collection: { id: number },
userId: number
) {
if (!collection.id)
const collectionId = collection.id;
if (!collectionId)
return { response: "Please choose a valid collection.", status: 401 };
const collectionIsAccessible = await getPermission(userId, collection.id);
const collectionIsAccessible = await getPermission(userId, collectionId);
if (!(collectionIsAccessible?.ownerId === userId))
const memberHasAccess = collectionIsAccessible?.members.some(
(e: UsersAndCollections) => e.userId === userId
);
if (collectionIsAccessible?.ownerId !== userId && memberHasAccess) {
// Remove relation/Leave collection
const deletedUsersAndCollectionsRelation =
await prisma.usersAndCollections.delete({
where: {
userId_collectionId: {
userId: userId,
collectionId: collectionId,
},
},
});
return { response: deletedUsersAndCollectionsRelation, status: 200 };
} else if (collectionIsAccessible?.ownerId !== userId) {
return { response: "Collection is not accessible.", status: 401 };
}
const deletedCollection = await prisma.$transaction(async () => {
await prisma.usersAndCollections.deleteMany({
where: {
collection: {
id: collection.id,
id: collectionId,
},
},
});
@ -26,13 +47,13 @@ export default async function deleteCollection(
await prisma.link.deleteMany({
where: {
collection: {
id: collection.id,
id: collectionId,
},
},
});
try {
fs.rmdirSync(`data/archives/${collection.id}`, { recursive: true });
fs.rmdirSync(`data/archives/${collectionId}`, { recursive: true });
} catch (error) {
console.log(
"Collection's archive directory wasn't deleted most likely because it didn't exist..."
@ -41,7 +62,7 @@ export default async function deleteCollection(
return await prisma.collection.delete({
where: {
id: collection.id,
id: collectionId,
},
});
});

View File

@ -17,6 +17,7 @@ import ProfilePhoto from "@/components/ProfilePhoto";
import SortDropdown from "@/components/SortDropdown";
import useModalStore from "@/store/modals";
import useLinks from "@/hooks/useLinks";
import usePermissions from "@/hooks/usePermissions";
export default function Index() {
const { setModal } = useModalStore();
@ -35,6 +36,8 @@ export default function Index() {
const [activeCollection, setActiveCollection] =
useState<CollectionIncludingMembersAndLinkCount>();
const permissions = usePermissions(activeCollection?.id as number);
useLinks({ collectionId: Number(router.query.id), sort: sortBy });
useEffect(() => {
@ -71,13 +74,13 @@ export default function Index() {
>
<div
onClick={() =>
activeCollection &&
setModal({
modal: "COLLECTION",
state: true,
method: "UPDATE",
isOwner: permissions === true,
active: activeCollection,
defaultIndex: 1,
defaultIndex: permissions === true ? 1 : 0,
})
}
className="flex justify-center sm:justify-end items-center w-fit mx-auto sm:mr-0 sm:ml-auto group cursor-pointer"
@ -87,10 +90,7 @@ export default function Index() {
activeCollection.members[0] && "mr-1"
}`}
>
{activeCollection.ownerId === data?.user.id
? "Manage"
: "View"}{" "}
Team
{permissions === true ? "Manage" : "View"} Team
</div>
{activeCollection?.members
.sort((a, b) => (a.userId as number) - (b.userId as number))
@ -99,7 +99,7 @@ export default function Index() {
<ProfilePhoto
key={i}
src={`/api/avatar/${e.userId}`}
className="-mr-3 duration-100"
className="-mr-3 duration-100 border-[3px]"
/>
);
})
@ -155,54 +155,68 @@ export default function Index() {
{expandDropdown ? (
<Dropdown
items={[
permissions === true || permissions?.canCreate
? {
name: "Add Link Here",
onClick: () => {
setModal({
modal: "LINK",
state: true,
method: "CREATE",
});
setExpandDropdown(false);
},
}
: undefined,
permissions === true
? {
name: "Edit Collection Info",
onClick: () => {
activeCollection &&
setModal({
modal: "COLLECTION",
state: true,
method: "UPDATE",
isOwner: permissions === true,
active: activeCollection,
});
setExpandDropdown(false);
},
}
: undefined,
{
name: "Add Link Here",
onClick: () => {
setModal({
modal: "LINK",
state: true,
method: "CREATE",
});
setExpandDropdown(false);
},
},
{
name: "Edit Collection Info",
onClick: () => {
activeCollection &&
setModal({
modal: "COLLECTION",
state: true,
method: "UPDATE",
active: activeCollection,
});
setExpandDropdown(false);
},
},
{
name: "Share/Collaborate",
name:
permissions === true
? "Share/Collaborate"
: "View Team",
onClick: () => {
activeCollection &&
setModal({
modal: "COLLECTION",
state: true,
method: "UPDATE",
isOwner: permissions === true,
active: activeCollection,
defaultIndex: 1,
});
setExpandDropdown(false);
},
},
{
name: "Delete Collection",
name:
permissions === true
? "Delete Collection"
: "Leave Collection",
onClick: () => {
activeCollection &&
setModal({
modal: "COLLECTION",
state: true,
method: "UPDATE",
isOwner: permissions === true,
active: activeCollection,
defaultIndex: 2,
defaultIndex: permissions === true ? 2 : 1,
});
setExpandDropdown(false);
},

View File

@ -30,6 +30,7 @@ type Modal =
modal: "COLLECTION";
state: boolean;
method: "UPDATE";
isOwner: boolean;
active: CollectionIncludingMembersAndLinkCount;
defaultIndex?: number;
}
@ -37,6 +38,7 @@ type Modal =
modal: "COLLECTION";
state: boolean;
method: "CREATE";
isOwner: boolean;
active?: CollectionIncludingMembersAndLinkCount;
defaultIndex?: number;
}