From c18a5f41621f977541fe6e2655e29ce4622dc7fd Mon Sep 17 00:00:00 2001 From: daniel31x13 Date: Sun, 18 Aug 2024 02:55:59 -0400 Subject: [PATCH] added details drawer --- components/CopyButton.tsx | 36 +++ components/DashboardItem.tsx | 7 +- components/Drawer.tsx | 88 +++++ components/LinkDetails.tsx | 302 ++++++++++++++++++ .../LinkViews/LinkComponents/LinkActions.tsx | 223 +++++++------ .../LinkViews/LinkComponents/LinkCard.tsx | 62 +--- .../LinkViews/LinkComponents/LinkList.tsx | 20 +- .../LinkViews/LinkComponents/LinkMasonry.tsx | 57 +--- components/Modal.tsx | 2 +- .../EditCollectionSharingModal.tsx | 17 +- components/ModalContent/LinkDetailModal.tsx | 145 +++++++++ components/ModalContent/NewLinkModal.tsx | 2 +- .../ModalContent/PreservedFormatsModal.tsx | 6 +- components/ModalContent/UploadFileModal.tsx | 2 +- components/PreserverdFormatRow.tsx | 12 +- components/ReadableView.tsx | 21 +- hooks/store/links.tsx | 31 +- package.json | 4 +- pages/dashboard.tsx | 8 +- pages/links/[id].tsx | 41 +++ pages/preserved/[id].tsx | 2 +- pages/public/links/[id].tsx | 41 +++ pages/public/preserved/[id].tsx | 17 +- public/locales/en/common.json | 4 +- yarn.lock | 41 ++- 25 files changed, 881 insertions(+), 310 deletions(-) create mode 100644 components/CopyButton.tsx create mode 100644 components/Drawer.tsx create mode 100644 components/LinkDetails.tsx create mode 100644 components/ModalContent/LinkDetailModal.tsx create mode 100644 pages/links/[id].tsx create mode 100644 pages/public/links/[id].tsx diff --git a/components/CopyButton.tsx b/components/CopyButton.tsx new file mode 100644 index 0000000..090d64b --- /dev/null +++ b/components/CopyButton.tsx @@ -0,0 +1,36 @@ +import React from "react"; + +type Props = { + text: string; +}; + +const CopyButton = ({ text }: Props) => { + return ( +
{ + try { + navigator.clipboard.writeText(text).then(() => { + const copyIcon = document.querySelector(".bi-copy"); + if (copyIcon) { + copyIcon.classList.remove("bi-copy"); + copyIcon.classList.add("bi-check2"); + copyIcon.classList.add("text-success"); + } + setTimeout(() => { + if (copyIcon) { + copyIcon.classList.remove("bi-check2"); + copyIcon.classList.remove("text-success"); + copyIcon.classList.add("bi-copy"); + } + }, 1000); + }); + } catch (err) { + console.log(err); + } + }} + >
+ ); +}; + +export default CopyButton; diff --git a/components/DashboardItem.tsx b/components/DashboardItem.tsx index 337fc36..d255c4b 100644 --- a/components/DashboardItem.tsx +++ b/components/DashboardItem.tsx @@ -14,7 +14,12 @@ export default function dashboardItem({

{name}

-

{value}

+

+ {value < 1000 ? value : (value / 1000).toFixed(1) + "k"} +

+

+ {value} +

); diff --git a/components/Drawer.tsx b/components/Drawer.tsx new file mode 100644 index 0000000..12f931e --- /dev/null +++ b/components/Drawer.tsx @@ -0,0 +1,88 @@ +import React, { ReactNode, useEffect } from "react"; +import ClickAwayHandler from "@/components/ClickAwayHandler"; +import { Drawer as D } from "vaul"; + +type Props = { + toggleDrawer: Function; + children: ReactNode; + className?: string; + dismissible?: boolean; +}; + +export default function Drawer({ + toggleDrawer, + className, + children, + dismissible = true, +}: Props) { + const [drawerIsOpen, setDrawerIsOpen] = React.useState(true); + + useEffect(() => { + if (window.innerWidth >= 640) { + document.body.style.overflow = "hidden"; + document.body.style.position = "relative"; + return () => { + document.body.style.overflow = "auto"; + document.body.style.position = ""; + }; + } + }, []); + + if (window.innerWidth < 640) { + return ( + dismissible && setTimeout(() => toggleDrawer(), 350)} + dismissible={dismissible} + > + + + dismissible && setDrawerIsOpen(false)} + > + +
+
+ {children} +
+ + + + + ); + } else { + return ( + dismissible && setTimeout(() => toggleDrawer(), 350)} + dismissible={dismissible} + direction="right" + > + + + dismissible && setDrawerIsOpen(false)} + className="z-30" + > + +
+ {children} +
+
+
+
+
+ ); + } +} diff --git a/components/LinkDetails.tsx b/components/LinkDetails.tsx new file mode 100644 index 0000000..e489078 --- /dev/null +++ b/components/LinkDetails.tsx @@ -0,0 +1,302 @@ +import React, { useEffect, useState } from "react"; +import { + LinkIncludingShortenedCollectionAndTags, + ArchivedFormat, +} from "@/types/global"; +import Link from "next/link"; +import { useSession } from "next-auth/react"; +import { + pdfAvailable, + readabilityAvailable, + monolithAvailable, + screenshotAvailable, +} from "@/lib/shared/getArchiveValidity"; +import PreservedFormatRow from "@/components/PreserverdFormatRow"; +import getPublicUserData from "@/lib/client/getPublicUserData"; +import { useTranslation } from "next-i18next"; +import { BeatLoader } from "react-spinners"; +import { useUser } from "@/hooks/store/user"; +import { useGetLink } from "@/hooks/store/links"; +import LinkIcon from "./LinkViews/LinkComponents/LinkIcon"; +import CopyButton from "./CopyButton"; +import { useRouter } from "next/router"; + +type Props = { + className?: string; + link: LinkIncludingShortenedCollectionAndTags; +}; + +export default function LinkDetails({ className, link }: Props) { + const { t } = useTranslation(); + const session = useSession(); + const getLink = useGetLink(); + const { data: user = {} } = useUser(); + + const [collectionOwner, setCollectionOwner] = useState({ + id: null as unknown as number, + name: "", + username: "", + image: "", + archiveAsScreenshot: undefined as unknown as boolean, + archiveAsMonolith: undefined as unknown as boolean, + archiveAsPDF: undefined as unknown as boolean, + }); + + useEffect(() => { + const fetchOwner = async () => { + if (link.collection.ownerId !== user.id) { + const owner = await getPublicUserData( + link.collection.ownerId as number + ); + setCollectionOwner(owner); + } else if (link.collection.ownerId === user.id) { + setCollectionOwner({ + id: user.id as number, + name: user.name, + username: user.username as string, + image: user.image as string, + archiveAsScreenshot: user.archiveAsScreenshot as boolean, + archiveAsMonolith: user.archiveAsScreenshot as boolean, + archiveAsPDF: user.archiveAsPDF as boolean, + }); + } + }; + + fetchOwner(); + }, [link.collection.ownerId]); + + const isReady = () => { + return ( + link && + (collectionOwner.archiveAsScreenshot === true + ? link.pdf && link.pdf !== "pending" + : true) && + (collectionOwner.archiveAsMonolith === true + ? link.monolith && link.monolith !== "pending" + : true) && + (collectionOwner.archiveAsPDF === true + ? link.pdf && link.pdf !== "pending" + : true) && + link.readable && + link.readable !== "pending" + ); + }; + + const atLeastOneFormatAvailable = () => { + return ( + screenshotAvailable(link) || + pdfAvailable(link) || + readabilityAvailable(link) || + monolithAvailable(link) + ); + }; + + useEffect(() => { + (async () => { + await getLink.mutateAsync({ + id: link.id as number, + }); + })(); + + let interval: any; + + if (!isReady()) { + interval = setInterval(async () => { + await getLink.mutateAsync({ + id: link.id as number, + }); + }, 5000); + } else { + if (interval) { + clearInterval(interval); + } + } + + return () => { + if (interval) { + clearInterval(interval); + } + }; + }, [link?.monolith]); + + const router = useRouter(); + + const isPublicRoute = router.pathname.startsWith("/public") ? true : false; + + return ( +
+ + + {link.name &&

{link.name}

} + + {link.url && ( + <> +
+ +

{t("link")}

+ +
+ + {link.url} + + +
+ + )} + +
+ +

{t("collection")}

+ + +

{link.collection.name}

+ + + + {link.tags[0] && ( + <> +
+ +
+

{t("tags")}

+
+ +
+ {link.tags.map((tag) => + isPublicRoute ? ( +
+ {tag.name} +
+ ) : ( + + {tag.name} + + ) + )} +
+ + )} + + {link.description && ( + <> +
+ +
+

{t("notes")}

+ +
+

{link.description}

+
+
+ + )} + +
+ +

+ {link.url ? t("preserved_formats") : t("file")} +

+ +
+ {monolithAvailable(link) ? ( + + ) : undefined} + + {screenshotAvailable(link) ? ( + + ) : undefined} + + {pdfAvailable(link) ? ( + + ) : undefined} + + {readabilityAvailable(link) ? ( + + ) : undefined} + + {!isReady() && !atLeastOneFormatAvailable() ? ( +
+ + +

{t("preservation_in_queue")}

+

{t("check_back_later")}

+
+ ) : link.url && !isReady() && atLeastOneFormatAvailable() ? ( +
+ +

{t("there_are_more_formats")}

+

{t("check_back_later")}

+
+ ) : undefined} + + {link.url && ( + +

{t("view_latest_snapshot")}

+ + + )} +
+
+ ); +} diff --git a/components/LinkViews/LinkComponents/LinkActions.tsx b/components/LinkViews/LinkComponents/LinkActions.tsx index 3abc06d..837cd6f 100644 --- a/components/LinkViews/LinkComponents/LinkActions.tsx +++ b/components/LinkViews/LinkComponents/LinkActions.tsx @@ -12,22 +12,20 @@ import { useTranslation } from "next-i18next"; import { useUser } from "@/hooks/store/user"; import { useDeleteLink, useUpdateLink } from "@/hooks/store/links"; import toast from "react-hot-toast"; +import LinkDetailModal from "@/components/ModalContent/LinkDetailModal"; +import { useRouter } from "next/router"; type Props = { link: LinkIncludingShortenedCollectionAndTags; collection: CollectionIncludingMembersAndLinkCount; position?: string; - toggleShowInfo?: () => void; - linkInfo?: boolean; alignToTop?: boolean; flipDropdown?: boolean; }; export default function LinkActions({ link, - toggleShowInfo, position, - linkInfo, alignToTop, flipDropdown, }: Props) { @@ -36,6 +34,7 @@ export default function LinkActions({ const permissions = usePermissions(link.collection.id as number); const [editLinkModal, setEditLinkModal] = useState(false); + const [linkDetailModal, setLinkDetailModal] = useState(false); const [deleteLinkModal, setDeleteLinkModal] = useState(false); const [preservedFormatsModal, setPreservedFormatsModal] = useState(false); @@ -70,119 +69,137 @@ export default function LinkActions({ ); }; + const router = useRouter(); + + const isPublicRoute = router.pathname.startsWith("/public") ? true : false; + return ( <> -
+ {isPublicRoute ? (
setLinkDetailModal(true)} > - +
+ +
-
    -
  • -
    { - (document?.activeElement as HTMLElement)?.blur(); - pinLink(); - }} - className="whitespace-nowrap" - > - {link?.pinnedBy && link.pinnedBy[0] - ? t("unpin") - : t("pin_to_dashboard")} -
    -
  • - {linkInfo !== undefined && toggleShowInfo ? ( +
    + +
    +
      + {permissions === true || + (permissions?.canUpdate && ( +
    • +
      { + (document?.activeElement as HTMLElement)?.blur(); + pinLink(); + }} + className="whitespace-nowrap" + > + {link?.pinnedBy && link.pinnedBy[0] + ? t("unpin") + : t("pin_to_dashboard")} +
      +
    • + ))}
    • { (document?.activeElement as HTMLElement)?.blur(); - toggleShowInfo(); + setLinkDetailModal(true); }} className="whitespace-nowrap" > - {!linkInfo ? t("show_link_details") : t("hide_link_details")} + {t("show_link_details")}
    • - ) : undefined} - {permissions === true || permissions?.canUpdate ? ( -
    • -
      { - (document?.activeElement as HTMLElement)?.blur(); - setEditLinkModal(true); - }} - className="whitespace-nowrap" - > - {t("edit_link")} -
      -
    • - ) : undefined} - {link.type === "url" && ( -
    • -
      { - (document?.activeElement as HTMLElement)?.blur(); - setPreservedFormatsModal(true); - }} - className="whitespace-nowrap" - > - {t("preserved_formats")} -
      -
    • - )} - {permissions === true || permissions?.canDelete ? ( -
    • -
      { - (document?.activeElement as HTMLElement)?.blur(); - e.shiftKey - ? async () => { - const load = toast.loading(t("deleting")); + {permissions === true || permissions?.canUpdate ? ( +
    • +
      { + (document?.activeElement as HTMLElement)?.blur(); + setEditLinkModal(true); + }} + className="whitespace-nowrap" + > + {t("edit_link")} +
      +
    • + ) : undefined} + {link.type === "url" && ( +
    • +
      { + (document?.activeElement as HTMLElement)?.blur(); + setPreservedFormatsModal(true); + }} + className="whitespace-nowrap" + > + {t("preserved_formats")} +
      +
    • + )} + {permissions === true || permissions?.canDelete ? ( +
    • +
      { + (document?.activeElement as HTMLElement)?.blur(); + e.shiftKey + ? async () => { + const load = toast.loading(t("deleting")); - await deleteLink.mutateAsync(link.id as number, { - onSettled: (data, error) => { - toast.dismiss(load); + await deleteLink.mutateAsync(link.id as number, { + onSettled: (data, error) => { + toast.dismiss(load); - if (error) { - toast.error(error.message); - } else { - toast.success(t("deleted")); - } - }, - }); - } - : setDeleteLinkModal(true); - }} - className="whitespace-nowrap" - > - {t("delete")} -
      -
    • - ) : undefined} -
    -
+ if (error) { + toast.error(error.message); + } else { + toast.success(t("deleted")); + } + }, + }); + } + : setDeleteLinkModal(true); + }} + className="whitespace-nowrap" + > + {t("delete")} +
+ + ) : undefined} + + + )} {editLinkModal ? ( ) : undefined} - {/* {expandedLink ? ( - setExpandedLink(false)} link={link} /> - ) : undefined} */} + {linkDetailModal ? ( + setLinkDetailModal(false)} + onEdit={() => setEditLinkModal(true)} + link={link} + /> + ) : undefined} ); } diff --git a/components/LinkViews/LinkComponents/LinkCard.tsx b/components/LinkViews/LinkComponents/LinkCard.tsx index e228fb1..c6b60c5 100644 --- a/components/LinkViews/LinkComponents/LinkCard.tsx +++ b/components/LinkViews/LinkComponents/LinkCard.tsx @@ -22,6 +22,7 @@ import { useTranslation } from "next-i18next"; import { useCollections } from "@/hooks/store/collections"; import { useUser } from "@/hooks/store/user"; import { useGetLink, useLinks } from "@/hooks/store/links"; +import { useRouter } from "next/router"; type Props = { link: LinkIncludingShortenedCollectionAndTags; @@ -90,6 +91,10 @@ export default function LinkCard({ link, flipDropdown, editMode }: Props) { const isVisible = useOnScreen(ref); const permissions = usePermissions(collection?.id as number); + const router = useRouter(); + + let isPublic = router.pathname.startsWith("/public") ? true : undefined; + useEffect(() => { let interval: any; @@ -99,7 +104,7 @@ export default function LinkCard({ link, flipDropdown, editMode }: Props) { link.preview !== "unavailable" ) { interval = setInterval(async () => { - getLink.mutateAsync(link.id as number); + getLink.mutateAsync({ id: link.id as number, isPublicRoute: isPublic }); }, 5000); } @@ -110,8 +115,6 @@ export default function LinkCard({ link, flipDropdown, editMode }: Props) { }; }, [isVisible, link.preview]); - const [showInfo, setShowInfo] = useState(false); - const selectedStyle = selectedLinks.some( (selectedLink) => selectedLink.id === link.id ) @@ -196,63 +199,10 @@ export default function LinkCard({ link, flipDropdown, editMode }: Props) { - {showInfo && ( -
-
setShowInfo(!showInfo)} - className=" float-right btn btn-sm outline-none btn-circle btn-ghost z-10" - > - -
-

- {t("description")} -

- -
-

- {link.description ? ( - unescapeString(link.description) - ) : ( - - {t("no_description")} - - )} -

- {link.tags && link.tags[0] && ( - <> -

- {t("tags")} -

- -
- -
-
- {link.tags.map((e, i) => ( - { - e.stopPropagation(); - }} - className="btn btn-xs btn-ghost truncate max-w-[19rem]" - > - #{e.name} - - ))} -
-
- - )} -
- )} - setShowInfo(!showInfo)} - linkInfo={showInfo} flipDropdown={flipDropdown} /> diff --git a/components/LinkViews/LinkComponents/LinkList.tsx b/components/LinkViews/LinkComponents/LinkList.tsx index d3c8d40..59e6e2d 100644 --- a/components/LinkViews/LinkComponents/LinkList.tsx +++ b/components/LinkViews/LinkComponents/LinkList.tsx @@ -80,8 +80,6 @@ export default function LinkCardCompact({ const permissions = usePermissions(collection?.id as number); - const [showInfo, setShowInfo] = useState(false); - const selectedStyle = selectedLinks.some( (selectedLink) => selectedLink.id === link.id ) @@ -96,7 +94,7 @@ export default function LinkCardCompact({ <>
selectable @@ -106,20 +104,6 @@ export default function LinkCardCompact({ : undefined } > - {/* {showCheckbox && - editMode && - (permissions === true || - permissions?.canCreate || - permissions?.canDelete) && ( - selectedLink.id === link.id - )} - onChange={() => handleCheckboxClick(link)} - /> - )} */}
@@ -157,8 +141,6 @@ export default function LinkCardCompact({ collection={collection} position="top-3 right-3" flipDropdown={flipDropdown} - // toggleShowInfo={() => setShowInfo(!showInfo)} - // linkInfo={showInfo} />
{ - getLink.mutateAsync(link.id as number); + getLink.mutateAsync({ id: link.id as number }); }, 5000); } @@ -107,8 +107,6 @@ export default function LinkMasonry({ link, flipDropdown, editMode }: Props) { }; }, [isVisible, link.preview]); - const [showInfo, setShowInfo] = useState(false); - const selectedStyle = selectedLinks.some( (selectedLink) => selectedLink.id === link.id ) @@ -207,57 +205,6 @@ export default function LinkMasonry({ link, flipDropdown, editMode }: Props) {
- {showInfo && ( -
-
setShowInfo(!showInfo)} - className=" float-right btn btn-sm outline-none btn-circle btn-ghost z-10" - > - -
-

- {t("description")} -

- -
-

- {link.description ? ( - unescapeString(link.description) - ) : ( - - {t("no_description")} - - )} -

- {link.tags && link.tags[0] && ( - <> -

- {t("tags")} -

- -
- -
-
- {link.tags.map((e, i) => ( - { - e.stopPropagation(); - }} - className="btn btn-xs btn-ghost truncate max-w-[19rem]" - > - #{e.name} - - ))} -
-
- - )} -
- )} - setShowInfo(!showInfo)} - linkInfo={showInfo} flipDropdown={flipDropdown} /> diff --git a/components/Modal.tsx b/components/Modal.tsx index 4691df7..2eb97e4 100644 --- a/components/Modal.tsx +++ b/components/Modal.tsx @@ -32,7 +32,7 @@ export default function Modal({ return ( dismissible && setTimeout(() => toggleModal(), 100)} + onClose={() => dismissible && setTimeout(() => toggleModal(), 350)} dismissible={dismissible} > diff --git a/components/ModalContent/EditCollectionSharingModal.tsx b/components/ModalContent/EditCollectionSharingModal.tsx index f004af4..321e672 100644 --- a/components/ModalContent/EditCollectionSharingModal.tsx +++ b/components/ModalContent/EditCollectionSharingModal.tsx @@ -11,6 +11,7 @@ import { dropdownTriggerer } from "@/lib/client/utils"; import { useTranslation } from "next-i18next"; import { useUpdateCollection } from "@/hooks/store/collections"; import { useUser } from "@/hooks/store/user"; +import CopyButton from "../CopyButton"; type Props = { onClose: Function; @@ -133,21 +134,11 @@ export default function EditCollectionSharingModal({ )} {collection.isPublic ? ( -
+

{t("sharable_link_guide")}

-
{ - try { - navigator.clipboard - .writeText(publicCollectionURL) - .then(() => toast.success(t("copied"))); - } catch (err) { - console.log(err); - } - }} - className="w-full hide-scrollbar overflow-x-auto whitespace-nowrap rounded-md p-2 bg-base-200 border-neutral-content border-solid border outline-none hover:border-primary dark:hover:border-primary duration-100 cursor-text" - > +
{publicCollectionURL} +
) : null} diff --git a/components/ModalContent/LinkDetailModal.tsx b/components/ModalContent/LinkDetailModal.tsx new file mode 100644 index 0000000..2e6308a --- /dev/null +++ b/components/ModalContent/LinkDetailModal.tsx @@ -0,0 +1,145 @@ +import React, { useEffect, useState } from "react"; +import { LinkIncludingShortenedCollectionAndTags } from "@/types/global"; +import getPublicUserData from "@/lib/client/getPublicUserData"; +import { useTranslation } from "next-i18next"; +import { useUser } from "@/hooks/store/user"; +import { useGetLink } from "@/hooks/store/links"; +import Drawer from "../Drawer"; +import LinkDetails from "../LinkDetails"; +import Link from "next/link"; +import usePermissions from "@/hooks/usePermissions"; +import { useRouter } from "next/router"; + +type Props = { + onClose: Function; + onEdit: Function; + link: LinkIncludingShortenedCollectionAndTags; +}; + +export default function LinkDetailModal({ onClose, onEdit, link }: Props) { + const { t } = useTranslation(); + const getLink = useGetLink(); + const { data: user = {} } = useUser(); + + const [collectionOwner, setCollectionOwner] = useState({ + id: null as unknown as number, + name: "", + username: "", + image: "", + archiveAsScreenshot: undefined as unknown as boolean, + archiveAsMonolith: undefined as unknown as boolean, + archiveAsPDF: undefined as unknown as boolean, + }); + + useEffect(() => { + const fetchOwner = async () => { + if (link.collection.ownerId !== user.id) { + const owner = await getPublicUserData( + link.collection.ownerId as number + ); + setCollectionOwner(owner); + } else if (link.collection.ownerId === user.id) { + setCollectionOwner({ + id: user.id as number, + name: user.name, + username: user.username as string, + image: user.image as string, + archiveAsScreenshot: user.archiveAsScreenshot as boolean, + archiveAsMonolith: user.archiveAsScreenshot as boolean, + archiveAsPDF: user.archiveAsPDF as boolean, + }); + } + }; + + fetchOwner(); + }, [link.collection.ownerId]); + + const isReady = () => { + return ( + link && + (collectionOwner.archiveAsScreenshot === true + ? link.pdf && link.pdf !== "pending" + : true) && + (collectionOwner.archiveAsMonolith === true + ? link.monolith && link.monolith !== "pending" + : true) && + (collectionOwner.archiveAsPDF === true + ? link.pdf && link.pdf !== "pending" + : true) && + link.readable && + link.readable !== "pending" + ); + }; + + useEffect(() => { + (async () => { + await getLink.mutateAsync({ + id: link.id as number, + }); + })(); + + let interval: any; + + if (!isReady()) { + interval = setInterval(async () => { + await getLink.mutateAsync({ + id: link.id as number, + }); + }, 5000); + } else { + if (interval) { + clearInterval(interval); + } + } + + return () => { + if (interval) { + clearInterval(interval); + } + }; + }, [link?.monolith]); + + const permissions = usePermissions(link.collection.id as number); + + const router = useRouter(); + + const isPublicRoute = router.pathname.startsWith("/public") ? true : false; + + return ( + +
onClose()} + >
+ + +
+ + + {permissions === true || + (permissions?.canUpdate && ( + <> +
+
+ +
+
{ + onEdit(); + onClose(); + }} + > + {t("edit_link")} +
+
+ + ))} +
+
+ ); +} diff --git a/components/ModalContent/NewLinkModal.tsx b/components/ModalContent/NewLinkModal.tsx index 660c1e4..ff628bf 100644 --- a/components/ModalContent/NewLinkModal.tsx +++ b/components/ModalContent/NewLinkModal.tsx @@ -61,7 +61,7 @@ export default function NewLinkModal({ onClose }: Props) { }; useEffect(() => { - if (router.query.id) { + if (router.pathname.startsWith("/collections/") && router.query.id) { const currentCollection = collections.find( (e) => e.id == Number(router.query.id) ); diff --git a/components/ModalContent/PreservedFormatsModal.tsx b/components/ModalContent/PreservedFormatsModal.tsx index 7cd1df0..309170a 100644 --- a/components/ModalContent/PreservedFormatsModal.tsx +++ b/components/ModalContent/PreservedFormatsModal.tsx @@ -96,14 +96,14 @@ export default function PreservedFormatsModal({ onClose, link }: Props) { useEffect(() => { (async () => { - await getLink.mutateAsync(link.id as number); + await getLink.mutateAsync({ id: link.id as number }); })(); let interval: any; if (!isReady()) { interval = setInterval(async () => { - await getLink.mutateAsync(link.id as number); + await getLink.mutateAsync({ id: link.id as number }); }, 5000); } else { if (interval) { @@ -129,7 +129,7 @@ export default function PreservedFormatsModal({ onClose, link }: Props) { toast.dismiss(load); if (response.ok) { - await getLink.mutateAsync(link?.id as number); + await getLink.mutateAsync({ id: link.id as number }); toast.success(t("link_being_archived")); } else toast.error(data.response); diff --git a/components/ModalContent/UploadFileModal.tsx b/components/ModalContent/UploadFileModal.tsx index bbb1577..85ecaff 100644 --- a/components/ModalContent/UploadFileModal.tsx +++ b/components/ModalContent/UploadFileModal.tsx @@ -70,7 +70,7 @@ export default function UploadFileModal({ onClose }: Props) { useEffect(() => { setOptionsExpanded(false); - if (router.query.id) { + if (router.pathname.startsWith("/collections/") && router.query.id) { const currentCollection = collections.find( (e) => e.id == Number(router.query.id) ); diff --git a/components/PreserverdFormatRow.tsx b/components/PreserverdFormatRow.tsx index e8ddbd1..45b156f 100644 --- a/components/PreserverdFormatRow.tsx +++ b/components/PreserverdFormatRow.tsx @@ -52,11 +52,9 @@ export default function PreservedFormatRow({ }; return ( -
+
-
- -
+

{name}

@@ -64,7 +62,7 @@ export default function PreservedFormatRow({ {downloadable || false ? (
handleDownload()} - className="btn btn-sm btn-square" + className="btn btn-sm btn-square btn-ghost" >
@@ -75,9 +73,9 @@ export default function PreservedFormatRow({ isPublic ? "/public" : "" }/preserved/${link?.id}?format=${format}`} target="_blank" - className="btn btn-sm btn-square" + className="btn btn-sm btn-square btn-ghost" > - +
diff --git a/components/ReadableView.tsx b/components/ReadableView.tsx index 801e9c1..def3185 100644 --- a/components/ReadableView.tsx +++ b/components/ReadableView.tsx @@ -46,13 +46,6 @@ export default function ReadableView({ link }: Props) { const router = useRouter(); const getLink = useGetLink(); - const { data: collections = [] } = useCollections(); - - const collection = useMemo(() => { - return collections.find( - (e) => e.id === link.collection.id - ) as CollectionIncludingMembersAndLinkCount; - }, [collections, link]); useEffect(() => { const fetchLinkContent = async () => { @@ -73,7 +66,7 @@ export default function ReadableView({ link }: Props) { }, [link]); useEffect(() => { - if (link) getLink.mutateAsync(link?.id as number); + if (link) getLink.mutateAsync({ id: link.id as number }); let interval: any; if ( @@ -88,7 +81,10 @@ export default function ReadableView({ link }: Props) { !link?.monolith) ) { interval = setInterval( - () => getLink.mutateAsync(link.id as number), + () => + getLink.mutateAsync({ + id: link.id as number, + }), 5000 ); } else { @@ -243,13 +239,6 @@ export default function ReadableView({ link }: Props) { {link?.name ?

{unescapeString(link?.description)}

: undefined}
- -
diff --git a/hooks/store/links.tsx b/hooks/store/links.tsx index abd5624..6d2177f 100644 --- a/hooks/store/links.tsx +++ b/hooks/store/links.tsx @@ -225,9 +225,21 @@ const useDeleteLink = () => { const useGetLink = () => { const queryClient = useQueryClient(); + const router = useRouter(); + return useMutation({ - mutationFn: async (id: number) => { - const response = await fetch(`/api/v1/links/${id}`); + mutationFn: async ({ + id, + isPublicRoute = router.pathname.startsWith("/public") ? true : undefined, + }: { + id: number; + isPublicRoute?: boolean; + }) => { + const path = isPublicRoute + ? `/api/v1/public/links/${id}` + : `/api/v1/links/${id}`; + + const response = await fetch(path); const data = await response.json(); if (!response.ok) throw new Error(data.response); @@ -250,7 +262,20 @@ const useGetLink = () => { }; }); - queryClient.invalidateQueries({ queryKey: ["publicLinks"] }); + queryClient.setQueriesData( + { queryKey: ["publicLinks"] }, + (oldData: any) => { + if (!oldData) return undefined; + return { + pages: oldData.pages.map((page: any) => + page.map((item: any) => (item.id === data.id ? data : item)) + ), + pageParams: oldData.pageParams, + }; + } + ); + + // queryClient.invalidateQueries({ queryKey: ["publicLinks"] }); }, }); }; diff --git a/package.json b/package.json index 7f090d9..0e72474 100644 --- a/package.json +++ b/package.json @@ -79,7 +79,7 @@ "socks-proxy-agent": "^8.0.2", "stripe": "^12.13.0", "tailwind-merge": "^2.3.0", - "vaul": "^0.8.8", + "vaul": "^0.9.1", "zustand": "^4.3.8" }, "devDependencies": { @@ -95,7 +95,7 @@ "postcss": "^8.4.26", "prettier": "3.1.1", "prisma": "^4.16.2", - "tailwindcss": "^3.3.3", + "tailwindcss": "^3.4.10", "ts-node": "^10.9.2", "typescript": "4.9.4" } diff --git a/pages/dashboard.tsx b/pages/dashboard.tsx index 5ee0f2d..7f59a3b 100644 --- a/pages/dashboard.tsx +++ b/pages/dashboard.tsx @@ -111,14 +111,14 @@ export default function Dashboard() {
-
+
-
+
-
+
diff --git a/pages/links/[id].tsx b/pages/links/[id].tsx new file mode 100644 index 0000000..cfa8785 --- /dev/null +++ b/pages/links/[id].tsx @@ -0,0 +1,41 @@ +import LinkDetails from "@/components/LinkDetails"; +import { useGetLink } from "@/hooks/store/links"; +import { useRouter } from "next/router"; +import { useEffect, useState } from "react"; +import getServerSideProps from "@/lib/client/getServerSideProps"; + +const Index = () => { + const router = useRouter(); + const { id } = router.query; + + useState; + + const getLink = useGetLink(); + + useEffect(() => { + getLink.mutate({ id: Number(id) }); + }, []); + + return ( +
+ {getLink.data ? ( + + ) : ( +
+
+
+
+
+
+
+ )} +
+ ); +}; + +export default Index; + +export { getServerSideProps }; diff --git a/pages/preserved/[id].tsx b/pages/preserved/[id].tsx index c5806ff..de21605 100644 --- a/pages/preserved/[id].tsx +++ b/pages/preserved/[id].tsx @@ -20,7 +20,7 @@ export default function Index() { useEffect(() => { const fetchLink = async () => { if (router.query.id) { - await getLink.mutateAsync(Number(router.query.id)); + await getLink.mutateAsync({ id: Number(router.query.id) }); } }; diff --git a/pages/public/links/[id].tsx b/pages/public/links/[id].tsx new file mode 100644 index 0000000..cfa8785 --- /dev/null +++ b/pages/public/links/[id].tsx @@ -0,0 +1,41 @@ +import LinkDetails from "@/components/LinkDetails"; +import { useGetLink } from "@/hooks/store/links"; +import { useRouter } from "next/router"; +import { useEffect, useState } from "react"; +import getServerSideProps from "@/lib/client/getServerSideProps"; + +const Index = () => { + const router = useRouter(); + const { id } = router.query; + + useState; + + const getLink = useGetLink(); + + useEffect(() => { + getLink.mutate({ id: Number(id) }); + }, []); + + return ( +
+ {getLink.data ? ( + + ) : ( +
+
+
+
+
+
+
+ )} +
+ ); +}; + +export default Index; + +export { getServerSideProps }; diff --git a/pages/public/preserved/[id].tsx b/pages/public/preserved/[id].tsx index 2f415ae..31544b0 100644 --- a/pages/public/preserved/[id].tsx +++ b/pages/public/preserved/[id].tsx @@ -6,10 +6,9 @@ import { } from "@/types/global"; import ReadableView from "@/components/ReadableView"; import getServerSideProps from "@/lib/client/getServerSideProps"; -import { useGetLink, useLinks } from "@/hooks/store/links"; +import { useGetLink } from "@/hooks/store/links"; export default function Index() { - const { links } = useLinks(); const getLink = useGetLink(); const [link, setLink] = useState(); @@ -19,18 +18,14 @@ export default function Index() { useEffect(() => { const fetchLink = async () => { if (router.query.id) { - await getLink.mutateAsync(Number(router.query.id)); + const get = await getLink.mutateAsync({ id: Number(router.query.id) }); + setLink(get); } }; fetchLink(); }, []); - useEffect(() => { - if (links && links[0]) - setLink(links.find((e) => e.id === Number(router.query.id))); - }, [links]); - return (
{/*
@@ -39,6 +34,12 @@ export default function Index() { {link && Number(router.query.format) === ArchivedFormat.readability && ( )} + {link && Number(router.query.format) === ArchivedFormat.monolith && ( + + )} {link && Number(router.query.format) === ArchivedFormat.pdf && (