diff --git a/components/CollectionCard.tsx b/components/CollectionCard.tsx index 38a554e..ffde8ee 100644 --- a/components/CollectionCard.tsx +++ b/components/CollectionCard.tsx @@ -11,7 +11,7 @@ import { faEllipsis, } from "@fortawesome/free-solid-svg-icons"; import Link from "next/link"; -import { ExtendedCollection } from "@/types/global"; +import { CollectionIncludingMembers } from "@/types/global"; import useLinkStore from "@/store/links"; import ImageWithFallback from "./ImageWithFallback"; import Dropdown from "./Dropdown"; @@ -20,9 +20,15 @@ import Modal from "@/components/Modal"; import EditCollection from "@/components/Modal/EditCollection"; import DeleteCollection from "@/components/Modal/DeleteCollection"; -export default function ({ collection }: { collection: ExtendedCollection }) { +export default function ({ + collection, +}: { + collection: CollectionIncludingMembers; +}) { const { links } = useLinkStore(); - const formattedDate = new Date(collection.createdAt).toLocaleString("en-US", { + const formattedDate = new Date( + collection.createdAt as unknown as string + ).toLocaleString("en-US", { year: "numeric", month: "short", day: "numeric", @@ -61,7 +67,7 @@ export default function ({ collection }: { collection: ExtendedCollection }) {
{collection.members - .sort((a, b) => a.userId - b.userId) + .sort((a, b) => (a.user.id as number) - (b.user.id as number)) .map((e, i) => { return ( ) : null} diff --git a/components/Dashboard/CollectionItem.tsx b/components/Dashboard/CollectionItem.tsx index 5afd8a5..6053f69 100644 --- a/components/Dashboard/CollectionItem.tsx +++ b/components/Dashboard/CollectionItem.tsx @@ -6,16 +6,15 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faChevronRight } from "@fortawesome/free-solid-svg-icons"; import Link from "next/link"; -import { ExtendedCollection } from "@/types/global"; +import { CollectionIncludingMembers } from "@/types/global"; import useLinkStore from "@/store/links"; -export default function ({ collection }: { collection: ExtendedCollection }) { +export default function ({ + collection, +}: { + collection: CollectionIncludingMembers; +}) { const { links } = useLinkStore(); - const formattedDate = new Date(collection.createdAt).toLocaleString("en-US", { - year: "numeric", - month: "short", - day: "numeric", - }); return (
-

{formattedDate}

{links.filter((e) => e.collectionId === collection.id).length} Links

diff --git a/components/Modal/AddCollection.tsx b/components/Modal/AddCollection.tsx index e949c28..00157c5 100644 --- a/components/Modal/AddCollection.tsx +++ b/components/Modal/AddCollection.tsx @@ -3,7 +3,7 @@ // This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. // You should have received a copy of the GNU General Public License along with this program. If not, see . -import React, { useState } from "react"; +import React, { useEffect, useState } from "react"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faClose, @@ -12,7 +12,7 @@ import { faUserPlus, } from "@fortawesome/free-solid-svg-icons"; import useCollectionStore from "@/store/collections"; -import { ExtendedCollection, NewCollection } from "@/types/global"; +import { CollectionIncludingMembers, Member } from "@/types/global"; import { useSession } from "next-auth/react"; import RequiredBadge from "../RequiredBadge"; import addMemberToCollection from "@/lib/client/addMemberToCollection"; @@ -23,33 +23,52 @@ type Props = { }; export default function AddCollection({ toggleCollectionModal }: Props) { - const [newCollection, setNewCollection] = useState({ + const session = useSession(); + + const [collection, setCollection] = useState({ name: "", description: "", + ownerId: session.data?.user.id as number, members: [], }); - const [memberEmail, setMemberEmail] = useState(""); + const [member, setMember] = useState({ + canCreate: false, + canUpdate: false, + canDelete: false, + user: { + name: "", + email: "", + }, + }); const { addCollection } = useCollectionStore(); - const session = useSession(); - const submit = async () => { - console.log(newCollection); + if (!collection) return null; - const response = await addCollection(newCollection as NewCollection); + const response = await addCollection(collection); if (response) toggleCollectionModal(); }; - const setMemberState = (newMember: any) => { - setNewCollection({ - ...newCollection, - members: [...newCollection.members, newMember], + const setMemberState = (newMember: Member) => { + if (!collection) return null; + + setCollection({ + ...collection, + members: [...collection.members, newMember], }); - setMemberEmail(""); + setMember({ + canCreate: false, + canUpdate: false, + canDelete: false, + user: { + name: "", + email: "", + }, + }); }; return ( @@ -63,9 +82,9 @@ export default function AddCollection({ toggleCollectionModal }: Props) {

- setNewCollection({ ...newCollection, name: e.target.value }) + setCollection({ ...collection, name: e.target.value }) } type="text" placeholder="e.g. Example Collection" @@ -76,10 +95,10 @@ export default function AddCollection({ toggleCollectionModal }: Props) {

Description

- setNewCollection({ - ...newCollection, + setCollection({ + ...collection, description: e.target.value, }) } @@ -95,9 +114,12 @@ export default function AddCollection({ toggleCollectionModal }: Props) {

Members

{ - setMemberEmail(e.target.value); + setMember({ + ...member, + user: { ...member.user, email: e.target.value }, + }); }} type="text" placeholder="Email" @@ -108,10 +130,9 @@ export default function AddCollection({ toggleCollectionModal }: Props) { onClick={() => addMemberToCollection( session.data?.user.email as string, - memberEmail, - newCollection as unknown as ExtendedCollection, - setMemberState, - "ADD" + member.user.email as string, + collection, + setMemberState ) } className="absolute flex items-center justify-center right-2 top-2 bottom-2 bg-sky-500 hover:bg-sky-400 duration-100 text-white w-9 rounded-md cursor-pointer" @@ -120,143 +141,148 @@ export default function AddCollection({ toggleCollectionModal }: Props) {
- {newCollection.members[0] ? ( -

- (All Members have Read access to this collection.) -

- ) : null} + {collection?.members[0]?.user ? ( + <> +

+ (All Members have Read access to this collection.) +

-
- {newCollection.members.map((e, i) => { - return ( -
- { - const updatedMembers = newCollection.members.filter( - (member) => { - return member.email !== e.email; - } - ); - setNewCollection({ - ...newCollection, - members: updatedMembers, - }); - }} - /> -
- + {collection.members.map((e, i) => { + return ( +
-
- + { + const updatedMembers = collection.members.filter( + (member) => { + return member.user.email !== e.user.email; + } + ); + setCollection({ + ...collection, + members: updatedMembers, + }); + }} + /> +
+ +
+ +
+
+
+

+ {e.user.name} +

+

{e.user.email}

+
- -
-

{e.name}

-

{e.email}

-
-
-
-
-

Permissions

-

- (Click to toggle.) -

-
+
+
+

+ Permissions +

+

+ (Click to toggle.) +

+
-
- +
+ - + - + +
+
-
-
- ); - })} -
+ ); + })} +
+ + ) : null}
{ + if (!collection.id) return null; + const response = await removeCollection(collection.id); if (response) { toggleDeleteCollectionModal(); diff --git a/components/Modal/EditCollection.tsx b/components/Modal/EditCollection.tsx index 46359ac..96e02bd 100644 --- a/components/Modal/EditCollection.tsx +++ b/components/Modal/EditCollection.tsx @@ -8,13 +8,12 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faClose, faPenToSquare, - faPlus, faTrashCan, faUser, faUserPlus, } from "@fortawesome/free-solid-svg-icons"; import useCollectionStore from "@/store/collections"; -import { ExtendedCollection } from "@/types/global"; +import { CollectionIncludingMembers, Member } from "@/types/global"; import { useSession } from "next-auth/react"; import Modal from "@/components/Modal"; import DeleteCollection from "@/components/Modal/DeleteCollection"; @@ -25,17 +24,25 @@ import Checkbox from "../Checkbox"; type Props = { toggleCollectionModal: Function; - collection: ExtendedCollection; + activeCollection: CollectionIncludingMembers; }; export default function EditCollection({ toggleCollectionModal, - collection, + activeCollection, }: Props) { - const [activeCollection, setActiveCollection] = - useState(collection); + const [collection, setCollection] = + useState(activeCollection); - const [memberEmail, setMemberEmail] = useState(""); + const [member, setMember] = useState({ + canCreate: false, + canUpdate: false, + canDelete: false, + user: { + name: "", + email: "", + }, + }); const { updateCollection } = useCollectionStore(); @@ -47,21 +54,29 @@ export default function EditCollection({ const session = useSession(); - const setMemberState = (newMember: any) => { - setActiveCollection({ - ...activeCollection, - members: [...activeCollection.members, newMember], + const setMemberState = (newMember: Member) => { + if (!collection) return null; + + setCollection({ + ...collection, + members: [...collection.members, newMember], }); - setMemberEmail(""); + setMember({ + canCreate: false, + canUpdate: false, + canDelete: false, + user: { + name: "", + email: "", + }, + }); }; const submit = async () => { - console.log(activeCollection); + if (!collection) return null; - const response = await updateCollection( - activeCollection as ExtendedCollection - ); + const response = await updateCollection(collection); if (response) toggleCollectionModal(); }; @@ -77,9 +92,9 @@ export default function EditCollection({

- setActiveCollection({ ...activeCollection, name: e.target.value }) + setCollection({ ...collection, name: e.target.value }) } type="text" placeholder="e.g. Example Collection" @@ -90,10 +105,10 @@ export default function EditCollection({

Description

- setActiveCollection({ - ...activeCollection, + setCollection({ + ...collection, description: e.target.value, }) } @@ -127,9 +142,12 @@ export default function EditCollection({
{ - setMemberEmail(e.target.value); + setMember({ + ...member, + user: { ...member.user, email: e.target.value }, + }); }} type="text" placeholder="Email" @@ -140,10 +158,9 @@ export default function EditCollection({ onClick={() => addMemberToCollection( session.data?.user.email as string, - memberEmail, - activeCollection, - setMemberState, - "UPDATE" + member.user.email, + collection, + setMemberState ) } className="absolute flex items-center justify-center right-2 top-2 bottom-2 bg-sky-500 hover:bg-sky-400 duration-100 text-white w-9 rounded-md cursor-pointer" @@ -151,14 +168,14 @@ export default function EditCollection({
- {activeCollection.members[0] ? ( + {collection.members[0] ? (

(All Members have Read access to this collection.)

) : null}
- {activeCollection.members.map((e, i) => { + {collection.members.map((e, i) => { return (
{ - const updatedMembers = activeCollection.members.filter( - (member) => { - return member.user.email !== e.user.email; - } - ); - setActiveCollection({ - ...activeCollection, + const updatedMembers = collection.members.filter((member) => { + return member.user.email !== e.user.email; + }); + setCollection({ + ...collection, members: updatedMembers, }); }} @@ -213,7 +228,7 @@ export default function EditCollection({ className="peer sr-only" checked={e.canCreate} onChange={() => { - const updatedMembers = activeCollection.members.map( + const updatedMembers = collection.members.map( (member) => { if (member.user.email === e.user.email) { return { ...member, canCreate: !e.canCreate }; @@ -221,8 +236,8 @@ export default function EditCollection({ return member; } ); - setActiveCollection({ - ...activeCollection, + setCollection({ + ...collection, members: updatedMembers, }); }} @@ -239,7 +254,7 @@ export default function EditCollection({ className="peer sr-only" checked={e.canUpdate} onChange={() => { - const updatedMembers = activeCollection.members.map( + const updatedMembers = collection.members.map( (member) => { if (member.user.email === e.user.email) { return { ...member, canUpdate: !e.canUpdate }; @@ -247,8 +262,8 @@ export default function EditCollection({ return member; } ); - setActiveCollection({ - ...activeCollection, + setCollection({ + ...collection, members: updatedMembers, }); }} @@ -265,7 +280,7 @@ export default function EditCollection({ className="peer sr-only" checked={e.canDelete} onChange={() => { - const updatedMembers = activeCollection.members.map( + const updatedMembers = collection.members.map( (member) => { if (member.user.email === e.user.email) { return { ...member, canDelete: !e.canDelete }; @@ -273,8 +288,8 @@ export default function EditCollection({ return member; } ); - setActiveCollection({ - ...activeCollection, + setCollection({ + ...collection, members: updatedMembers, }); }} @@ -320,7 +335,7 @@ export default function EditCollection({ {deleteCollectionModal ? ( diff --git a/components/Modal/Team.tsx b/components/Modal/Team.tsx deleted file mode 100644 index b287d51..0000000 --- a/components/Modal/Team.tsx +++ /dev/null @@ -1,265 +0,0 @@ -// Copyright (C) 2022-present Daniel31x13 -// This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3. -// This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. -// You should have received a copy of the GNU General Public License along with this program. If not, see . - -import React, { useState } from "react"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { faClose, faPlus, faUser } from "@fortawesome/free-solid-svg-icons"; -import useCollectionStore from "@/store/collections"; -import { ExtendedCollection, NewCollection } from "@/types/global"; -import { useSession } from "next-auth/react"; -import RequiredBadge from "../RequiredBadge"; -import addMemberToCollection from "@/lib/client/addMemberToCollection"; -import ImageWithFallback from "../ImageWithFallback"; - -type Props = { - toggleCollectionModal: Function; -}; - -export default function AddCollection({ toggleCollectionModal }: Props) { - const [newCollection, setNewCollection] = useState({ - name: "", - description: "", - members: [], - }); - - const [memberEmail, setMemberEmail] = useState(""); - - const { addCollection } = useCollectionStore(); - - const session = useSession(); - - const submit = async () => { - console.log(newCollection); - - const response = await addCollection(newCollection as NewCollection); - - if (response) toggleCollectionModal(); - }; - - const setMemberState = (newMember: any) => { - setNewCollection({ - ...newCollection, - members: [...newCollection.members, newMember], - }); - - setMemberEmail(""); - }; - - return ( -
-

New Collection

- -
-
-

- Name - -

- - setNewCollection({ ...newCollection, name: e.target.value }) - } - type="text" - placeholder="e.g. Example Collection" - className="w-full rounded-md p-3 border-sky-100 border-solid border outline-none focus:border-sky-500 duration-100" - /> -
- -
-

Description

- - setNewCollection({ - ...newCollection, - description: e.target.value, - }) - } - type="text" - placeholder="Collection description" - className="w-full rounded-md p-3 border-sky-100 border-solid border outline-none focus:border-sky-500 duration-100" - /> -
-
- -
- -

Members

-
- { - setMemberEmail(e.target.value); - }} - type="text" - placeholder="Email" - className="w-full rounded-md p-3 pr-12 border-sky-100 border-solid border outline-none focus:border-sky-500 duration-100" - /> - -
- addMemberToCollection( - session.data?.user.email as string, - memberEmail, - newCollection as unknown as ExtendedCollection, - setMemberState, - "ADD" - ) - } - className="absolute flex items-center justify-center right-2 top-2 bottom-2 bg-sky-500 hover:bg-sky-400 duration-100 text-white w-9 rounded-md cursor-pointer" - > - -
-
- - {newCollection.members[0] ? ( -

- (All Members have Read access to this collection.) -

- ) : null} - -
- {newCollection.members.map((e, i) => { - return ( -
- { - const updatedMembers = newCollection.members.filter( - (member) => { - return member.email !== e.email; - } - ); - setNewCollection({ - ...newCollection, - members: updatedMembers, - }); - }} - /> -
- -
- -
-
-
-

{e.name}

-

{e.email}

-
-
-
-
-

Permissions

-

- (Click to toggle.) -

-
- -
- - - - - -
-
-
- ); - })} -
- -
- - Add Collection -
-
- ); -} diff --git a/lib/api/controllers/collections/postCollection.ts b/lib/api/controllers/collections/postCollection.ts index 3de0478..5957b95 100644 --- a/lib/api/controllers/collections/postCollection.ts +++ b/lib/api/controllers/collections/postCollection.ts @@ -4,10 +4,13 @@ // You should have received a copy of the GNU General Public License along with this program. If not, see . import { prisma } from "@/lib/api/db"; -import { NewCollection } from "@/types/global"; +import { CollectionIncludingMembers } from "@/types/global"; import { existsSync, mkdirSync } from "fs"; -export default async function (collection: NewCollection, userId: number) { +export default async function ( + collection: CollectionIncludingMembers, + userId: number +) { if (!collection || collection.name.trim() === "") return { response: "Please enter a valid collection.", @@ -43,7 +46,7 @@ export default async function (collection: NewCollection, userId: number) { description: collection.description, members: { create: collection.members.map((e) => ({ - user: { connect: { email: e.email } }, + user: { connect: { email: e.user.email } }, canCreate: e.canCreate, canUpdate: e.canUpdate, canDelete: e.canDelete, diff --git a/lib/api/controllers/collections/updateCollection.ts b/lib/api/controllers/collections/updateCollection.ts index 4815fe4..ff172f5 100644 --- a/lib/api/controllers/collections/updateCollection.ts +++ b/lib/api/controllers/collections/updateCollection.ts @@ -4,11 +4,14 @@ // You should have received a copy of the GNU General Public License along with this program. If not, see . import { prisma } from "@/lib/api/db"; -import { ExtendedCollection } from "@/types/global"; +import { CollectionIncludingMembers } from "@/types/global"; import getPermission from "@/lib/api/getPermission"; -export default async function (collection: ExtendedCollection, userId: number) { - if (!collection) +export default async function ( + collection: CollectionIncludingMembers, + userId: number +) { + if (!collection.id) return { response: "Please choose a valid collection.", status: 401 }; const collectionIsAccessible = await getPermission(userId, collection.id); diff --git a/lib/client/addMemberToCollection.ts b/lib/client/addMemberToCollection.ts index dd8002f..6c64610 100644 --- a/lib/client/addMemberToCollection.ts +++ b/lib/client/addMemberToCollection.ts @@ -1,15 +1,15 @@ -import { ExtendedCollection, NewCollection } from "@/types/global"; +import { CollectionIncludingMembers, Member } from "@/types/global"; import getPublicUserDataByEmail from "./getPublicUserDataByEmail"; const addMemberToCollection = async ( ownerEmail: string, memberEmail: string, - collection: ExtendedCollection, - setMemberState: Function, - collectionMethod: "ADD" | "UPDATE" + collection: CollectionIncludingMembers, + setMember: (newMember: Member) => null | undefined ) => { - const checkIfMemberAlreadyExists = collection.members.find((e: any) => { - const email = collectionMethod === "ADD" ? e.email : e.user.email; + console.log(collection.members); + const checkIfMemberAlreadyExists = collection.members.find((e) => { + const email = e.user.email; return email === memberEmail; }); @@ -24,30 +24,20 @@ const addMemberToCollection = async ( // Lookup, get data/err, list ... const user = await getPublicUserDataByEmail(memberEmail.trim()); - if (user.email) { - const newMember = - collectionMethod === "ADD" - ? { - id: user.id, - name: user.name, - email: user.email, - canCreate: false, - canUpdate: false, - canDelete: false, - } - : { - collectionId: collection.id, - userId: user.id, - canCreate: false, - canUpdate: false, - canDelete: false, - user: { - name: user.name, - email: user.email, - }, - }; + console.log(collection); - setMemberState(newMember); + if (user.email) { + setMember({ + collectionId: collection.id, + userId: user.id, + canCreate: false, + canUpdate: false, + canDelete: false, + user: { + name: user.name, + email: user.email, + }, + }); } } }; diff --git a/pages/collections/[id].tsx b/pages/collections/[id].tsx index 48d7856..59b2d17 100644 --- a/pages/collections/[id].tsx +++ b/pages/collections/[id].tsx @@ -11,7 +11,7 @@ import EditCollection from "@/components/Modal/EditCollection"; import DeleteCollection from "@/components/Modal/DeleteCollection"; import useCollectionStore from "@/store/collections"; import useLinkStore from "@/store/links"; -import { ExtendedCollection } from "@/types/global"; +import { CollectionIncludingMembers } from "@/types/global"; import { faAdd, faEllipsis, @@ -46,7 +46,7 @@ export default function () { const [sortBy, setSortBy] = useState("Name (A-Z)"); const [activeCollection, setActiveCollection] = - useState(); + useState(); const [sortedLinks, setSortedLinks] = useState(links); @@ -128,7 +128,9 @@ export default function () { Team
{activeCollection?.members - .sort((a, b) => a.userId - b.userId) + .sort( + (a, b) => (a.user.id as number) - (b.user.id as number) + ) .map((e, i) => { return ( ) : null} diff --git a/store/collections.ts b/store/collections.ts index 6a2f9e2..05d7efe 100644 --- a/store/collections.ts +++ b/store/collections.ts @@ -4,15 +4,17 @@ // You should have received a copy of the GNU General Public License along with this program. If not, see . import { create } from "zustand"; -import { ExtendedCollection, NewCollection } from "@/types/global"; +import { CollectionIncludingMembers } from "@/types/global"; import useTagStore from "./tags"; import useLinkStore from "./links"; type CollectionStore = { - collections: ExtendedCollection[]; + collections: CollectionIncludingMembers[]; setCollections: () => void; - addCollection: (body: NewCollection) => Promise; - updateCollection: (collection: ExtendedCollection) => Promise; + addCollection: (body: CollectionIncludingMembers) => Promise; + updateCollection: ( + collection: CollectionIncludingMembers + ) => Promise; removeCollection: (collectionId: number) => Promise; }; diff --git a/types/global.ts b/types/global.ts index 107cbab..61e1f65 100644 --- a/types/global.ts +++ b/types/global.ts @@ -4,7 +4,9 @@ // You should have received a copy of the GNU General Public License along with this program. If not, see . import { Collection, Link, Tag, User } from "@prisma/client"; -import { SetStateAction } from "react"; + +type OptionalExcluding = Partial & + Pick; export interface ExtendedLink extends Link { tags: Tag[]; @@ -34,18 +36,20 @@ export interface NewCollection { }[]; } -export interface ExtendedCollection extends Collection { - members: { - collectionId: number; - userId: number; - canCreate: boolean; - canUpdate: boolean; - canDelete: boolean; - user: { - name: string; - email: string; - }; - }[]; +export interface Member { + collectionId?: number; + userId?: number; + canCreate: boolean; + canUpdate: boolean; + canDelete: boolean; + user: OptionalExcluding; +} + +export interface CollectionIncludingMembers + extends Omit { + id?: number; + createdAt?: Date; + members: Member[]; } export type SearchSettings = {