Many changes.

This commit is contained in:
Daniel 2023-02-19 07:02:02 +03:30
parent d19204f4c0
commit e0f4c71eb2
19 changed files with 282 additions and 122 deletions

View File

@ -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 <div ref={wrapperRef}>{children}</div>;
return (
<div ref={wrapperRef} className={className}>
{children}
</div>
);
}

View File

@ -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 (
<div className="flex flex-wrap">
{collections.map((e, i) => {
const formattedDate = new Date(e.createdAt).toLocaleString("en-US", {
year: "numeric",
month: "short",
day: "numeric",
});
return (
<div
className="p-5 bg-gray-100 m-2 h-40 w-60 rounded border-sky-100 border-solid border flex flex-col justify-between cursor-pointer hover:shadow duration-100"
key={i}
>
<div className="flex justify-between text-sky-900">
<p className="text-lg w-max">{e.name}</p>
<FontAwesomeIcon icon={faChevronRight} className="w-3" />
</div>
<p className="text-sm text-sky-300">{formattedDate}</p>
</div>
);
})}
</div>
);
}

View File

@ -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<Collections[]>([]);
useEffect(() => {
fetch("/api/routes/collections/getCollections")
.then((res) => res.json())
.then((data) => setCollections(data.response));
}, []);
return (
<div className="flex flex-wrap">
{collections.map((e, i) => {
return (
<div className="p-5 bg-gray-200 m-2 w-max rounded" key={i}>
<p>{e.name}</p>
</div>
);
})}
</div>
);
}

View File

@ -2,7 +2,7 @@ import { signOut } from "next-auth/react";
export default function Navbar() {
return (
<div className="flex justify-between p-5">
<div className="flex justify-between p-5 border-solid border-b-sky-100 border border-l-white">
<p>Navbar</p>
<div onClick={() => signOut()} className="cursor-pointer w-max">
Sign Out

View File

@ -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 (
<div className="fixed bg-gray-200 top-0 bottom-0 left-0 w-80 p-5">
<div className="flex justify-between gap-5 items-center h-9">
<p>{user?.name}</p>
<div className="fixed bg-gray-100 top-0 bottom-0 left-0 w-80 p-5 overflow-y-auto hide-scrollbar border-solid border-r-sky-100 border">
<div className="flex gap-3 items-center mb-5 cursor-pointer w-fit text-gray-600">
<FontAwesomeIcon icon={faUserCircle} className="h-8" />
<div className="flex items-center gap-1">
<p>{user?.name}</p>
<FontAwesomeIcon icon={faChevronDown} className="h-3" />
</div>
</div>
{addCollection ? (
<ClickAwayHandler onClickOutside={toggleAddCollection}>
<div className="hover:bg-sky-100 hover:shadow-sm duration-100 text-sky-900 rounded my-1 p-3 cursor-pointer flex items-center gap-2">
<FontAwesomeIcon icon={faDatabase} className="w-4" />
<p>All Collections</p>
</div>
<div className="text-gray-500 flex items-center justify-between mt-5">
<p>Collections</p>
{collectionInput ? (
<ClickAwayHandler
onClickOutside={toggleCollectionInput}
className="w-fit"
>
<input
type="text"
placeholder="Enter Collection Name"
className="w-48 rounded p-2"
className="w-44 rounded p-1"
onKeyDown={submitCollection}
autoFocus
/>
</ClickAwayHandler>
) : (
<div
onClick={toggleAddCollection}
className="select-none cursor-pointer bg-white rounded p-2"
>
Create Collection +
</div>
<FontAwesomeIcon
icon={faPlus}
onClick={toggleCollectionInput}
className="select-none cursor-pointer rounded p-2 w-3"
/>
)}
</div>
<div>
{collections.map((e, i) => {
return (
<div
className="hover:bg-sky-100 hover:shadow-sm duration-100 text-sky-900 rounded my-1 p-3 cursor-pointer flex items-center gap-2"
key={i}
>
<FontAwesomeIcon icon={faFolder} className="w-4" />
<p>{e.name}</p>
</div>
);
})}
</div>
</div>
);
}

View File

@ -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 (
<>

View File

@ -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 || [],
});
}

View File

@ -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<Data>
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,
});
}

View File

@ -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]);
}

View File

@ -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",

View File

@ -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";

View File

@ -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";

View File

@ -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";

View File

@ -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);
}

View File

@ -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
<div className="p-5">
<p className="text-3xl font-bold text-center mb-10">Linkwarden</p>
<Collections />
<CollectionCards />
</div>
);
}

65
store/collection.ts Normal file
View File

@ -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<CollectionSlice>()((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;

View File

@ -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;
}

View File

@ -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"