From 5d26617251fd8be806e38973a8119754ec6c50be Mon Sep 17 00:00:00 2001 From: daniel31x13 Date: Fri, 16 Aug 2024 12:35:04 -0400 Subject: [PATCH 1/6] bug fixed --- hooks/store/links.tsx | 16 ++++++------- lib/client/getPublicCollectionData.ts | 12 +--------- pages/public/collections/[id].tsx | 34 ++++++++++----------------- 3 files changed, 22 insertions(+), 40 deletions(-) diff --git a/hooks/store/links.tsx b/hooks/store/links.tsx index 4702ff4..f8a2c75 100644 --- a/hooks/store/links.tsx +++ b/hooks/store/links.tsx @@ -398,14 +398,13 @@ const useBulkEditLinks = () => { return data.response; }, onSuccess: (data, { links, newData, removePreviousTags }) => { - queryClient.setQueryData(["dashboardData"], (oldData: any) => { - if (!oldData) return undefined; - return oldData.map((e: any) => - data.find((d: any) => d.id === e.id) ? data : e - ); - }); - - // TODO: Fix this + // TODO: Fix these + // queryClient.setQueryData(["dashboardData"], (oldData: any) => { + // if (!oldData) return undefined; + // return oldData.map((e: any) => + // data.find((d: any) => d.id === e.id) ? data : e + // ); + // }); // queryClient.setQueriesData({ queryKey: ["links"] }, (oldData: any) => { // if (!oldData) return undefined; // return { @@ -417,6 +416,7 @@ const useBulkEditLinks = () => { // }; // }); queryClient.invalidateQueries({ queryKey: ["links"] }); // Temporary workaround + queryClient.invalidateQueries({ queryKey: ["dashboardData"] }); // Temporary workaround queryClient.invalidateQueries({ queryKey: ["collections"] }); queryClient.invalidateQueries({ queryKey: ["tags"] }); diff --git a/lib/client/getPublicCollectionData.ts b/lib/client/getPublicCollectionData.ts index a8a74a3..c87abd1 100644 --- a/lib/client/getPublicCollectionData.ts +++ b/lib/client/getPublicCollectionData.ts @@ -1,12 +1,4 @@ -import { CollectionIncludingMembersAndLinkCount } from "@/types/global"; -import { Dispatch, SetStateAction } from "react"; - -const getPublicCollectionData = async ( - collectionId: number, - setData: Dispatch< - SetStateAction - > -) => { +const getPublicCollectionData = async (collectionId: number) => { const res = await fetch("/api/v1/public/collections/" + collectionId); if (res.status === 400) @@ -14,8 +6,6 @@ const getPublicCollectionData = async ( const data = await res.json(); - setData(data.response); - return data; }; diff --git a/pages/public/collections/[id].tsx b/pages/public/collections/[id].tsx index ab7fd42..53efbb3 100644 --- a/pages/public/collections/[id].tsx +++ b/pages/public/collections/[id].tsx @@ -19,7 +19,6 @@ import EditCollectionSharingModal from "@/components/ModalContent/EditCollection import { useTranslation } from "next-i18next"; import getServerSideProps from "@/lib/client/getServerSideProps"; import LinkListOptions from "@/components/LinkListOptions"; -import { useCollections } from "@/hooks/store/collections"; import { usePublicLinks } from "@/hooks/store/publicLinks"; import Links from "@/components/LinkViews/Links"; @@ -28,8 +27,6 @@ export default function PublicCollections() { const { settings } = useLocalSettingsStore(); - const { data: collections = [] } = useCollections(); - const router = useRouter(); const [collectionOwner, setCollectionOwner] = useState({ @@ -71,25 +68,22 @@ export default function PublicCollections() { useEffect(() => { if (router.query.id) { - getPublicCollectionData(Number(router.query.id), setCollection).then( - (res) => { - if (res.status === 400) { - router.push("/dashboard"); - } + getPublicCollectionData(Number(router.query.id)).then((res) => { + if (res.status === 400) { + router.push("/dashboard"); + } else { + setCollection(res.response); } - ); + }); } - }, [collections]); + }, []); useEffect(() => { - const fetchOwner = async () => { - if (collection) { - const owner = await getPublicUserData(collection.ownerId as number); - setCollectionOwner(owner); - } - }; - - fetchOwner(); + if (collection) { + getPublicUserData(collection.ownerId as number).then((owner) => + setCollectionOwner(owner) + ); + } }, [collection]); const [editCollectionSharingModal, setEditCollectionSharingModal] = @@ -236,9 +230,7 @@ export default function PublicCollections() { placeholderCount={1} useData={data} /> - {!data.isLoading && links && !links[0] && ( -

{t("collection_is_empty")}

- )} + {!data.isLoading && links && !links[0] &&

{t("nothing_found")}

} {/*

List created with Linkwarden. From 03f4523d57ce6632b0de15939fa6cf5663f91854 Mon Sep 17 00:00:00 2001 From: daniel31x13 Date: Fri, 16 Aug 2024 13:42:55 -0400 Subject: [PATCH 2/6] bug fix --- components/FilterSearchDropdown.tsx | 40 +++++++++++++++---------- components/SortDropdown.tsx | 33 +++++++++++++++++---- hooks/store/links.tsx | 46 +++++++++++++++++++++-------- 3 files changed, 84 insertions(+), 35 deletions(-) diff --git a/components/FilterSearchDropdown.tsx b/components/FilterSearchDropdown.tsx index a0adc37..70f0aca 100644 --- a/components/FilterSearchDropdown.tsx +++ b/components/FilterSearchDropdown.tsx @@ -1,6 +1,8 @@ import { dropdownTriggerer } from "@/lib/client/utils"; -import React from "react"; +import React, { useEffect } from "react"; import { useTranslation } from "next-i18next"; +import { resetInfiniteQueryPagination } from "@/hooks/store/links"; +import { useQueryClient } from "@tanstack/react-query"; type Props = { setSearchFilter: Function; @@ -18,6 +20,7 @@ export default function FilterSearchDropdown({ searchFilter, }: Props) { const { t } = useTranslation(); + const queryClient = useQueryClient(); return (

@@ -41,9 +44,10 @@ export default function FilterSearchDropdown({ name="search-filter-checkbox" className="checkbox checkbox-primary" checked={searchFilter.name} - onChange={() => - setSearchFilter({ ...searchFilter, name: !searchFilter.name }) - } + onChange={() => { + resetInfiniteQueryPagination(queryClient, ["links"]); + setSearchFilter({ ...searchFilter, name: !searchFilter.name }); + }} /> {t("name")} @@ -59,9 +63,10 @@ export default function FilterSearchDropdown({ name="search-filter-checkbox" className="checkbox checkbox-primary" checked={searchFilter.url} - onChange={() => - setSearchFilter({ ...searchFilter, url: !searchFilter.url }) - } + onChange={() => { + resetInfiniteQueryPagination(queryClient, ["links"]); + setSearchFilter({ ...searchFilter, url: !searchFilter.url }); + }} /> {t("link")} @@ -77,12 +82,13 @@ export default function FilterSearchDropdown({ name="search-filter-checkbox" className="checkbox checkbox-primary" checked={searchFilter.description} - onChange={() => + onChange={() => { + resetInfiniteQueryPagination(queryClient, ["links"]); setSearchFilter({ ...searchFilter, description: !searchFilter.description, - }) - } + }); + }} /> {t("description")} @@ -100,9 +106,10 @@ export default function FilterSearchDropdown({ name="search-filter-checkbox" className="checkbox checkbox-primary" checked={searchFilter.tags} - onChange={() => - setSearchFilter({ ...searchFilter, tags: !searchFilter.tags }) - } + onChange={() => { + resetInfiniteQueryPagination(queryClient, ["links"]); + setSearchFilter({ ...searchFilter, tags: !searchFilter.tags }); + }} /> {t("tags")} @@ -118,12 +125,13 @@ export default function FilterSearchDropdown({ name="search-filter-checkbox" className="checkbox checkbox-primary" checked={searchFilter.textContent} - onChange={() => + onChange={() => { + resetInfiniteQueryPagination(queryClient, ["links"]); setSearchFilter({ ...searchFilter, textContent: !searchFilter.textContent, - }) - } + }); + }} /> {t("full_content")} diff --git a/components/SortDropdown.tsx b/components/SortDropdown.tsx index 7dee461..af9ed5f 100644 --- a/components/SortDropdown.tsx +++ b/components/SortDropdown.tsx @@ -3,6 +3,8 @@ import { Sort } from "@/types/global"; import { dropdownTriggerer } from "@/lib/client/utils"; import { TFunction } from "i18next"; import useLocalSettingsStore from "@/store/localSettings"; +import { resetInfiniteQueryPagination } from "@/hooks/store/links"; +import { useQueryClient } from "@tanstack/react-query"; type Props = { sortBy: Sort; @@ -12,6 +14,7 @@ type Props = { export default function SortDropdown({ sortBy, setSort, t }: Props) { const { updateSettings } = useLocalSettingsStore(); + const queryClient = useQueryClient(); useEffect(() => { updateSettings({ sortBy }); @@ -39,7 +42,10 @@ export default function SortDropdown({ sortBy, setSort, t }: Props) { name="sort-radio" className="radio checked:bg-primary" checked={sortBy === Sort.DateNewestFirst} - onChange={() => setSort(Sort.DateNewestFirst)} + onChange={() => { + resetInfiniteQueryPagination(queryClient, ["links"]); + setSort(Sort.DateNewestFirst); + }} /> {t("date_newest_first")} @@ -57,7 +63,10 @@ export default function SortDropdown({ sortBy, setSort, t }: Props) { name="sort-radio" className="radio checked:bg-primary" checked={sortBy === Sort.DateOldestFirst} - onChange={() => setSort(Sort.DateOldestFirst)} + onChange={() => { + resetInfiniteQueryPagination(queryClient, ["links"]); + setSort(Sort.DateOldestFirst); + }} /> {t("date_oldest_first")} @@ -75,7 +84,10 @@ export default function SortDropdown({ sortBy, setSort, t }: Props) { name="sort-radio" className="radio checked:bg-primary" checked={sortBy === Sort.NameAZ} - onChange={() => setSort(Sort.NameAZ)} + onChange={() => { + resetInfiniteQueryPagination(queryClient, ["links"]); + setSort(Sort.NameAZ); + }} /> {t("name_az")} @@ -91,7 +103,10 @@ export default function SortDropdown({ sortBy, setSort, t }: Props) { name="sort-radio" className="radio checked:bg-primary" checked={sortBy === Sort.NameZA} - onChange={() => setSort(Sort.NameZA)} + onChange={() => { + resetInfiniteQueryPagination(queryClient, ["links"]); + setSort(Sort.NameZA); + }} /> {t("name_za")} @@ -107,7 +122,10 @@ export default function SortDropdown({ sortBy, setSort, t }: Props) { name="sort-radio" className="radio checked:bg-primary" checked={sortBy === Sort.DescriptionAZ} - onChange={() => setSort(Sort.DescriptionAZ)} + onChange={() => { + resetInfiniteQueryPagination(queryClient, ["links"]); + setSort(Sort.DescriptionAZ); + }} /> {t("description_az")} @@ -125,7 +143,10 @@ export default function SortDropdown({ sortBy, setSort, t }: Props) { name="sort-radio" className="radio checked:bg-primary" checked={sortBy === Sort.DescriptionZA} - onChange={() => setSort(Sort.DescriptionZA)} + onChange={() => { + resetInfiniteQueryPagination(queryClient, ["links"]); + setSort(Sort.DescriptionZA); + }} /> {t("description_za")} diff --git a/hooks/store/links.tsx b/hooks/store/links.tsx index f8a2c75..abd5624 100644 --- a/hooks/store/links.tsx +++ b/hooks/store/links.tsx @@ -159,20 +159,23 @@ const useUpdateLink = () => { return data.response; }, onSuccess: (data) => { - queryClient.setQueryData(["dashboardData"], (oldData: any) => { - if (!oldData) return undefined; - return oldData.map((e: any) => (e.id === data.id ? data : e)); - }); + // queryClient.setQueryData(["dashboardData"], (oldData: any) => { + // if (!oldData) return undefined; + // return oldData.map((e: any) => (e.id === data.id ? data : e)); + // }); - queryClient.setQueriesData({ queryKey: ["links"] }, (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.setQueriesData({ queryKey: ["links"] }, (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: ["links"] }); // Temporary workaround + queryClient.invalidateQueries({ queryKey: ["dashboardData"] }); // Temporary workaround queryClient.invalidateQueries({ queryKey: ["collections"] }); queryClient.invalidateQueries({ queryKey: ["tags"] }); @@ -425,6 +428,22 @@ const useBulkEditLinks = () => { }); }; +const resetInfiniteQueryPagination = async ( + queryClient: any, + queryKey: any +) => { + queryClient.setQueriesData({ queryKey }, (oldData: any) => { + if (!oldData) return undefined; + + return { + pages: oldData.pages.slice(0, 1), + pageParams: oldData.pageParams.slice(0, 1), + }; + }); + + await queryClient.invalidateQueries(queryKey); +}; + export { useLinks, useAddLink, @@ -434,4 +453,5 @@ export { useUploadFile, useGetLink, useBulkEditLinks, + resetInfiniteQueryPagination, }; From 6b647573f0774dbc527bc01677196222a8669b0b Mon Sep 17 00:00:00 2001 From: daniel31x13 Date: Fri, 16 Aug 2024 13:44:53 -0400 Subject: [PATCH 3/6] bump version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6a592e9..0850217 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "linkwarden", - "version": "v2.7.0", + "version": "v2.7.1", "main": "index.js", "repository": "https://github.com/linkwarden/linkwarden.git", "author": "Daniel31X13 ", From a40026040c1b12f2b85336ca145f4f958c4efd4c Mon Sep 17 00:00:00 2001 From: daniel31x13 Date: Fri, 16 Aug 2024 23:00:37 -0400 Subject: [PATCH 4/6] icon picker component --- components/IconPicker.tsx | 46 +++++++++++++++++++++++++++++++++++++++ lib/client/icons.ts | 18 +++++++++++++++ package.json | 5 ++++- yarn.lock | 15 +++++++++++++ 4 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 components/IconPicker.tsx create mode 100644 lib/client/icons.ts diff --git a/components/IconPicker.tsx b/components/IconPicker.tsx new file mode 100644 index 0000000..994a5af --- /dev/null +++ b/components/IconPicker.tsx @@ -0,0 +1,46 @@ +import { icons } from "@/lib/client/icons"; +import React, { useMemo, useState } from "react"; +import Fuse from "fuse.js"; +import TextInput from "./TextInput"; + +const fuse = new Fuse(icons, { + keys: [{ name: "name", weight: 4 }, "tags", "categories"], + threshold: 0.2, + useExtendedSearch: true, +}); + +type Props = {}; + +const IconPicker = (props: Props) => { + const [query, setQuery] = useState(""); + + const filteredQueryResultsSelector = useMemo(() => { + if (!query) { + return icons; + } + return fuse.search(query).map((result) => result.item); + }, [query]); + + return ( +
+ setQuery(e.target.value)} + /> +
+ {filteredQueryResultsSelector.map((icon) => { + const IconComponent = icon.Icon; + return ( +
console.log(icon.name)}> + +
+ ); + })} +
+
+ ); +}; + +export default IconPicker; diff --git a/lib/client/icons.ts b/lib/client/icons.ts new file mode 100644 index 0000000..2083bd0 --- /dev/null +++ b/lib/client/icons.ts @@ -0,0 +1,18 @@ +import * as Icons from "@phosphor-icons/react"; +import { icons as iconData } from "@phosphor-icons/core"; +import { IconEntry as CoreEntry } from "@phosphor-icons/core"; + +interface IconEntry extends CoreEntry { + Icon: Icons.Icon; +} + +export const icons: ReadonlyArray = iconData.map((entry) => ({ + ...entry, + Icon: Icons[entry.pascal_name as keyof typeof Icons] as Icons.Icon, +})); + +if (process.env.NODE_ENV === "development") { + console.log(`${icons.length} icons`); +} + +export const iconCount = Intl.NumberFormat("en-US").format(icons.length * 6); diff --git a/package.json b/package.json index 0850217..7f090d9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "linkwarden", - "version": "v2.7.1", + "version": "v2.8.0", "main": "index.js", "repository": "https://github.com/linkwarden/linkwarden.git", "author": "Daniel31X13 ", @@ -25,6 +25,8 @@ "@aws-sdk/client-s3": "^3.379.1", "@headlessui/react": "^1.7.15", "@mozilla/readability": "^0.4.4", + "@phosphor-icons/core": "^2.1.1", + "@phosphor-icons/react": "^2.1.7", "@prisma/client": "^4.16.2", "@stripe/stripe-js": "^1.54.1", "@tanstack/react-query": "^5.51.15", @@ -50,6 +52,7 @@ "eslint-config-next": "13.4.9", "formidable": "^3.5.1", "framer-motion": "^10.16.4", + "fuse.js": "^7.0.0", "handlebars": "^4.7.8", "himalaya": "^1.1.0", "i18next": "^23.11.5", diff --git a/yarn.lock b/yarn.lock index edf5040..85783ea 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1281,6 +1281,16 @@ resolved "https://registry.yarnpkg.com/@panva/hkdf/-/hkdf-1.1.1.tgz#ab9cd8755d1976e72fc77a00f7655a64efe6cd5d" integrity sha512-dhPeilub1NuIG0X5Kvhh9lH4iW3ZsHlnzwgwbOlgwQ2wG1IqFzsgHqmKPk3WzsdWAeaxKJxgM0+W433RmN45GA== +"@phosphor-icons/core@^2.1.1": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@phosphor-icons/core/-/core-2.1.1.tgz#62a4cfbec9772f1a613a647da214fbb96f3ad39d" + integrity sha512-v4ARvrip4qBCImOE5rmPUylOEK4iiED9ZyKjcvzuezqMaiRASCHKcRIuvvxL/twvLpkfnEODCOJp5dM4eZilxQ== + +"@phosphor-icons/react@^2.1.7": + version "2.1.7" + resolved "https://registry.yarnpkg.com/@phosphor-icons/react/-/react-2.1.7.tgz#b11a4b25849b7e3849970b688d9fe91e5d4fd8d7" + integrity sha512-g2e2eVAn1XG2a+LI09QU3IORLhnFNAFkNbo2iwbX6NOKSLOwvEMmTa7CgOzEbgNWR47z8i8kwjdvYZ5fkGx1mQ== + "@pkgr/utils@^2.3.1": version "2.3.1" resolved "https://registry.yarnpkg.com/@pkgr/utils/-/utils-2.3.1.tgz#0a9b06ffddee364d6642b3cd562ca76f55b34a03" @@ -3553,6 +3563,11 @@ functions-have-names@^1.2.2: resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== +fuse.js@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/fuse.js/-/fuse.js-7.0.0.tgz#6573c9fcd4c8268e403b4fc7d7131ffcf99a9eb2" + integrity sha512-14F4hBIxqKvD4Zz/XjDc3y94mNZN6pRv3U13Udo0lNLCWRBUsrMv2xwcF/y/Z5sV6+FQW+/ow68cHpm4sunt8Q== + gauge@^3.0.0: version "3.0.2" resolved "https://registry.yarnpkg.com/gauge/-/gauge-3.0.2.tgz#03bf4441c044383908bcfa0656ad91803259b395" From c18a5f41621f977541fe6e2655e29ce4622dc7fd Mon Sep 17 00:00:00 2001 From: daniel31x13 Date: Sun, 18 Aug 2024 02:55:59 -0400 Subject: [PATCH 5/6] 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 && (