diff --git a/components/LinkCard.tsx b/components/LinkCard.tsx deleted file mode 100644 index fc23253..0000000 --- a/components/LinkCard.tsx +++ /dev/null @@ -1,307 +0,0 @@ -import { - CollectionIncludingMembersAndLinkCount, - LinkIncludingShortenedCollectionAndTags, -} from "@/types/global"; -import { - faFolder, - faEllipsis, - faLink, -} from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { useEffect, useState } from "react"; -import Image from "next/image"; -import useLinkStore from "@/store/links"; -import useCollectionStore from "@/store/collections"; -import useAccountStore from "@/store/account"; -import { - faCalendarDays, - faFileImage, - faFilePdf, -} from "@fortawesome/free-regular-svg-icons"; -import usePermissions from "@/hooks/usePermissions"; -import { toast } from "react-hot-toast"; -import isValidUrl from "@/lib/shared/isValidUrl"; -import Link from "next/link"; -import unescapeString from "@/lib/client/unescapeString"; -import { useRouter } from "next/router"; -import EditLinkModal from "./ModalContent/EditLinkModal"; -import DeleteLinkModal from "./ModalContent/DeleteLinkModal"; -import PreservedFormatsModal from "./ModalContent/PreservedFormatsModal"; - -type Props = { - link: LinkIncludingShortenedCollectionAndTags; - count: number; - className?: string; -}; - -export default function LinkCard({ link, count, className }: Props) { - const router = useRouter(); - - const permissions = usePermissions(link.collection.id as number); - - const { collections } = useCollectionStore(); - - const { links } = useLinkStore(); - - const { account } = useAccountStore(); - - let shortendURL; - - try { - shortendURL = new URL(link.url || "").host.toLowerCase(); - } catch (error) { - console.log(error); - } - - const [collection, setCollection] = - useState( - collections.find( - (e) => e.id === link.collection.id - ) as CollectionIncludingMembersAndLinkCount - ); - - useEffect(() => { - setCollection( - collections.find( - (e) => e.id === link.collection.id - ) as CollectionIncludingMembersAndLinkCount - ); - }, [collections, links]); - - const { removeLink, updateLink } = useLinkStore(); - - const pinLink = async () => { - const isAlreadyPinned = link?.pinnedBy && link.pinnedBy[0]; - - const load = toast.loading("Applying..."); - - const response = await updateLink({ - ...link, - pinnedBy: isAlreadyPinned ? undefined : [{ id: account.id }], - }); - - toast.dismiss(load); - - response.ok && - toast.success(`Link ${isAlreadyPinned ? "Unpinned!" : "Pinned!"}`); - }; - - const deleteLink = async () => { - const load = toast.loading("Deleting..."); - - const response = await removeLink(link.id as number); - - toast.dismiss(load); - - response.ok && toast.success(`Link Deleted.`); - }; - - const url = - isValidUrl(link.url || "") && link.url ? new URL(link.url) : undefined; - - const formattedDate = new Date(link.createdAt as string).toLocaleString( - "en-US", - { - year: "numeric", - month: "short", - day: "numeric", - } - ); - - const [editLinkModal, setEditLinkModal] = useState(false); - const [deleteLinkModal, setDeleteLinkModal] = useState(false); - const [preservedFormatsModal, setPreservedFormatsModal] = useState(false); - - return ( -
- {permissions === true || - permissions?.canUpdate || - permissions?.canDelete ? ( -
-
- -
-
    - {permissions === true ? ( -
  • -
    { - (document?.activeElement as HTMLElement)?.blur(); - pinLink(); - }} - > - {link?.pinnedBy && link.pinnedBy[0] - ? "Unpin" - : "Pin to Dashboard"} -
    -
  • - ) : undefined} - {permissions === true || permissions?.canUpdate ? ( -
  • -
    { - (document?.activeElement as HTMLElement)?.blur(); - setEditLinkModal(true); - }} - > - Edit -
    -
  • - ) : undefined} - {permissions === true ? ( -
  • -
    { - (document?.activeElement as HTMLElement)?.blur(); - setPreservedFormatsModal(true); - // updateArchive(); - }} - > - Preserved Formats -
    -
  • - ) : undefined} - {permissions === true || permissions?.canDelete ? ( -
  • -
    { - (document?.activeElement as HTMLElement)?.blur(); - e.shiftKey ? deleteLink() : setDeleteLinkModal(true); - }} - > - Delete -
    -
  • - ) : undefined} -
-
- ) : undefined} - -
link.url && window.open(link.url || "", "_blank")} - className="flex flex-col justify-between cursor-pointer h-full w-full gap-1 p-3" - > - {link.url && url ? ( - { - const target = e.target as HTMLElement; - target.style.display = "none"; - }} - /> - ) : link.type === "pdf" ? ( - - ) : link.type === "image" ? ( - - ) : undefined} - -
-

{count + 1}

-

- {unescapeString(link.name || link.description) || link.url} -

-
- - {link.url ? ( -
- -

{shortendURL}

-
- ) : ( -
{link.type}
- )} - -
{ - e.stopPropagation(); - router.push(`/collections/${link.collection.id}`); - }} - className="flex items-center gap-1 max-w-full w-fit hover:opacity-70 duration-100" - > - -

{collection?.name}

-
- -
- -

{formattedDate}

-
- {/* {link.tags[0] ? ( -
-
- {link.tags.map((e, i) => ( - { - e.stopPropagation(); - }} - className="btn btn-xs btn-ghost truncate max-w-[19rem]" - > - #{e.name} - - ))} -
-
-
- ) : ( -

No Tags

- )} */} -
- {editLinkModal ? ( - setEditLinkModal(false)} - activeLink={link} - /> - ) : undefined} - {deleteLinkModal ? ( - setDeleteLinkModal(false)} - activeLink={link} - /> - ) : undefined} - {preservedFormatsModal ? ( - setPreservedFormatsModal(false)} - activeLink={link} - /> - ) : undefined} -
- ); -} diff --git a/components/LinkViews/DefaultView.tsx b/components/LinkViews/DefaultView.tsx new file mode 100644 index 0000000..714ee73 --- /dev/null +++ b/components/LinkViews/DefaultView.tsx @@ -0,0 +1,16 @@ +import LinkCard from "@/components/LinkViews/LinkComponents/LinkCard"; +import { LinkIncludingShortenedCollectionAndTags } from "@/types/global"; + +export default function DefaultGridView({ + links, +}: { + links: LinkIncludingShortenedCollectionAndTags[]; +}) { + return ( +
+ {links.map((e, i) => { + return ; + })} +
+ ); +} diff --git a/components/LinkViews/GridView.tsx b/components/LinkViews/GridView.tsx new file mode 100644 index 0000000..1b55f26 --- /dev/null +++ b/components/LinkViews/GridView.tsx @@ -0,0 +1,20 @@ +import LinkCardGrid from "@/components/LinkViews/LinkComponents/LinkCardGrid"; +import { LinkIncludingShortenedCollectionAndTags } from "@/types/global"; + +export default function CompactGridView({ + links, +}: { + links: LinkIncludingShortenedCollectionAndTags[]; +}) { + return ( +
+ {links.map((e, i) => { + return ( +
+ +
+ ); + })} +
+ ); +} diff --git a/components/LinkViews/LinkComponents/LinkActions.tsx b/components/LinkViews/LinkComponents/LinkActions.tsx new file mode 100644 index 0000000..9f53a05 --- /dev/null +++ b/components/LinkViews/LinkComponents/LinkActions.tsx @@ -0,0 +1,171 @@ +import { useState } from "react"; +import { + CollectionIncludingMembersAndLinkCount, + LinkIncludingShortenedCollectionAndTags, +} from "@/types/global"; +import usePermissions from "@/hooks/usePermissions"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faEllipsis } from "@fortawesome/free-solid-svg-icons"; +import EditLinkModal from "@/components/ModalContent/EditLinkModal"; +import DeleteLinkModal from "@/components/ModalContent/DeleteLinkModal"; +import PreservedFormatsModal from "@/components/ModalContent/PreservedFormatsModal"; +import useLinkStore from "@/store/links"; +import { toast } from "react-hot-toast"; +import useAccountStore from "@/store/account"; + +export default function LinkActions({ + link, + collection, + position, +}: { + link: LinkIncludingShortenedCollectionAndTags; + collection: CollectionIncludingMembersAndLinkCount; + position?: string; +}) { + const permissions = usePermissions(link.collection.id as number); + + const [editLinkModal, setEditLinkModal] = useState(false); + const [deleteLinkModal, setDeleteLinkModal] = useState(false); + const [preservedFormatsModal, setPreservedFormatsModal] = useState(false); + const [expandedLink, setExpandedLink] = useState(false); + + const { account } = useAccountStore(); + + const { removeLink, updateLink } = useLinkStore(); + + const pinLink = async () => { + const isAlreadyPinned = link?.pinnedBy && link.pinnedBy[0]; + + const load = toast.loading("Applying..."); + + const response = await updateLink({ + ...link, + pinnedBy: isAlreadyPinned ? undefined : [{ id: account.id }], + }); + + toast.dismiss(load); + + response.ok && + toast.success(`Link ${isAlreadyPinned ? "Unpinned!" : "Pinned!"}`); + }; + + const deleteLink = async () => { + const load = toast.loading("Deleting..."); + + const response = await removeLink(link.id as number); + + toast.dismiss(load); + + response.ok && toast.success(`Link Deleted.`); + }; + + return ( +
+ {permissions === true || + permissions?.canUpdate || + permissions?.canDelete ? ( +
+
+ +
+
    + {permissions === true ? ( +
  • +
    { + (document?.activeElement as HTMLElement)?.blur(); + pinLink(); + }} + > + {link?.pinnedBy && link.pinnedBy[0] + ? "Unpin" + : "Pin to Dashboard"} +
    +
  • + ) : undefined} + {permissions === true || permissions?.canUpdate ? ( +
  • +
    { + (document?.activeElement as HTMLElement)?.blur(); + setEditLinkModal(true); + }} + > + Edit +
    +
  • + ) : undefined} + {permissions === true ? ( +
  • +
    { + (document?.activeElement as HTMLElement)?.blur(); + setPreservedFormatsModal(true); + // updateArchive(); + }} + > + Preserved Formats +
    +
  • + ) : undefined} + {permissions === true || permissions?.canDelete ? ( +
  • +
    { + (document?.activeElement as HTMLElement)?.blur(); + e.shiftKey ? deleteLink() : setDeleteLinkModal(true); + }} + > + Delete +
    +
  • + ) : undefined} +
+
+ ) : undefined} + + {editLinkModal ? ( + setEditLinkModal(false)} + activeLink={link} + /> + ) : undefined} + {deleteLinkModal ? ( + setDeleteLinkModal(false)} + activeLink={link} + /> + ) : undefined} + {preservedFormatsModal ? ( + setPreservedFormatsModal(false)} + activeLink={link} + /> + ) : undefined} + {/* {expandedLink ? ( + setExpandedLink(false)} link={link} /> + ) : undefined} */} +
+ ); +} diff --git a/components/LinkViews/LinkComponents/LinkCard.tsx b/components/LinkViews/LinkComponents/LinkCard.tsx new file mode 100644 index 0000000..a1787d1 --- /dev/null +++ b/components/LinkViews/LinkComponents/LinkCard.tsx @@ -0,0 +1,92 @@ +import { + CollectionIncludingMembersAndLinkCount, + LinkIncludingShortenedCollectionAndTags, +} from "@/types/global"; +import { faLink } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { useEffect, useState } from "react"; +import useLinkStore from "@/store/links"; +import useCollectionStore from "@/store/collections"; +import isValidUrl from "@/lib/shared/isValidUrl"; +import unescapeString from "@/lib/client/unescapeString"; +import LinkActions from "@/components/LinkViews/LinkComponents/LinkActions"; +import LinkDate from "@/components/LinkViews/LinkComponents/LinkDate"; +import LinkCollection from "@/components/LinkViews/LinkComponents/LinkCollection"; +import LinkIcon from "@/components/LinkViews/LinkComponents/LinkIcon"; + +type Props = { + link: LinkIncludingShortenedCollectionAndTags; + count: number; + className?: string; +}; + +export default function LinkCard({ link, count, className }: Props) { + const { links } = useLinkStore(); + + const { collections } = useCollectionStore(); + + const [collection, setCollection] = + useState( + collections.find( + (e) => e.id === link.collection.id + ) as CollectionIncludingMembersAndLinkCount + ); + + useEffect(() => { + setCollection( + collections.find( + (e) => e.id === link.collection.id + ) as CollectionIncludingMembersAndLinkCount + ); + }, [collections, links]); + + let shortendURL; + + try { + shortendURL = new URL(link.url || "").host.toLowerCase(); + } catch (error) { + console.log(error); + } + + const url = + isValidUrl(link.url || "") && link.url ? new URL(link.url) : undefined; + + return ( +
+
link.url && window.open(link.url || "", "_blank")} + className="flex flex-col justify-between cursor-pointer h-full w-full gap-1 p-3" + > +
+ +
+ +
+

{count + 1}

+

+ {unescapeString(link.name || link.description) || link.url} +

+
+ + {link.url ? ( +
+ +

{shortendURL}

+
+ ) : ( +
{link.type}
+ )} + + + + +
+ + +
+ ); +} diff --git a/components/LinkViews/LinkComponents/LinkCardGrid.tsx b/components/LinkViews/LinkComponents/LinkCardGrid.tsx new file mode 100644 index 0000000..8198884 --- /dev/null +++ b/components/LinkViews/LinkComponents/LinkCardGrid.tsx @@ -0,0 +1,112 @@ +import { + CollectionIncludingMembersAndLinkCount, + LinkIncludingShortenedCollectionAndTags, +} from "@/types/global"; +import { useEffect, useState } from "react"; +import useLinkStore from "@/store/links"; +import useCollectionStore from "@/store/collections"; +import unescapeString from "@/lib/client/unescapeString"; +import LinkActions from "@/components/LinkViews/LinkComponents/LinkActions"; +import LinkDate from "@/components/LinkViews/LinkComponents/LinkDate"; +import LinkCollection from "@/components/LinkViews/LinkComponents/LinkCollection"; +import LinkIcon from "@/components/LinkViews/LinkComponents/LinkIcon"; +import Link from "next/link"; + +type Props = { + link: LinkIncludingShortenedCollectionAndTags; + count: number; + className?: string; +}; + +export default function LinkCardGrid({ link, count, className }: Props) { + const { collections } = useCollectionStore(); + + const { links } = useLinkStore(); + + let shortendURL; + + try { + shortendURL = new URL(link.url || "").host.toLowerCase(); + } catch (error) { + console.log(error); + } + + const [collection, setCollection] = + useState( + collections.find( + (e) => e.id === link.collection.id + ) as CollectionIncludingMembersAndLinkCount + ); + + useEffect(() => { + setCollection( + collections.find( + (e) => e.id === link.collection.id + ) as CollectionIncludingMembersAndLinkCount + ); + }, [collections, links]); + + return ( +
+
link.url && window.open(link.url || "", "_blank")} + className="flex items-center cursor-pointer p-3" + > +
+ +
+ +
+

+ {unescapeString(link.name || link.description) || link.url} +

+ +
+
+ + · + {link.url ? ( +
{ + e.preventDefault(); + window.open(link.url || "", "_blank"); + }} + className="flex items-center hover:opacity-60 cursor-pointer duration-100" + > +

{shortendURL}

+
+ ) : ( +
+ {link.type} +
+ )} +
+ · + +
+

{unescapeString(link.description)}

+ {link.tags[0] ? ( +
+
+ {link.tags.map((e, i) => ( + { + e.stopPropagation(); + }} + className="btn btn-xs btn-ghost truncate max-w-[19rem]" + > + #{e.name} + + ))} +
+
+ ) : undefined} +
+
+ + +
+ ); +} diff --git a/components/LinkViews/LinkComponents/LinkCollection.tsx b/components/LinkViews/LinkComponents/LinkCollection.tsx new file mode 100644 index 0000000..16c9bb5 --- /dev/null +++ b/components/LinkViews/LinkComponents/LinkCollection.tsx @@ -0,0 +1,34 @@ +import { + CollectionIncludingMembersAndLinkCount, + LinkIncludingShortenedCollectionAndTags, +} from "@/types/global"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faFolder } from "@fortawesome/free-solid-svg-icons"; +import { useRouter } from "next/router"; + +export default function LinkCollection({ + link, + collection, +}: { + link: LinkIncludingShortenedCollectionAndTags; + collection: CollectionIncludingMembersAndLinkCount; +}) { + const router = useRouter(); + + return ( +
{ + e.stopPropagation(); + router.push(`/collections/${link.collection.id}`); + }} + className="flex items-center gap-1 max-w-full w-fit hover:opacity-70 duration-100" + > + +

{collection?.name}

+
+ ); +} diff --git a/components/LinkViews/LinkComponents/LinkDate.tsx b/components/LinkViews/LinkComponents/LinkDate.tsx new file mode 100644 index 0000000..282ab70 --- /dev/null +++ b/components/LinkViews/LinkComponents/LinkDate.tsx @@ -0,0 +1,25 @@ +import { LinkIncludingShortenedCollectionAndTags } from "@/types/global"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faCalendarDays } from "@fortawesome/free-regular-svg-icons"; + +export default function LinkDate({ + link, +}: { + link: LinkIncludingShortenedCollectionAndTags; +}) { + const formattedDate = new Date(link.createdAt as string).toLocaleString( + "en-US", + { + year: "numeric", + month: "short", + day: "numeric", + }, + ); + + return ( +
+ +

{formattedDate}

+
+ ); +} diff --git a/components/LinkViews/LinkComponents/LinkIcon.tsx b/components/LinkViews/LinkComponents/LinkIcon.tsx new file mode 100644 index 0000000..f09149c --- /dev/null +++ b/components/LinkViews/LinkComponents/LinkIcon.tsx @@ -0,0 +1,44 @@ +import { LinkIncludingShortenedCollectionAndTags } from "@/types/global"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faFileImage, faFilePdf } from "@fortawesome/free-regular-svg-icons"; +import Image from "next/image"; +import isValidUrl from "@/lib/shared/isValidUrl"; + +export default function LinkIcon({ + link, + width, +}: { + link: LinkIncludingShortenedCollectionAndTags; + width?: string; +}) { + const url = + isValidUrl(link.url || "") && link.url ? new URL(link.url) : undefined; + + const iconClasses: string = + "bg-white text-primary shadow rounded-md border-[2px] border-white select-none z-10" + + " " + + (width || "w-12"); + + return ( +
+ {link.url && url ? ( + { + const target = e.target as HTMLElement; + target.style.display = "none"; + }} + /> + ) : link.type === "pdf" ? ( + + ) : link.type === "image" ? ( + + ) : undefined} +
+ ); +} diff --git a/components/LinkViews/LinkComponents/LinkRow.tsx b/components/LinkViews/LinkComponents/LinkRow.tsx new file mode 100644 index 0000000..ea01c57 --- /dev/null +++ b/components/LinkViews/LinkComponents/LinkRow.tsx @@ -0,0 +1,92 @@ +import { + CollectionIncludingMembersAndLinkCount, + LinkIncludingShortenedCollectionAndTags, +} from "@/types/global"; +import { useEffect, useState } from "react"; +import useLinkStore from "@/store/links"; +import useCollectionStore from "@/store/collections"; +import unescapeString from "@/lib/client/unescapeString"; +import LinkActions from "@/components/LinkViews/LinkComponents/LinkActions"; +import LinkDate from "@/components/LinkViews/LinkComponents/LinkDate"; +import LinkCollection from "@/components/LinkViews/LinkComponents/LinkCollection"; +import LinkIcon from "@/components/LinkViews/LinkComponents/LinkIcon"; + +type Props = { + link: LinkIncludingShortenedCollectionAndTags; + count: number; + className?: string; +}; + +export default function LinkCardCompact({ link, count, className }: Props) { + const { collections } = useCollectionStore(); + + const { links } = useLinkStore(); + + let shortendURL; + + try { + shortendURL = new URL(link.url || "").host.toLowerCase(); + } catch (error) { + console.log(error); + } + + const [collection, setCollection] = + useState( + collections.find( + (e) => e.id === link.collection.id + ) as CollectionIncludingMembersAndLinkCount + ); + + useEffect(() => { + setCollection( + collections.find( + (e) => e.id === link.collection.id + ) as CollectionIncludingMembersAndLinkCount + ); + }, [collections, links]); + + return ( + <> +
+
link.url && window.open(link.url || "", "_blank")} + className="flex items-center cursor-pointer py-3 sm:px-3" + > +
+ +
+ +
+

+ {unescapeString(link.name || link.description) || link.url} +

+ +
+
+ + · + {link.url ? ( +

{shortendURL}

+ ) : ( +
+ {link.type} +
+ )} +
+ · + +
+
+
+ + +
+ +
+ + ); +} diff --git a/components/LinkViews/ListView.tsx b/components/LinkViews/ListView.tsx new file mode 100644 index 0000000..8b08add --- /dev/null +++ b/components/LinkViews/ListView.tsx @@ -0,0 +1,16 @@ +import LinkRow from "@/components/LinkViews/LinkComponents/LinkRow"; +import { LinkIncludingShortenedCollectionAndTags } from "@/types/global"; + +export default function ListView({ + links, +}: { + links: LinkIncludingShortenedCollectionAndTags[]; +}) { + return ( +
+ {links.map((e, i) => { + return ; + })} +
+ ); +} diff --git a/components/SortDropdown.tsx b/components/SortDropdown.tsx index 7aebd56..e125e6b 100644 --- a/components/SortDropdown.tsx +++ b/components/SortDropdown.tsx @@ -16,11 +16,14 @@ export default function SortDropdown({ sortBy, setSort }: Props) { role="button" className="btn btn-sm btn-square btn-ghost" > - + viewBox="0 0 24 24" + fill="none" + stroke="currentColor" + > + +
  • diff --git a/components/ViewDropdown.tsx b/components/ViewDropdown.tsx new file mode 100644 index 0000000..2be399a --- /dev/null +++ b/components/ViewDropdown.tsx @@ -0,0 +1,73 @@ +import React, { Dispatch, SetStateAction, useEffect, useState } from "react"; +import useLocalSettingsStore from "@/store/localSettings"; + +import { ViewMode } from "@/types/global"; + +type Props = { + viewMode: string; + setViewMode: Dispatch>; +}; + +export default function ViewDropdown({ viewMode, setViewMode }: Props) { + const { updateSettings } = useLocalSettingsStore(); + + const onChangeViewMode = ( + e: React.MouseEvent, + viewMode: ViewMode + ) => { + setViewMode(viewMode); + }; + + useEffect(() => { + updateSettings({ viewMode: viewMode as ViewMode }); + }, [viewMode]); + + return ( +
    + + + + + {/* */} +
    + ); +} diff --git a/pages/collections/[id].tsx b/pages/collections/[id].tsx index 08ff8dc..1ac2a9a 100644 --- a/pages/collections/[id].tsx +++ b/pages/collections/[id].tsx @@ -1,7 +1,11 @@ -import LinkCard from "@/components/LinkCard"; +import LinkCard from "@/components/LinkViews/LinkComponents/LinkCard"; import useCollectionStore from "@/store/collections"; import useLinkStore from "@/store/links"; -import { CollectionIncludingMembersAndLinkCount, Sort } from "@/types/global"; +import { + CollectionIncludingMembersAndLinkCount, + Sort, + ViewMode, +} from "@/types/global"; import { faEllipsis, faFolder } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { useRouter } from "next/router"; @@ -18,6 +22,10 @@ import getPublicUserData from "@/lib/client/getPublicUserData"; import EditCollectionModal from "@/components/ModalContent/EditCollectionModal"; import EditCollectionSharingModal from "@/components/ModalContent/EditCollectionSharingModal"; import DeleteCollectionModal from "@/components/ModalContent/DeleteCollectionModal"; +import ViewDropdown from "@/components/ViewDropdown"; +import DefaultView from "@/components/LinkViews/DefaultView"; +import GridView from "@/components/LinkViews/GridView"; +import ListView from "@/components/LinkViews/ListView"; export default function Index() { const { settings } = useLocalSettingsStore(); @@ -76,6 +84,19 @@ export default function Index() { useState(false); const [deleteCollectionModal, setDeleteCollectionModal] = useState(false); + const [viewMode, setViewMode] = useState( + localStorage.getItem("viewMode") || ViewMode.Default + ); + + const linkView = { + [ViewMode.Default]: DefaultView, + // [ViewMode.Grid]: GridView, + [ViewMode.List]: ListView, + }; + + // @ts-ignore + const LinkComponent = linkView[viewMode]; + return (
    -
    - {activeCollection && ( -
    -
    - -

    - {activeCollection?.name} -

    -
    + {activeCollection && ( +
    +
    + +

    + {activeCollection?.name} +

    - )} -
    + +
    +
    + +
    +
      + {permissions === true ? ( +
    • +
      { + (document?.activeElement as HTMLElement)?.blur(); + setEditCollectionModal(true); + }} + > + Edit Collection Info +
      +
    • + ) : undefined} +
    • +
      { + (document?.activeElement as HTMLElement)?.blur(); + setEditCollectionSharingModal(true); + }} + > + {permissions === true + ? "Share and Collaborate" + : "View Team"} +
      +
    • +
    • +
      { + (document?.activeElement as HTMLElement)?.blur(); + setDeleteCollectionModal(true); + }} + > + {permissions === true + ? "Delete Collection" + : "Leave Collection"} +
      +
    • +
    +
    +
    + )} {activeCollection ? (
    @@ -158,76 +235,16 @@ export default function Index() {

    Showing {activeCollection?._count?.links} results

    -
    -
    -
    - -
    -
      - {permissions === true ? ( -
    • -
      { - (document?.activeElement as HTMLElement)?.blur(); - setEditCollectionModal(true); - }} - > - Edit Collection Info -
      -
    • - ) : undefined} -
    • -
      { - (document?.activeElement as HTMLElement)?.blur(); - setEditCollectionSharingModal(true); - }} - > - {permissions === true - ? "Share and Collaborate" - : "View Team"} -
      -
    • -
    • -
      { - (document?.activeElement as HTMLElement)?.blur(); - setDeleteCollectionModal(true); - }} - > - {permissions === true - ? "Delete Collection" - : "Leave Collection"} -
      -
    • -
    -
    -
    +
    {links.some((e) => e.collectionId === Number(router.query.id)) ? ( -
    - {links - .filter((e) => e.collection.id === activeCollection?.id) - .map((e, i) => { - return ; - })} -
    + e.collection.id === activeCollection?.id + )} + /> ) : ( )} diff --git a/pages/collections/index.tsx b/pages/collections/index.tsx index 05891fe..349a223 100644 --- a/pages/collections/index.tsx +++ b/pages/collections/index.tsx @@ -62,7 +62,9 @@ export default function Collections() { Other Collections

    -

    Shared collections you're a member of

    +

    + Shared collections you're a member of +

    diff --git a/pages/dashboard.tsx b/pages/dashboard.tsx index 0297941..352cff9 100644 --- a/pages/dashboard.tsx +++ b/pages/dashboard.tsx @@ -3,6 +3,7 @@ import useCollectionStore from "@/store/collections"; import useTagStore from "@/store/tags"; import MainLayout from "@/layouts/MainLayout"; import LinkCard from "@/components/LinkCard"; +import LinkCard from "@/components/LinkViews/LinkComponents/LinkCard"; import { useEffect, useState } from "react"; import useLinks from "@/hooks/useLinks"; import Link from "next/link"; @@ -95,7 +96,6 @@ export default function Dashboard() {
    -
    ( + localStorage.getItem("viewMode") || ViewMode.Default + ); const [sortBy, setSortBy] = useState(Sort.DateNewestFirst); useLinks({ sort: sortBy }); + const linkView = { + [ViewMode.Default]: DefaultView, + // [ViewMode.Grid]: GridView, + [ViewMode.List]: ListView, + }; + + // @ts-ignore + const LinkComponent = linkView[viewMode]; + return (
    @@ -23,19 +44,15 @@ export default function Links() { title={"All Links"} description={"Links from every Collections"} /> -
    +
    {links[0] ? ( -
    - {links.map((e, i) => { - return ; - })} -
    + ) : ( )} diff --git a/pages/links/pinned.tsx b/pages/links/pinned.tsx index a4a70f8..81a7252 100644 --- a/pages/links/pinned.tsx +++ b/pages/links/pinned.tsx @@ -1,4 +1,4 @@ -import LinkCard from "@/components/LinkCard"; +import LinkCard from "@/components/LinkViews/LinkComponents/LinkCard"; import SortDropdown from "@/components/SortDropdown"; import useLinks from "@/hooks/useLinks"; import MainLayout from "@/layouts/MainLayout"; @@ -6,14 +6,34 @@ import useLinkStore from "@/store/links"; import { Sort } from "@/types/global"; import React, { useState } from "react"; import PageHeader from "@/components/PageHeader"; +import { Sort, ViewMode } from "@/types/global"; +import { faThumbTack } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { useState } from "react"; +import ViewDropdown from "@/components/ViewDropdown"; +import DefaultView from "@/components/LinkViews/DefaultView"; +import GridView from "@/components/LinkViews/GridView"; +import ListView from "@/components/LinkViews/ListView"; export default function PinnedLinks() { const { links } = useLinkStore(); + const [viewMode, setViewMode] = useState( + localStorage.getItem("viewMode") || ViewMode.Default + ); const [sortBy, setSortBy] = useState(Sort.DateNewestFirst); useLinks({ sort: sortBy, pinnedOnly: true }); + const linkView = { + [ViewMode.Default]: DefaultView, + // [ViewMode.Grid]: GridView, + [ViewMode.List]: ListView, + }; + + // @ts-ignore + const LinkComponent = linkView[viewMode]; + return (
    @@ -25,14 +45,11 @@ export default function PinnedLinks() {
    +
    {links.some((e) => e.pinnedBy && e.pinnedBy[0]) ? ( -
    - {links.map((e, i) => { - return ; - })} -
    + ) : (
    ( + localStorage.getItem("viewMode") || ViewMode.Default + ); const [sortBy, setSortBy] = useState(Sort.DateNewestFirst); useLinks({ @@ -36,15 +44,24 @@ export default function Search() { searchByTags: searchFilter.tags, }); + const linkView = { + [ViewMode.Default]: DefaultView, + // [ViewMode.Grid]: GridView, + [ViewMode.List]: ListView, + }; + + // @ts-ignore + const LinkComponent = linkView[viewMode]; + return (
    -
    -
    +
    +

    Search Results @@ -52,25 +69,17 @@ export default function Search() {

    -
    -
    - -
    - -
    - -
    +
    + + +
    {links[0] ? ( -
    - {links.map((e, i) => { - return ; - })} -
    + ) : (

    Nothing found.{" "} diff --git a/pages/tags/[id].tsx b/pages/tags/[id].tsx index 29c417f..e1d92da 100644 --- a/pages/tags/[id].tsx +++ b/pages/tags/[id].tsx @@ -1,4 +1,4 @@ -import LinkCard from "@/components/LinkCard"; +import LinkCard from "@/components/LinkViews/LinkComponents/LinkCard"; import useLinkStore from "@/store/links"; import { faCheck, @@ -12,10 +12,13 @@ import { FormEvent, useEffect, useState } from "react"; import MainLayout from "@/layouts/MainLayout"; import useTagStore from "@/store/tags"; import SortDropdown from "@/components/SortDropdown"; -import { Sort, TagIncludingLinkCount } from "@/types/global"; +import { Sort, TagIncludingLinkCount, ViewMode } from "@/types/global"; import useLinks from "@/hooks/useLinks"; -import Dropdown from "@/components/Dropdown"; import { toast } from "react-hot-toast"; +import ViewDropdown from "@/components/ViewDropdown"; +import DefaultView from "@/components/LinkViews/DefaultView"; +import GridView from "@/components/LinkViews/GridView"; +import ListView from "@/components/LinkViews/ListView"; export default function Index() { const router = useRouter(); @@ -25,8 +28,6 @@ export default function Index() { const [sortBy, setSortBy] = useState(Sort.DateNewestFirst); - const [expandDropdown, setExpandDropdown] = useState(false); - const [renameTag, setRenameTag] = useState(false); const [newTagName, setNewTagName] = useState(); @@ -97,6 +98,19 @@ export default function Index() { setRenameTag(false); }; + const [viewMode, setViewMode] = useState( + localStorage.getItem("viewMode") || ViewMode.Default + ); + + const linkView = { + [ViewMode.Default]: DefaultView, + // [ViewMode.Grid]: GridView, + [ViewMode.List]: ListView, + }; + + // @ts-ignore + const LinkComponent = linkView[viewMode]; + return (

    @@ -105,7 +119,7 @@ export default function Index() {
    {renameTag ? ( <> @@ -147,7 +161,13 @@ export default function Index() { {activeTag?.name}

    -
    +
    8 + ? "dropdown-end" + : "" + }`} + >
- - {expandDropdown ? ( - { - setRenameTag(true); - setExpandDropdown(false); - }, - }, - { - name: "Remove Tag", - onClick: () => { - remove(); - setExpandDropdown(false); - }, - }, - ]} - onClickOutside={(e: Event) => { - const target = e.target as HTMLInputElement; - if (target.id !== "expand-dropdown") - setExpandDropdown(false); - }} - className="absolute top-8 left-0 w-36 font-normal" - /> - ) : null} )} -
+
+
-
- {links - .filter((e) => e.tags.some((e) => e.id === Number(router.query.id))) - .map((e, i) => { - return ; - })} -
+ + e.tags.some((e) => e.id === Number(router.query.id)) + )} + /> ); diff --git a/store/localSettings.ts b/store/localSettings.ts index 1ddcdcc..9ef126d 100644 --- a/store/localSettings.ts +++ b/store/localSettings.ts @@ -1,7 +1,9 @@ import { create } from "zustand"; +import {ViewMode} from "@/types/global"; type LocalSettings = { - theme: string; + theme?: string; + viewMode?: string }; type LocalSettingsStore = { @@ -13,6 +15,7 @@ type LocalSettingsStore = { const useLocalSettingsStore = create((set) => ({ settings: { theme: "", + viewMode: "", }, updateSettings: async (newSettings) => { if ( @@ -26,6 +29,15 @@ const useLocalSettingsStore = create((set) => ({ document.querySelector("html")?.setAttribute("data-theme", localTheme); } + if ( + newSettings.viewMode && + newSettings.viewMode !== localStorage.getItem("viewMode") + ) { + localStorage.setItem("viewMode", newSettings.viewMode); + + // const localTheme = localStorage.getItem("viewMode") || ""; + } + set((state) => ({ settings: { ...state.settings, ...newSettings } })); }, setSettings: async () => { diff --git a/types/global.ts b/types/global.ts index c1d1893..49505bd 100644 --- a/types/global.ts +++ b/types/global.ts @@ -57,6 +57,12 @@ export interface PublicCollectionIncludingLinks extends Collection { links: LinksIncludingTags[]; } +export enum ViewMode { + Default = "default", + Grid = "grid", + List = "list", +} + export enum Sort { DateNewestFirst, DateOldestFirst,