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
@@ -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>
);
+237 -162
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>
);
}
+26 -19
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" && (
<>
+2 -2
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 && (