From f80113c487c8b4256b2937d686c62bbd2f79f5e5 Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 23 Mar 2023 02:41:54 +0330 Subject: [PATCH] feat: added dropdown component --- components/AddLinkModal.tsx | 4 +- components/ClickAwayHandler.tsx | 4 +- components/Dropdown.tsx | 47 +++++++++++++++++++ .../InputSelect/CollectionSelection.tsx | 4 +- components/InputSelect/TagSelection.tsx | 4 +- components/LinkList.tsx | 37 +++++++++++++-- components/Navbar.tsx | 15 ++---- components/Sidebar/index.tsx | 44 +++++++++++++---- lib/api/controllers/links/postLink.ts | 2 +- lib/client/getInitialData.ts | 12 ++--- package.json | 1 + pages/collections/[id].tsx | 4 +- pages/collections/index.tsx | 4 +- .../migration.sql | 0 .../migration.sql | 2 + prisma/schema.prisma | 2 +- store/collections.ts | 6 +-- store/links.ts | 6 +-- store/tags.ts | 6 +-- styles/globals.css | 2 +- 20 files changed, 152 insertions(+), 54 deletions(-) create mode 100644 components/Dropdown.tsx rename prisma/migrations/{20230308212222_ => 20230312184928_init}/migration.sql (100%) create mode 100644 prisma/migrations/20230322214726_renamed_is_favorite_to_starred/migration.sql diff --git a/components/AddLinkModal.tsx b/components/AddLinkModal.tsx index fd4bfcb..8ba37db 100644 --- a/components/AddLinkModal.tsx +++ b/components/AddLinkModal.tsx @@ -5,7 +5,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faPlus } from "@fortawesome/free-solid-svg-icons"; import { useRouter } from "next/router"; import { NewLink } from "@/types/global"; -import useLinkSlice from "@/store/links"; +import useLinkStore from "@/store/links"; export default function ({ toggleLinkModal }: { toggleLinkModal: Function }) { const router = useRouter(); @@ -17,7 +17,7 @@ export default function ({ toggleLinkModal }: { toggleLinkModal: Function }) { collection: { id: Number(router.query.id) }, }); - const { addLink } = useLinkSlice(); + const { addLink } = useLinkStore(); const setTags = (e: any) => { const tagNames = e.map((e: any) => { diff --git a/components/ClickAwayHandler.tsx b/components/ClickAwayHandler.tsx index 3c8567d..d69edca 100644 --- a/components/ClickAwayHandler.tsx +++ b/components/ClickAwayHandler.tsx @@ -1,10 +1,10 @@ import React, { useRef, useEffect, ReactNode, RefObject } from "react"; -interface Props { +type Props = { children: ReactNode; onClickOutside: Function; className?: string; -} +}; function useOutsideAlerter( ref: RefObject, diff --git a/components/Dropdown.tsx b/components/Dropdown.tsx new file mode 100644 index 0000000..157f3da --- /dev/null +++ b/components/Dropdown.tsx @@ -0,0 +1,47 @@ +import Link from "next/link"; +import React, { MouseEventHandler, ReactElement } from "react"; +import ClickAwayHandler from "./ClickAwayHandler"; + +type MenuItem = { + name: string; + icon: ReactElement; + onClick?: MouseEventHandler; + href?: string; +}; + +type Props = { + onClickOutside: Function; + className?: string; + items: MenuItem[]; +}; + +export default function ({ onClickOutside, className, items }: Props) { + return ( + + {items.map((e, i) => { + return e.href ? ( + +
+ {React.cloneElement(e.icon, { + className: "text-sky-500 w-5 h-5", + })} +

{e.name}

+
+ + ) : ( +
+
+ {React.cloneElement(e.icon, { + className: "text-sky-500 w-5 h-5", + })} +

{e.name}

+
+
+ ); + })} +
+ ); +} diff --git a/components/InputSelect/CollectionSelection.tsx b/components/InputSelect/CollectionSelection.tsx index 1c3348a..aa5be0d 100644 --- a/components/InputSelect/CollectionSelection.tsx +++ b/components/InputSelect/CollectionSelection.tsx @@ -1,4 +1,4 @@ -import useCollectionSlice from "@/store/collections"; +import useCollectionStore from "@/store/collections"; import { useRouter } from "next/router"; import { useEffect, useState } from "react"; import CreatableSelect from "react-select/creatable"; @@ -6,7 +6,7 @@ import { styles } from "./styles"; import { Options } from "./types"; export default function ({ onChange }: any) { - const { collections } = useCollectionSlice(); + const { collections } = useCollectionStore(); const router = useRouter(); const [options, setOptions] = useState([]); diff --git a/components/InputSelect/TagSelection.tsx b/components/InputSelect/TagSelection.tsx index 24887b5..87ac38b 100644 --- a/components/InputSelect/TagSelection.tsx +++ b/components/InputSelect/TagSelection.tsx @@ -1,11 +1,11 @@ -import useTagSlice from "@/store/tags"; +import useTagStore from "@/store/tags"; import { useEffect, useState } from "react"; import CreatableSelect from "react-select/creatable"; import { styles } from "./styles"; import { Options } from "./types"; export default function ({ onChange }: any) { - const { tags } = useTagSlice(); + const { tags } = useTagStore(); const [options, setOptions] = useState([]); diff --git a/components/LinkList.tsx b/components/LinkList.tsx index 3c01e2c..c3ff611 100644 --- a/components/LinkList.tsx +++ b/components/LinkList.tsx @@ -3,13 +3,16 @@ import { faFolder, faArrowUpRightFromSquare, faEllipsis, - faHeart, + faStar, + faPenToSquare, + faTrashCan, } from "@fortawesome/free-solid-svg-icons"; import { faFileImage, faFilePdf } from "@fortawesome/free-regular-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { useState } from "react"; import Image from "next/image"; +import Dropdown from "./Dropdown"; export default function ({ link, @@ -18,6 +21,7 @@ export default function ({ link: ExtendedLink; count: number; }) { + const [editDropdown, setEditDropdown] = useState(false); const [archiveLabel, setArchiveLabel] = useState("Archived Formats"); const shortendURL = new URL(link.url).host.toLowerCase(); @@ -42,8 +46,8 @@ export default function ({

{count + 1}.

{link.name}

- {link.isFavorites ? ( - + {link.starred ? ( + ) : null}

{link.title}

@@ -76,10 +80,12 @@ export default function ({ -
+ +
setEditDropdown(!editDropdown)} />

@@ -121,6 +127,27 @@ export default function ({ />

+ + {editDropdown ? ( + , + }, + { + name: "Edit", + icon: , + }, + { + name: "Delete", + icon: , + }, + ]} + onClickOutside={() => setEditDropdown(!editDropdown)} + className="absolute top-8 right-0" + /> + ) : null}
diff --git a/components/Navbar.tsx b/components/Navbar.tsx index 4619427..075463b 100644 --- a/components/Navbar.tsx +++ b/components/Navbar.tsx @@ -1,6 +1,5 @@ -import { signOut } from "next-auth/react"; import { useRouter } from "next/router"; -import useCollectionSlice from "@/store/collections"; +import useCollectionStore from "@/store/collections"; import { Collection, Tag } from "@prisma/client"; import ClickAwayHandler from "./ClickAwayHandler"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; @@ -15,15 +14,15 @@ import { } from "@fortawesome/free-solid-svg-icons"; import { useEffect, useState } from "react"; import AddLinkModal from "./AddLinkModal"; -import useTagSlice from "@/store/tags"; +import useTagStore from "@/store/tags"; export default function () { const router = useRouter(); const [pageName, setPageName] = useState(""); const [pageIcon, setPageIcon] = useState(null); - const { collections } = useCollectionSlice(); - const { tags } = useTagSlice(); + const { collections } = useCollectionStore(); + const { tags } = useTagStore(); useEffect(() => { if (router.route === "/collections/[id]") { @@ -83,12 +82,6 @@ export default function () { icon={faMagnifyingGlass} className="select-none cursor-pointer w-5 h-5 text-white bg-sky-500 p-2 rounded hover:bg-sky-400 duration-100" /> -
signOut()} - className="cursor-pointer w-max text-sky-900" - > - Sign Out -
{linkModal ? (
diff --git a/components/Sidebar/index.tsx b/components/Sidebar/index.tsx index c70f3f1..8d6c48e 100644 --- a/components/Sidebar/index.tsx +++ b/components/Sidebar/index.tsx @@ -1,9 +1,9 @@ import { useSession } from "next-auth/react"; import ClickAwayHandler from "@/components/ClickAwayHandler"; import { useState } from "react"; -import useCollectionSlice from "@/store/collections"; +import useCollectionStore from "@/store/collections"; +import { signOut } from "next-auth/react"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { faUserCircle } from "@fortawesome/free-regular-svg-icons"; import { faPlus, faChevronDown, @@ -11,19 +11,24 @@ import { faBox, faHashtag, faBookmark, + faCircleUser, + faSliders, + faArrowRightFromBracket, } from "@fortawesome/free-solid-svg-icons"; import SidebarItem from "./SidebarItem"; -import useTagSlice from "@/store/tags"; +import useTagStore from "@/store/tags"; import Link from "next/link"; +import Dropdown from "@/components/Dropdown"; export default function () { const { data: session } = useSession(); const [collectionInput, setCollectionInput] = useState(false); + const [profileDropdown, setProfileDropdown] = useState(false); - const { collections, addCollection } = useCollectionSlice(); + const { collections, addCollection } = useCollectionStore(); - const { tags } = useTagSlice(); + const { tags } = useTagStore(); const user = session?.user; @@ -44,12 +49,35 @@ export default function () { return (
-
- -
+
+ +
setProfileDropdown(!profileDropdown)} + >

{user?.name}

+ {profileDropdown ? ( + , + }, + { + name: "Logout", + icon: , + onClick: () => { + signOut(); + setProfileDropdown(!profileDropdown); + }, + }, + ]} + onClickOutside={() => setProfileDropdown(!profileDropdown)} + className="absolute top-14 left-0" + /> + ) : null}
diff --git a/lib/api/controllers/links/postLink.ts b/lib/api/controllers/links/postLink.ts index bf677cd..d333e68 100644 --- a/lib/api/controllers/links/postLink.ts +++ b/lib/api/controllers/links/postLink.ts @@ -110,7 +110,7 @@ export default async function ( })), }, title, - isFavorites: false, + starred: false, screenshotPath: "", pdfPath: "", }, diff --git a/lib/client/getInitialData.ts b/lib/client/getInitialData.ts index b0afa40..4ab47e6 100644 --- a/lib/client/getInitialData.ts +++ b/lib/client/getInitialData.ts @@ -1,14 +1,14 @@ -import useCollectionSlice from "@/store/collections"; +import useCollectionStore from "@/store/collections"; import { useEffect } from "react"; import { useSession } from "next-auth/react"; -import useTagSlice from "@/store/tags"; -import useLinkSlice from "@/store/links"; +import useTagStore from "@/store/tags"; +import useLinkStore from "@/store/links"; export default function () { const { status } = useSession(); - const { setCollections } = useCollectionSlice(); - const { setTags } = useTagSlice(); - const { setLinks } = useLinkSlice(); + const { setCollections } = useCollectionStore(); + const { setTags } = useTagStore(); + const { setLinks } = useLinkStore(); useEffect(() => { if (status === "authenticated") { diff --git a/package.json b/package.json index a1d83ee..dd18e92 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "main": "index.js", "repository": "https://github.com/Daniel31x13/link-warden.git", "author": "Daniel31X13 ", + "license": "MIT", "private": true, "scripts": { "dev": "next dev", diff --git a/pages/collections/[id].tsx b/pages/collections/[id].tsx index 08b76bb..032e439 100644 --- a/pages/collections/[id].tsx +++ b/pages/collections/[id].tsx @@ -1,10 +1,10 @@ import LinkList from "@/components/LinkList"; -import useLinkSlice from "@/store/links"; +import useLinkStore from "@/store/links"; import { useRouter } from "next/router"; export default function () { const router = useRouter(); - const { links } = useLinkSlice(); + const { links } = useLinkStore(); const linksByCollection = links.filter( (e) => e.collectionId === Number(router.query.id) diff --git a/pages/collections/index.tsx b/pages/collections/index.tsx index 5b5312d..1b8e76b 100644 --- a/pages/collections/index.tsx +++ b/pages/collections/index.tsx @@ -1,10 +1,10 @@ import { useSession } from "next-auth/react"; -import useCollectionSlice from "@/store/collections"; +import useCollectionStore from "@/store/collections"; import CollectionCard from "@/components/CollectionCard"; export default function () { - const { collections } = useCollectionSlice(); + const { collections } = useCollectionStore(); const { data: session, status } = useSession(); const user = session?.user; diff --git a/prisma/migrations/20230308212222_/migration.sql b/prisma/migrations/20230312184928_init/migration.sql similarity index 100% rename from prisma/migrations/20230308212222_/migration.sql rename to prisma/migrations/20230312184928_init/migration.sql diff --git a/prisma/migrations/20230322214726_renamed_is_favorite_to_starred/migration.sql b/prisma/migrations/20230322214726_renamed_is_favorite_to_starred/migration.sql new file mode 100644 index 0000000..48f56e6 --- /dev/null +++ b/prisma/migrations/20230322214726_renamed_is_favorite_to_starred/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "Link" RENAME COLUMN "isFavorites" TO "starred"; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 872617b..19c207c 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -50,7 +50,7 @@ model Link { collection Collection @relation(fields: [collectionId], references: [id]) collectionId Int tags Tag[] - isFavorites Boolean + starred Boolean screenshotPath String pdfPath String createdAt DateTime @default(now()) diff --git a/store/collections.ts b/store/collections.ts index fa2ee83..5b4f648 100644 --- a/store/collections.ts +++ b/store/collections.ts @@ -1,7 +1,7 @@ import { create } from "zustand"; import { Collection } from "@prisma/client"; -type CollectionSlice = { +type CollectionStore = { collections: Collection[]; setCollections: () => void; addCollection: (collectionName: string) => void; @@ -9,7 +9,7 @@ type CollectionSlice = { removeCollection: (collectionId: number) => void; }; -const useCollectionSlice = create()((set) => ({ +const useCollectionStore = create()((set) => ({ collections: [], setCollections: async () => { const response = await fetch("/api/routes/collections"); @@ -47,4 +47,4 @@ const useCollectionSlice = create()((set) => ({ }, })); -export default useCollectionSlice; +export default useCollectionStore; diff --git a/store/links.ts b/store/links.ts index ecde3dd..6eb05d1 100644 --- a/store/links.ts +++ b/store/links.ts @@ -1,7 +1,7 @@ import { create } from "zustand"; import { ExtendedLink, NewLink } from "@/types/global"; -type LinkSlice = { +type LinkStore = { links: ExtendedLink[]; setLinks: () => void; addLink: (linkName: NewLink) => Promise; @@ -9,7 +9,7 @@ type LinkSlice = { removeLink: (linkId: number) => void; }; -const useLinkSlice = create()((set) => ({ +const useLinkStore = create()((set) => ({ links: [], setLinks: async () => { const response = await fetch("/api/routes/links"); @@ -47,4 +47,4 @@ const useLinkSlice = create()((set) => ({ }, })); -export default useLinkSlice; +export default useLinkStore; diff --git a/store/tags.ts b/store/tags.ts index 9fe969a..17b5c52 100644 --- a/store/tags.ts +++ b/store/tags.ts @@ -1,12 +1,12 @@ import { create } from "zustand"; import { Tag } from "@prisma/client"; -type TagSlice = { +type TagStore = { tags: Tag[]; setTags: () => void; }; -const useTagSlice = create()((set) => ({ +const useTagStore = create()((set) => ({ tags: [], setTags: async () => { const response = await fetch("/api/routes/tags"); @@ -17,4 +17,4 @@ const useTagSlice = create()((set) => ({ }, })); -export default useTagSlice; +export default useTagStore; diff --git a/styles/globals.css b/styles/globals.css index 790abe3..e755efd 100644 --- a/styles/globals.css +++ b/styles/globals.css @@ -39,7 +39,7 @@ @keyframes slide-up-animation { 0% { - transform: translateY(10%); + transform: translateY(15%); opacity: 0; } 100% {