From e0f4c71eb25f88128e20d3301cd2ea0c75dc518e Mon Sep 17 00:00:00 2001 From: Daniel Date: Sun, 19 Feb 2023 07:02:02 +0330 Subject: [PATCH] Many changes. --- components/ClickAwayHandler.tsx | 13 ++- components/CollectionCards.tsx | 32 ++++++++ components/Collections.tsx | 28 ------- components/Navbar.tsx | 2 +- components/Sidebar.tsx | 80 +++++++++++++------ {Layouts => layouts}/MainLayout.tsx | 5 +- .../controllers/collections/getCollections.ts | 19 +++++ .../collections/postCollection.ts | 48 +++-------- lib/{ => api}/db.ts | 0 lib/client/getInitialData.ts | 14 ++++ package.json | 7 +- pages/_app.tsx | 3 +- pages/api/auth/[...nextauth].ts | 2 +- pages/api/auth/register.ts | 2 +- .../{getCollections.ts => index.ts} | 24 +----- pages/dashboard.tsx | 6 +- store/collection.ts | 65 +++++++++++++++ styles/globals.css | 9 +++ yarn.lock | 45 +++++++++++ 19 files changed, 282 insertions(+), 122 deletions(-) create mode 100644 components/CollectionCards.tsx delete mode 100644 components/Collections.tsx rename {Layouts => layouts}/MainLayout.tsx (94%) create mode 100644 lib/api/controllers/collections/getCollections.ts rename {pages/api/routes => lib/api/controllers}/collections/postCollection.ts (52%) rename lib/{ => api}/db.ts (100%) create mode 100644 lib/client/getInitialData.ts rename pages/api/routes/collections/{getCollections.ts => index.ts} (51%) create mode 100644 store/collection.ts diff --git a/components/ClickAwayHandler.tsx b/components/ClickAwayHandler.tsx index dec4c93..0da0137 100644 --- a/components/ClickAwayHandler.tsx +++ b/components/ClickAwayHandler.tsx @@ -3,6 +3,7 @@ import React, { useRef, useEffect, ReactNode, RefObject } from "react"; interface Props { children: ReactNode; onClickOutside: Function; + className?: string; } function useOutsideAlerter( @@ -27,9 +28,17 @@ function useOutsideAlerter( }, [ref, onClickOutside]); } -export default function OutsideAlerter({ children, onClickOutside }: Props) { +export default function OutsideAlerter({ + children, + onClickOutside, + className, +}: Props) { const wrapperRef = useRef(null); useOutsideAlerter(wrapperRef, onClickOutside); - return
{children}
; + return ( +
+ {children} +
+ ); } diff --git a/components/CollectionCards.tsx b/components/CollectionCards.tsx new file mode 100644 index 0000000..250a576 --- /dev/null +++ b/components/CollectionCards.tsx @@ -0,0 +1,32 @@ +import useCollectionSlice from "@/store/collection"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faChevronRight } from "@fortawesome/free-solid-svg-icons"; + +export default function Collections() { + const { collections } = useCollectionSlice(); + + return ( +
+ {collections.map((e, i) => { + const formattedDate = new Date(e.createdAt).toLocaleString("en-US", { + year: "numeric", + month: "short", + day: "numeric", + }); + + return ( +
+
+

{e.name}

+ +
+

{formattedDate}

+
+ ); + })} +
+ ); +} diff --git a/components/Collections.tsx b/components/Collections.tsx deleted file mode 100644 index 0cf0a2b..0000000 --- a/components/Collections.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { useEffect, useState } from "react"; - -interface Collections { - id: number; - name: string; - createdAt: string; -} - -export default function Collections() { - const [collections, setCollections] = useState([]); - useEffect(() => { - fetch("/api/routes/collections/getCollections") - .then((res) => res.json()) - .then((data) => setCollections(data.response)); - }, []); - - return ( -
- {collections.map((e, i) => { - return ( -
-

{e.name}

-
- ); - })} -
- ); -} diff --git a/components/Navbar.tsx b/components/Navbar.tsx index cc7659f..26d5267 100644 --- a/components/Navbar.tsx +++ b/components/Navbar.tsx @@ -2,7 +2,7 @@ import { signOut } from "next-auth/react"; export default function Navbar() { return ( -
+

Navbar

signOut()} className="cursor-pointer w-max"> Sign Out diff --git a/components/Sidebar.tsx b/components/Sidebar.tsx index 36caab4..8892b40 100644 --- a/components/Sidebar.tsx +++ b/components/Sidebar.tsx @@ -1,16 +1,26 @@ import { useSession } from "next-auth/react"; import ClickAwayHandler from "@/components/ClickAwayHandler"; import { useState } from "react"; +import useCollectionSlice from "@/store/collection"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faFolder, faUserCircle } from "@fortawesome/free-regular-svg-icons"; +import { + faDatabase, + faPlus, + faChevronDown, +} from "@fortawesome/free-solid-svg-icons"; export default function Sidebar() { - const { data: session, status } = useSession(); + const { data: session } = useSession(); - const [addCollection, setAddCollection] = useState(false); + const [collectionInput, setCollectionInput] = useState(false); + + const { collections, addCollection } = useCollectionSlice(); const user = session?.user; - const toggleAddCollection = () => { - setAddCollection(!addCollection); + const toggleCollectionInput = () => { + setCollectionInput(!collectionInput); }; const submitCollection = async ( @@ -19,42 +29,62 @@ export default function Sidebar() { const collectionName: string = (event.target as HTMLInputElement).value; if (event.key === "Enter" && collectionName) { - await fetch("/api/routes/collections/postCollection", { - body: JSON.stringify({ collectionName }), - headers: { - "Content-Type": "application/json", - }, - method: "POST", - }) - .then((res) => res.json()) - .then((data) => console.log(data)); + addCollection(collectionName); + (event.target as HTMLInputElement).value = ""; } }; return ( -
-
-

{user?.name}

+
+
+ +
+

{user?.name}

+ +
+
- {addCollection ? ( - +
+ +

All Collections

+
+ +
+

Collections

+ {collectionInput ? ( + ) : ( -
- Create Collection + -
+ )}
+
+ {collections.map((e, i) => { + return ( +
+ +

{e.name}

+
+ ); + })} +
); } diff --git a/Layouts/MainLayout.tsx b/layouts/MainLayout.tsx similarity index 94% rename from Layouts/MainLayout.tsx rename to layouts/MainLayout.tsx index 1e9c924..e564a65 100644 --- a/Layouts/MainLayout.tsx +++ b/layouts/MainLayout.tsx @@ -6,6 +6,7 @@ import { useSession } from "next-auth/react"; import Loader from "../components/Loader"; import useRedirection from "@/hooks/useRedirection"; import { useRouter } from "next/router"; +import getInitialData from "@/lib/client/getInitialData"; interface Props { children: ReactNode; @@ -14,11 +15,11 @@ interface Props { export default function Layout({ children }: Props) { const { status } = useSession(); const router = useRouter(); - const redirection = useRedirection(); - const routeExists = router.route === "/_error" ? false : true; + getInitialData(); + if (status === "authenticated" && !redirection && routeExists) return ( <> diff --git a/lib/api/controllers/collections/getCollections.ts b/lib/api/controllers/collections/getCollections.ts new file mode 100644 index 0000000..acfb08d --- /dev/null +++ b/lib/api/controllers/collections/getCollections.ts @@ -0,0 +1,19 @@ +import type { NextApiRequest, NextApiResponse } from "next"; +import { prisma } from "@/lib/api/db"; +import { Session } from "next-auth"; + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse, + session: Session +) { + const collections = await prisma.collection.findMany({ + where: { + ownerId: session?.user.id, + }, + }); + + return res.status(200).json({ + response: collections || [], + }); +} diff --git a/pages/api/routes/collections/postCollection.ts b/lib/api/controllers/collections/postCollection.ts similarity index 52% rename from pages/api/routes/collections/postCollection.ts rename to lib/api/controllers/collections/postCollection.ts index 61fc928..22db6f7 100644 --- a/pages/api/routes/collections/postCollection.ts +++ b/lib/api/controllers/collections/postCollection.ts @@ -1,18 +1,12 @@ import type { NextApiRequest, NextApiResponse } from "next"; -import { getServerSession } from "next-auth/next"; -import { authOptions } from "pages/api/auth/[...nextauth]"; -import { prisma } from "@/lib/db"; - -type Data = { - response: object[] | string; -}; +import { prisma } from "@/lib/api/db"; +import { Session } from "next-auth"; export default async function handler( req: NextApiRequest, - res: NextApiResponse + res: NextApiResponse, + session: Session ) { - const session = await getServerSession(req, res, authOptions); - if (!session?.user?.email) { return res.status(401).json({ response: "You must be logged in." }); } @@ -39,44 +33,24 @@ export default async function handler( }, }); - console.log(typeof session.user.id); - const checkIfCollectionExists = findCollection?.collections[0]; if (checkIfCollectionExists) { return res.status(400).json({ response: "Collection already exists." }); } - // const a = await prisma.user.update({ - // where: { - // id: session.user.id, - // }, - // data: { - // // collections: { - // // create: { name: "Das" }, - // // }, - // }, - // include: { - // collections: { include: { collection: true } }, - // }, - // }); - - await prisma.user.update({ - where: { - id: session.user.id, - }, + const createCollection = await prisma.collection.create({ data: { - collections: { - create: [ - { - name: collectionName, - }, - ], + owner: { + connect: { + id: session.user.id, + }, }, + name: collectionName, }, }); return res.status(200).json({ - response: "Success", + response: createCollection, }); } diff --git a/lib/db.ts b/lib/api/db.ts similarity index 100% rename from lib/db.ts rename to lib/api/db.ts diff --git a/lib/client/getInitialData.ts b/lib/client/getInitialData.ts new file mode 100644 index 0000000..61a08ec --- /dev/null +++ b/lib/client/getInitialData.ts @@ -0,0 +1,14 @@ +import useCollectionSlice from "@/store/collection"; +import { useEffect } from "react"; +import { useSession } from "next-auth/react"; + +export default function getInitialData() { + const { status } = useSession(); + const { setCollections } = useCollectionSlice(); + + useEffect(() => { + if (status === "authenticated") { + setCollections(); + } + }, [status]); +} diff --git a/package.json b/package.json index aff7d38..1832903 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,10 @@ "lint": "next lint" }, "dependencies": { + "@fortawesome/fontawesome-svg-core": "^6.3.0", + "@fortawesome/free-regular-svg-icons": "^6.3.0", + "@fortawesome/free-solid-svg-icons": "^6.3.0", + "@fortawesome/react-fontawesome": "^0.2.0", "@next/font": "13.1.6", "@prisma/client": "^4.9.0", "@types/node": "18.11.18", @@ -25,7 +29,8 @@ "next-auth": "^4.19.1", "react": "18.2.0", "react-dom": "18.2.0", - "typescript": "4.9.4" + "typescript": "4.9.4", + "zustand": "^4.3.3" }, "devDependencies": { "@types/bcrypt": "^5.0.0", diff --git a/pages/_app.tsx b/pages/_app.tsx index 70ecb53..aff7668 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -1,4 +1,5 @@ -import MainLayout from "@/Layouts/MainLayout"; +import React from "react"; +import MainLayout from "@/layouts/MainLayout"; import "@/styles/globals.css"; import { SessionProvider } from "next-auth/react"; import type { AppProps } from "next/app"; diff --git a/pages/api/auth/[...nextauth].ts b/pages/api/auth/[...nextauth].ts index 2b9b237..804774f 100644 --- a/pages/api/auth/[...nextauth].ts +++ b/pages/api/auth/[...nextauth].ts @@ -1,4 +1,4 @@ -import { prisma } from "@/lib/db"; +import { prisma } from "@/lib/api/db"; import NextAuth from "next-auth/next"; import CredentialsProvider from "next-auth/providers/credentials"; import { AuthOptions } from "next-auth"; diff --git a/pages/api/auth/register.ts b/pages/api/auth/register.ts index 2c79af1..0677bda 100644 --- a/pages/api/auth/register.ts +++ b/pages/api/auth/register.ts @@ -1,4 +1,4 @@ -import { prisma } from "@/lib/db"; +import { prisma } from "@/lib/api/db"; import type { NextApiRequest, NextApiResponse } from "next"; import bcrypt from "bcrypt"; diff --git a/pages/api/routes/collections/getCollections.ts b/pages/api/routes/collections/index.ts similarity index 51% rename from pages/api/routes/collections/getCollections.ts rename to pages/api/routes/collections/index.ts index 6ab39d4..b8a102a 100644 --- a/pages/api/routes/collections/getCollections.ts +++ b/pages/api/routes/collections/index.ts @@ -1,7 +1,8 @@ import type { NextApiRequest, NextApiResponse } from "next"; import { getServerSession } from "next-auth/next"; import { authOptions } from "pages/api/auth/[...nextauth]"; -import { prisma } from "@/lib/db"; +import getCollections from "@/lib/api/controllers/collections/getCollections"; +import postCollections from "@/lib/api/controllers/collections/postCollection"; type Data = { response: object[] | string; @@ -17,24 +18,7 @@ export default async function handler( return res.status(401).json({ response: "You must be logged in." }); } - const email: string = session.user.email; + if (req.method === "GET") return await getCollections(req, res, session); - const findCollection = await prisma.user.findFirst({ - where: { - email: email, - }, - include: { - collections: true, - }, - }); - - const collections = findCollection?.collections.map((e) => { - return { id: e.id, name: e.name, createdAt: e.createdAt }; - }); - - // console.log(session?.user?.email); - - return res.status(200).json({ - response: collections || [], - }); + if (req.method === "POST") return await postCollections(req, res, session); } diff --git a/pages/dashboard.tsx b/pages/dashboard.tsx index 6a953c9..a8f97a7 100644 --- a/pages/dashboard.tsx +++ b/pages/dashboard.tsx @@ -1,5 +1,5 @@ import { useSession } from "next-auth/react"; -import Collections from "@/components/Collections"; +import CollectionCards from "@/components/CollectionCards"; export default function Dashboard() { const { data: session, status } = useSession(); @@ -7,9 +7,9 @@ export default function Dashboard() { const user = session?.user; return ( + // ml-80
-

Linkwarden

- +
); } diff --git a/store/collection.ts b/store/collection.ts new file mode 100644 index 0000000..f2c23ef --- /dev/null +++ b/store/collection.ts @@ -0,0 +1,65 @@ +import { create } from "zustand"; +import { Collection } from "@prisma/client"; + +type CollectionSlice = { + collections: Collection[]; + setCollections: () => void; + addCollection: (collectionName: string) => void; + updateCollection: (collection: Collection) => void; + removeCollection: (collectionId: number) => void; +}; + +const useCollectionSlice = create()((set) => ({ + collections: [], + setCollections: async () => { + const response = await fetch("/api/routes/collections"); + + const data = await response.json(); + + if (response.ok) set({ collections: data.response }); + }, + addCollection: async (collectionName) => { + const response = await fetch("/api/routes/collections", { + body: JSON.stringify({ collectionName }), + headers: { + "Content-Type": "application/json", + }, + method: "POST", + }); + + const data = await response.json(); + + if (response.ok) + set((state) => ({ + collections: [...state.collections, data.response], + })); + }, + updateCollection: (collection) => + set((state) => ({ + collections: state.collections.map((c) => + c.id === collection.id ? collection : c + ), + })), + removeCollection: (collectionId) => { + // await fetch("/api/routes/collections/postCollection", { + // body: JSON.stringify({ collectionName }), + // headers: { + // "Content-Type": "application/json", + // }, + // method: "POST", + // }) + // .then((res) => res.json()) + // .then((data) => { + // console.log(data); + // set((state) => ({ + // collections: [...state.collections, data.response], + // })); + // }); + + set((state) => ({ + collections: state.collections.filter((c) => c.id !== collectionId), + })); + }, +})); + +export default useCollectionSlice; diff --git a/styles/globals.css b/styles/globals.css index b5c61c9..edf4f20 100644 --- a/styles/globals.css +++ b/styles/globals.css @@ -1,3 +1,12 @@ @tailwind base; @tailwind components; @tailwind utilities; + +/* Hide scrollbar */ +.hidw-scrollbar::-webkit-scrollbar { + display: none; +} +.hide-scrollbar { + -ms-overflow-style: none; + scrollbar-width: none; +} diff --git a/yarn.lock b/yarn.lock index 51cd4ec..168f90c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -24,6 +24,39 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" +"@fortawesome/fontawesome-common-types@6.3.0": + version "6.3.0" + resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.3.0.tgz#51f734e64511dbc3674cd347044d02f4dd26e86b" + integrity sha512-4BC1NMoacEBzSXRwKjZ/X/gmnbp/HU5Qqat7E8xqorUtBFZS+bwfGH5/wqOC2K6GV0rgEobp3OjGRMa5fK9pFg== + +"@fortawesome/fontawesome-svg-core@^6.3.0": + version "6.3.0" + resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.3.0.tgz#b6a17d48d231ac1fad93e43fca7271676bf316cf" + integrity sha512-uz9YifyKlixV6AcKlOX8WNdtF7l6nakGyLYxYaCa823bEBqyj/U2ssqtctO38itNEwXb8/lMzjdoJ+aaJuOdrw== + dependencies: + "@fortawesome/fontawesome-common-types" "6.3.0" + +"@fortawesome/free-regular-svg-icons@^6.3.0": + version "6.3.0" + resolved "https://registry.yarnpkg.com/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.3.0.tgz#286f87f777e6c96af59151e86647c81083029ee2" + integrity sha512-cZnwiVHZ51SVzWHOaNCIA+u9wevZjCuAGSvSYpNlm6A4H4Vhwh8481Bf/5rwheIC3fFKlgXxLKaw8Xeroz8Ntg== + dependencies: + "@fortawesome/fontawesome-common-types" "6.3.0" + +"@fortawesome/free-solid-svg-icons@^6.3.0": + version "6.3.0" + resolved "https://registry.yarnpkg.com/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.3.0.tgz#d3bd33ae18bb15fdfc3ca136e2fea05f32768a65" + integrity sha512-x5tMwzF2lTH8pyv8yeZRodItP2IVlzzmBuD1M7BjawWgg9XAvktqJJ91Qjgoaf8qJpHQ8FEU9VxRfOkLhh86QA== + dependencies: + "@fortawesome/fontawesome-common-types" "6.3.0" + +"@fortawesome/react-fontawesome@^0.2.0": + version "0.2.0" + resolved "https://registry.yarnpkg.com/@fortawesome/react-fontawesome/-/react-fontawesome-0.2.0.tgz#d90dd8a9211830b4e3c08e94b63a0ba7291ddcf4" + integrity sha512-uHg75Rb/XORTtVt7OS9WoK8uM276Ufi7gCzshVWkUJbHhh3svsUUeqXerrM96Wm7fRiDzfKRwSoahhMIkGAYHw== + dependencies: + prop-types "^15.8.1" + "@humanwhocodes/config-array@^0.11.8": version "0.11.8" resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.8.tgz#03595ac2075a4dc0f191cc2131de14fbd7d410b9" @@ -2605,6 +2638,11 @@ uri-js@^4.2.2: dependencies: punycode "^2.1.0" +use-sync-external-store@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a" + integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA== + util-deprecate@^1.0.1, util-deprecate@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" @@ -2704,3 +2742,10 @@ yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== + +zustand@^4.3.3: + version "4.3.3" + resolved "https://registry.yarnpkg.com/zustand/-/zustand-4.3.3.tgz#c9113499074dde2d6d99c1b5f591e9329572c224" + integrity sha512-x2jXq8S0kfLGNwGh87nhRfEc2eZy37tSatpSoSIN+O6HIaBhgQHSONV/F9VNrNcBcKQu/E80K1DeHDYQC/zCrQ== + dependencies: + use-sync-external-store "1.2.0"