Fix merge conflicts

This commit is contained in:
Isaac Wise
2024-08-18 13:03:09 -05:00
95 changed files with 3462 additions and 1934 deletions
+79 -76
View File
@@ -11,7 +11,16 @@ import { Session } from "next-auth";
import { isPWA } from "@/lib/client/utils";
// import useInitialData from "@/hooks/useInitialData";
import { appWithTranslation } from "next-i18next";
import nextI18nextConfig from "../next-i18next.config";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 1000 * 30,
},
},
});
function App({
Component,
@@ -29,82 +38,76 @@ function App({
}, []);
return (
<SessionProvider
session={pageProps.session}
refetchOnWindowFocus={false}
basePath="/api/v1/auth"
>
<Head>
<title>Linkwarden</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<link
rel="apple-touch-icon"
sizes="180x180"
href="/apple-touch-icon.png"
/>
<link
rel="icon"
type="image/png"
sizes="32x32"
href="/favicon-32x32.png"
/>
<link
rel="icon"
type="image/png"
sizes="16x16"
href="/favicon-16x16.png"
/>
<link rel="manifest" href="/site.webmanifest" />
</Head>
<AuthRedirect>
{/* <GetData> */}
<Toaster
position="top-center"
reverseOrder={false}
toastOptions={{
className:
"border border-sky-100 dark:border-neutral-700 dark:bg-neutral-800 dark:text-white",
}}
>
{(t) => (
<ToastBar toast={t}>
{({ icon, message }) => (
<div
className="flex flex-row"
data-testid="toast-message-container"
data-type={t.type}
>
{icon}
<span data-testid="toast-message">{message}</span>
{t.type !== "loading" && (
<button
className="btn btn-xs outline-none btn-circle btn-ghost"
data-testid="close-toast-button"
onClick={() => toast.dismiss(t.id)}
>
<i className="bi bi-x"></i>
</button>
)}
</div>
)}
</ToastBar>
)}
</Toaster>
<Component {...pageProps} />
{/* </GetData> */}
</AuthRedirect>
</SessionProvider>
<QueryClientProvider client={queryClient}>
<SessionProvider
session={pageProps.session}
refetchOnWindowFocus={false}
basePath="/api/v1/auth"
>
<Head>
<title>Linkwarden</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<link
rel="apple-touch-icon"
sizes="180x180"
href="/apple-touch-icon.png"
/>
<link
rel="icon"
type="image/png"
sizes="32x32"
href="/favicon-32x32.png"
/>
<link
rel="icon"
type="image/png"
sizes="16x16"
href="/favicon-16x16.png"
/>
<link rel="manifest" href="/site.webmanifest" />
</Head>
<AuthRedirect>
{/* <GetData> */}
<Toaster
position="top-center"
reverseOrder={false}
toastOptions={{
className:
"border border-sky-100 dark:border-neutral-700 dark:bg-neutral-800 dark:text-white",
}}
>
{(t) => (
<ToastBar toast={t}>
{({ icon, message }) => (
<div
className="flex flex-row"
data-testid="toast-message-container"
data-type={t.type}
>
{icon}
<span data-testid="toast-message">{message}</span>
{t.type !== "loading" && (
<button
className="btn btn-xs outline-none btn-circle btn-ghost"
data-testid="close-toast-button"
onClick={() => toast.dismiss(t.id)}
>
<i className="bi bi-x"></i>
</button>
)}
</div>
)}
</ToastBar>
)}
</Toaster>
<Component {...pageProps} />
{/* </GetData> */}
</AuthRedirect>
</SessionProvider>
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
);
}
export default appWithTranslation(App);
// function GetData({ children }: { children: React.ReactNode }) {
// const status = useInitialData();
// return typeof window !== "undefined" && status !== "loading" ? (
// children
// ) : (
// <></>
// );
// }
+4 -8
View File
@@ -1,11 +1,11 @@
import NewUserModal from "@/components/ModalContent/NewUserModal";
import useUserStore from "@/store/admin/users";
import { User as U } from "@prisma/client";
import Link from "next/link";
import { useEffect, useState } from "react";
import { useState } from "react";
import { useTranslation } from "next-i18next";
import getServerSideProps from "@/lib/client/getServerSideProps";
import UserListing from "@/components/UserListing";
import { useUsers } from "@/hooks/store/admin/users";
interface User extends U {
subscriptions: {
@@ -21,7 +21,7 @@ type UserModal = {
export default function Admin() {
const { t } = useTranslation();
const { users, setUsers } = useUserStore();
const { data: users = [] } = useUsers();
const [searchQuery, setSearchQuery] = useState("");
const [filteredUsers, setFilteredUsers] = useState<User[]>();
@@ -33,10 +33,6 @@ export default function Admin() {
const [newUserModal, setNewUserModal] = useState(false);
useEffect(() => {
setUsers();
}, []);
return (
<div className="max-w-6xl mx-auto p-5">
<div className="flex sm:flex-row flex-col justify-between gap-2">
@@ -71,7 +67,7 @@ export default function Admin() {
if (users) {
setFilteredUsers(
users.filter((user) =>
users.filter((user: any) =>
JSON.stringify(user)
.toLowerCase()
.includes(e.target.value.toLowerCase())
+1 -1
View File
@@ -1186,7 +1186,7 @@ export default async function auth(req: NextApiRequest, res: NextApiResponse) {
providerAccountId: account?.providerAccountId,
},
});
if (existingUser && newSsoUsersDisabled) {
if (!existingUser && newSsoUsersDisabled) {
return false;
}
}
+57 -64
View File
@@ -1,5 +1,3 @@
import useCollectionStore from "@/store/collections";
import useLinkStore from "@/store/links";
import {
AccountSettings,
CollectionIncludingMembersAndLinkCount,
@@ -10,23 +8,22 @@ import { useRouter } from "next/router";
import React, { useEffect, useState } from "react";
import MainLayout from "@/layouts/MainLayout";
import ProfilePhoto from "@/components/ProfilePhoto";
import useLinks from "@/hooks/useLinks";
import usePermissions from "@/hooks/usePermissions";
import NoLinksFound from "@/components/NoLinksFound";
import useLocalSettingsStore from "@/store/localSettings";
import useAccountStore from "@/store/account";
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 CardView from "@/components/LinkViews/Layouts/CardView";
import ListView from "@/components/LinkViews/Layouts/ListView";
import { dropdownTriggerer } from "@/lib/client/utils";
import NewCollectionModal from "@/components/ModalContent/NewCollectionModal";
import MasonryView from "@/components/LinkViews/Layouts/MasonryView";
import getServerSideProps from "@/lib/client/getServerSideProps";
import { useTranslation } from "next-i18next";
import LinkListOptions from "@/components/LinkListOptions";
import { useCollections } from "@/hooks/store/collections";
import { useUser } from "@/hooks/store/user";
import { useLinks } from "@/hooks/store/links";
import Links from "@/components/LinkViews/Links";
export default function Index() {
const { t } = useTranslation();
@@ -34,25 +31,29 @@ export default function Index() {
const router = useRouter();
const { links } = useLinkStore();
const { collections } = useCollectionStore();
const { data: collections = [] } = useCollections();
const [sortBy, setSortBy] = useState<Sort>(Sort.DateNewestFirst);
const [sortBy, setSortBy] = useState<Sort>(
Number(localStorage.getItem("sortBy")) ?? Sort.DateNewestFirst
);
const { links, data } = useLinks({
sort: sortBy,
collectionId: Number(router.query.id),
});
const [activeCollection, setActiveCollection] =
useState<CollectionIncludingMembersAndLinkCount>();
const permissions = usePermissions(activeCollection?.id as number);
useLinks({ collectionId: Number(router.query.id), sort: sortBy });
useEffect(() => {
setActiveCollection(
collections.find((e) => e.id === Number(router.query.id))
);
}, [router, collections]);
const { account } = useAccountStore();
const { data: user = {} } = useUser();
const [collectionOwner, setCollectionOwner] = useState<
Partial<AccountSettings>
@@ -60,20 +61,20 @@ export default function Index() {
useEffect(() => {
const fetchOwner = async () => {
if (activeCollection && activeCollection.ownerId !== account.id) {
if (activeCollection && activeCollection.ownerId !== user.id) {
const owner = await getPublicUserData(
activeCollection.ownerId as number
);
setCollectionOwner(owner);
} else if (activeCollection && activeCollection.ownerId === account.id) {
} else if (activeCollection && activeCollection.ownerId === user.id) {
setCollectionOwner({
id: account.id as number,
name: account.name,
username: account.username,
image: account.image,
archiveAsScreenshot: account.archiveAsScreenshot,
archiveAsMonolith: account.archiveAsScreenshot,
archiveAsPDF: account.archiveAsPDF,
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,
});
}
};
@@ -92,27 +93,17 @@ export default function Index() {
if (editMode) return setEditMode(false);
}, [router]);
const [viewMode, setViewMode] = useState<string>(
localStorage.getItem("viewMode") || ViewMode.Card
const [viewMode, setViewMode] = useState<ViewMode>(
(localStorage.getItem("viewMode") as ViewMode) || ViewMode.Card
);
const linkView = {
[ViewMode.Card]: CardView,
[ViewMode.List]: ListView,
[ViewMode.Masonry]: MasonryView,
};
// @ts-ignore
const LinkComponent = linkView[viewMode];
return (
<MainLayout>
<div
className="h-[60rem] p-5 flex gap-3 flex-col"
style={{
backgroundImage: `linear-gradient(${activeCollection?.color}20 10%, ${
settings.theme === "dark" ? "#262626" : "#f3f4f6"
} 13rem, ${settings.theme === "dark" ? "#171717" : "#ffffff"} 100%)`,
backgroundImage: `linear-gradient(${activeCollection?.color}20 10%, ${settings.theme === "dark" ? "#262626" : "#f3f4f6"
} 13rem, ${settings.theme === "dark" ? "#171717" : "#ffffff"} 100%)`,
}}
>
{activeCollection && (
@@ -137,7 +128,7 @@ export default function Index() {
>
<i className="bi-three-dots text-xl" title="More"></i>
</div>
<ul className="dropdown-content z-[30] menu shadow bg-base-200 border border-neutral-content rounded-box w-52 mt-1">
<ul className="dropdown-content z-[30] menu shadow bg-base-200 border border-neutral-content rounded-box mt-1">
{permissions === true && (
<li>
<div
@@ -147,6 +138,7 @@ export default function Index() {
(document?.activeElement as HTMLElement)?.blur();
setEditCollectionModal(true);
}}
className="whitespace-nowrap"
>
{t("edit_collection_info")}
</div>
@@ -160,6 +152,7 @@ export default function Index() {
(document?.activeElement as HTMLElement)?.blur();
setEditCollectionSharingModal(true);
}}
className="whitespace-nowrap"
>
{permissions === true
? t("share_and_collaborate")
@@ -175,6 +168,7 @@ export default function Index() {
(document?.activeElement as HTMLElement)?.blur();
setNewCollectionModal(true);
}}
className="whitespace-nowrap"
>
{t("create_subcollection")}
</div>
@@ -188,6 +182,7 @@ export default function Index() {
(document?.activeElement as HTMLElement)?.blur();
setDeleteCollectionModal(true);
}}
className="whitespace-nowrap"
>
{permissions === true
? t("delete_collection")
@@ -236,20 +231,20 @@ export default function Index() {
<p className="text-neutral text-sm">
{activeCollection.members.length > 0 &&
activeCollection.members.length === 1
activeCollection.members.length === 1
? t("by_author_and_other", {
author: collectionOwner.name,
count: activeCollection.members.length,
})
: activeCollection.members.length > 0 &&
activeCollection.members.length !== 1
? t("by_author_and_others", {
author: collectionOwner.name,
count: activeCollection.members.length,
})
: activeCollection.members.length > 0 &&
activeCollection.members.length !== 1
? t("by_author_and_others", {
author: collectionOwner.name,
count: activeCollection.members.length,
})
: t("by_author", {
author: collectionOwner.name,
})}
author: collectionOwner.name,
})}
</p>
</div>
</div>
@@ -294,15 +289,15 @@ export default function Index() {
setSortBy={setSortBy}
editMode={
permissions === true ||
permissions?.canUpdate ||
permissions?.canDelete
permissions?.canUpdate ||
permissions?.canDelete
? editMode
: undefined
}
setEditMode={
permissions === true ||
permissions?.canUpdate ||
permissions?.canDelete
permissions?.canUpdate ||
permissions?.canDelete
? setEditMode
: undefined
}
@@ -310,24 +305,22 @@ export default function Index() {
<p>
{activeCollection?._count?.links === 1
? t("showing_count_result", {
count: activeCollection?._count?.links,
})
count: activeCollection?._count?.links,
})
: t("showing_count_results", {
count: activeCollection?._count?.links,
})}
count: activeCollection?._count?.links,
})}
</p>
</LinkListOptions>
{links.some((e) => e.collectionId === Number(router.query.id)) ? (
<LinkComponent
editMode={editMode}
links={links.filter(
(e) => e.collection.id === activeCollection?.id
)}
/>
) : (
<NoLinksFound />
)}
<Links
editMode={editMode}
links={links}
layout={viewMode}
placeholderCount={1}
useData={data}
/>
{!data.isLoading && links && !links[0] && <NoLinksFound />}
</div>
{activeCollection && (
<>
+5 -3
View File
@@ -1,4 +1,3 @@
import useCollectionStore from "@/store/collections";
import CollectionCard from "@/components/CollectionCard";
import { useState } from "react";
import MainLayout from "@/layouts/MainLayout";
@@ -10,11 +9,14 @@ import NewCollectionModal from "@/components/ModalContent/NewCollectionModal";
import PageHeader from "@/components/PageHeader";
import getServerSideProps from "@/lib/client/getServerSideProps";
import { useTranslation } from "next-i18next";
import { useCollections } from "@/hooks/store/collections";
export default function Collections() {
const { t } = useTranslation();
const { collections } = useCollectionStore();
const [sortBy, setSortBy] = useState<Sort>(Sort.DateNewestFirst);
const { data: collections = [] } = useCollections();
const [sortBy, setSortBy] = useState<Sort>(
Number(localStorage.getItem("sortBy")) ?? Sort.DateNewestFirst
);
const [sortedCollections, setSortedCollections] = useState(collections);
const { data } = useSession();
+6
View File
@@ -43,6 +43,12 @@ export default function EmailConfirmaion() {
<div className="divider my-3"></div>
{router.query.email && typeof router.query.email === "string" && (
<p className="text-center font-bold mb-3 break-all">
{decodeURIComponent(router.query.email)}
</p>
)}
<p>{t("verification_email_sent_desc")}</p>
<div className="mx-auto w-fit mt-3">
+46 -31
View File
@@ -1,9 +1,5 @@
import useLinkStore from "@/store/links";
import useCollectionStore from "@/store/collections";
import useTagStore from "@/store/tags";
import MainLayout from "@/layouts/MainLayout";
import { useEffect, useState } from "react";
import useLinks from "@/hooks/useLinks";
import Link from "next/link";
import useWindowDimensions from "@/hooks/useWindowDimensions";
import React from "react";
@@ -12,26 +8,25 @@ import { MigrationFormat, MigrationRequest, ViewMode } from "@/types/global";
import DashboardItem from "@/components/DashboardItem";
import NewLinkModal from "@/components/ModalContent/NewLinkModal";
import PageHeader from "@/components/PageHeader";
import CardView from "@/components/LinkViews/Layouts/CardView";
import ListView from "@/components/LinkViews/Layouts/ListView";
import ViewDropdown from "@/components/ViewDropdown";
import { dropdownTriggerer } from "@/lib/client/utils";
import MasonryView from "@/components/LinkViews/Layouts/MasonryView";
import getServerSideProps from "@/lib/client/getServerSideProps";
import { useTranslation } from "next-i18next";
import { useCollections } from "@/hooks/store/collections";
import { useTags } from "@/hooks/store/tags";
import { useDashboardData } from "@/hooks/store/dashboardData";
import Links from "@/components/LinkViews/Links";
export default function Dashboard() {
const { t } = useTranslation();
const { collections } = useCollectionStore();
const { links } = useLinkStore();
const { tags } = useTagStore();
const { data: collections = [] } = useCollections();
const dashboardData = useDashboardData();
const { data: tags = [] } = useTags();
const [numberOfLinks, setNumberOfLinks] = useState(0);
const [showLinks, setShowLinks] = useState(3);
useLinks({ pinnedOnly: true, sort: 0 });
useEffect(() => {
setNumberOfLinks(
collections.reduce(
@@ -102,20 +97,10 @@ export default function Dashboard() {
const [newLinkModal, setNewLinkModal] = useState(false);
const [viewMode, setViewMode] = useState<string>(
localStorage.getItem("viewMode") || ViewMode.Card
const [viewMode, setViewMode] = useState<ViewMode>(
(localStorage.getItem("viewMode") as ViewMode) || ViewMode.Card
);
const linkView = {
[ViewMode.Card]: CardView,
// [ViewMode.Grid]: ,
[ViewMode.List]: ListView,
[ViewMode.Masonry]: MasonryView,
};
// @ts-ignore
const LinkComponent = linkView[viewMode];
return (
<MainLayout>
<div style={{ flex: "1 1 auto" }} className="p-5 flex flex-col gap-5">
@@ -174,12 +159,30 @@ export default function Dashboard() {
</div>
<div
style={{ flex: links[0] ? "0 1 auto" : "1 1 auto" }}
style={{
flex:
dashboardData.data || dashboardData.isLoading
? "0 1 auto"
: "1 1 auto",
}}
className="flex flex-col 2xl:flex-row items-start 2xl:gap-2"
>
{links[0] ? (
{dashboardData.isLoading ? (
<div className="w-full">
<LinkComponent links={links.slice(0, showLinks)} />
<Links
layout={viewMode}
placeholderCount={showLinks / 2}
useData={dashboardData}
/>
</div>
) : dashboardData.data &&
dashboardData.data[0] &&
!dashboardData.isLoading ? (
<div className="w-full">
<Links
links={dashboardData.data.slice(0, showLinks)}
layout={viewMode}
/>
</div>
) : (
<div className="sky-shadow flex flex-col justify-center h-full border border-solid border-neutral-content w-full mx-auto p-10 rounded-2xl bg-base-200">
@@ -214,13 +217,14 @@ export default function Dashboard() {
<i className="bi-cloud-upload text-xl duration-100"></i>
<p>{t("import_links")}</p>
</div>
<ul className="shadow menu dropdown-content z-[1] bg-base-200 border border-neutral-content rounded-box mt-1 w-60">
<ul className="shadow menu dropdown-content z-[1] bg-base-200 border border-neutral-content rounded-box mt-1">
<li>
<label
tabIndex={0}
role="button"
htmlFor="import-linkwarden-file"
title={t("from_linkwarden")}
className="whitespace-nowrap"
>
{t("from_linkwarden")}
<input
@@ -241,6 +245,7 @@ export default function Dashboard() {
role="button"
htmlFor="import-html-file"
title={t("from_html")}
className="whitespace-nowrap"
>
{t("from_html")}
<input
@@ -261,6 +266,7 @@ export default function Dashboard() {
role="button"
htmlFor="import-wallabag-file"
title={t("from_wallabag")}
className="whitespace-nowrap"
>
{t("from_wallabag")}
<input
@@ -303,12 +309,21 @@ export default function Dashboard() {
style={{ flex: "1 1 auto" }}
className="flex flex-col 2xl:flex-row items-start 2xl:gap-2"
>
{links.some((e) => e.pinnedBy && e.pinnedBy[0]) ? (
{dashboardData.isLoading ? (
<div className="w-full">
<LinkComponent
links={links
<Links
layout={viewMode}
placeholderCount={showLinks / 2}
useData={dashboardData}
/>
</div>
) : dashboardData.data?.some((e) => e.pinnedBy && e.pinnedBy[0]) ? (
<div className="w-full">
<Links
links={dashboardData.data
.filter((e) => e.pinnedBy && e.pinnedBy[0])
.slice(0, showLinks)}
layout={viewMode}
/>
</div>
) : (
+20 -24
View File
@@ -1,26 +1,28 @@
import NoLinksFound from "@/components/NoLinksFound";
import useLinks from "@/hooks/useLinks";
import { useLinks } from "@/hooks/store/links";
import MainLayout from "@/layouts/MainLayout";
import useLinkStore from "@/store/links";
import React, { useEffect, useState } from "react";
import PageHeader from "@/components/PageHeader";
import { Sort, ViewMode } from "@/types/global";
import CardView from "@/components/LinkViews/Layouts/CardView";
import ListView from "@/components/LinkViews/Layouts/ListView";
import { useRouter } from "next/router";
import MasonryView from "@/components/LinkViews/Layouts/MasonryView";
import LinkListOptions from "@/components/LinkListOptions";
import getServerSideProps from "@/lib/client/getServerSideProps";
import { useTranslation } from "next-i18next";
import Links from "@/components/LinkViews/Links";
export default function Links() {
export default function Index() {
const { t } = useTranslation();
const { links } = useLinkStore();
const [viewMode, setViewMode] = useState<string>(
localStorage.getItem("viewMode") || ViewMode.Card
const [viewMode, setViewMode] = useState<ViewMode>(
(localStorage.getItem("viewMode") as ViewMode) || ViewMode.Card
);
const [sortBy, setSortBy] = useState<Sort>(Sort.DateNewestFirst);
const [sortBy, setSortBy] = useState<Sort>(
Number(localStorage.getItem("sortBy")) ?? Sort.DateNewestFirst
);
const { links, data } = useLinks({
sort: sortBy,
});
const router = useRouter();
@@ -30,17 +32,6 @@ export default function Links() {
if (editMode) return setEditMode(false);
}, [router]);
useLinks({ sort: sortBy });
const linkView = {
[ViewMode.Card]: CardView,
[ViewMode.List]: ListView,
[ViewMode.Masonry]: MasonryView,
};
// @ts-ignore
const LinkComponent = linkView[viewMode];
return (
<MainLayout>
<div className="p-5 flex flex-col gap-5 w-full h-full">
@@ -60,11 +51,16 @@ export default function Links() {
/>
</LinkListOptions>
{links[0] ? (
<LinkComponent editMode={editMode} links={links} />
) : (
{!data.isLoading && links && !links[0] && (
<NoLinksFound text={t("you_have_not_added_any_links")} />
)}
<Links
editMode={editMode}
links={links}
layout={viewMode}
placeholderCount={1}
useData={data}
/>
</div>
</MainLayout>
);
+19 -27
View File
@@ -1,45 +1,32 @@
import useLinks from "@/hooks/useLinks";
import MainLayout from "@/layouts/MainLayout";
import useLinkStore from "@/store/links";
import React, { useEffect, useState } from "react";
import PageHeader from "@/components/PageHeader";
import { Sort, ViewMode } from "@/types/global";
import CardView from "@/components/LinkViews/Layouts/CardView";
import ListView from "@/components/LinkViews/Layouts/ListView";
import { useRouter } from "next/router";
import MasonryView from "@/components/LinkViews/Layouts/MasonryView";
import { useTranslation } from "next-i18next";
import getServerSideProps from "@/lib/client/getServerSideProps";
import LinkListOptions from "@/components/LinkListOptions";
import { useLinks } from "@/hooks/store/links";
import Links from "@/components/LinkViews/Links";
export default function PinnedLinks() {
const { t } = useTranslation();
const { links } = useLinkStore();
const [viewMode, setViewMode] = useState<string>(
localStorage.getItem("viewMode") || ViewMode.Card
const [viewMode, setViewMode] = useState<ViewMode>(
(localStorage.getItem("viewMode") as ViewMode) || ViewMode.Card
);
const [sortBy, setSortBy] = useState<Sort>(
Number(localStorage.getItem("sortBy")) ?? Sort.DateNewestFirst
);
const [sortBy, setSortBy] = useState<Sort>(Sort.DateNewestFirst);
useLinks({ sort: sortBy, pinnedOnly: true });
const { links, data } = useLinks({
sort: sortBy,
pinnedOnly: true,
});
const router = useRouter();
const [editMode, setEditMode] = useState(false);
useEffect(() => {
if (editMode) return setEditMode(false);
}, [router]);
const linkView = {
[ViewMode.Card]: CardView,
[ViewMode.List]: ListView,
[ViewMode.Masonry]: MasonryView,
};
// @ts-ignore
const LinkComponent = linkView[viewMode];
return (
<MainLayout>
<div className="p-5 flex flex-col gap-5 w-full h-full">
@@ -59,9 +46,7 @@ export default function PinnedLinks() {
/>
</LinkListOptions>
{links.some((e) => e.pinnedBy && e.pinnedBy[0]) ? (
<LinkComponent editMode={editMode} links={links} />
) : (
{!data.isLoading && links && !links[0] && (
<div
style={{ flex: "1 1 auto" }}
className="flex flex-col gap-2 justify-center h-full w-full mx-auto p-10"
@@ -82,6 +67,13 @@ export default function PinnedLinks() {
</p>
</div>
)}
<Links
editMode={editMode}
links={links}
layout={viewMode}
placeholderCount={1}
useData={data}
/>
</div>
</MainLayout>
);
+7 -4
View File
@@ -1,5 +1,4 @@
import React, { useEffect, useState } from "react";
import useLinkStore from "@/store/links";
import { useRouter } from "next/router";
import {
ArchivedFormat,
@@ -7,9 +6,12 @@ import {
} from "@/types/global";
import ReadableView from "@/components/ReadableView";
import getServerSideProps from "@/lib/client/getServerSideProps";
import { useGetLink, useLinks } from "@/hooks/store/links";
export default function Index() {
const { links, getLink } = useLinkStore();
const { links } = useLinks();
const getLink = useGetLink();
const [link, setLink] = useState<LinkIncludingShortenedCollectionAndTags>();
@@ -18,7 +20,7 @@ export default function Index() {
useEffect(() => {
const fetchLink = async () => {
if (router.query.id) {
await getLink(Number(router.query.id));
await getLink.mutateAsync(Number(router.query.id));
}
};
@@ -26,7 +28,8 @@ export default function Index() {
}, []);
useEffect(() => {
if (links[0]) setLink(links.find((e) => e.id === Number(router.query.id)));
if (links && links[0])
setLink(links.find((e) => e.id === Number(router.query.id)));
}, [links]);
return (
+47 -42
View File
@@ -9,8 +9,6 @@ import {
import { useRouter } from "next/router";
import React, { useEffect, useState } from "react";
import Head from "next/head";
import useLinks from "@/hooks/useLinks";
import useLinkStore from "@/store/links";
import ProfilePhoto from "@/components/ProfilePhoto";
import ToggleDarkMode from "@/components/ToggleDarkMode";
import getPublicUserData from "@/lib/client/getPublicUserData";
@@ -19,21 +17,19 @@ import Link from "next/link";
import useLocalSettingsStore from "@/store/localSettings";
import SearchBar from "@/components/SearchBar";
import EditCollectionSharingModal from "@/components/ModalContent/EditCollectionSharingModal";
import CardView from "@/components/LinkViews/Layouts/CardView";
import ListView from "@/components/LinkViews/Layouts/ListView";
import MasonryView from "@/components/LinkViews/Layouts/MasonryView";
import { useTranslation } from "next-i18next";
import getServerSideProps from "@/lib/client/getServerSideProps";
import useCollectionStore from "@/store/collections";
import LinkListOptions from "@/components/LinkListOptions";
import { useCollections } from "@/hooks/store/collections";
import { usePublicLinks } from "@/hooks/store/publicLinks";
import Links from "@/components/LinkViews/Links";
export default function PublicCollections() {
const { t } = useTranslation();
const { links } = useLinkStore();
const { settings } = useLocalSettingsStore();
const { collections } = useCollectionStore();
const { data: collections = [] } = useCollections();
const router = useRouter();
@@ -49,9 +45,11 @@ export default function PublicCollections() {
textContent: false,
});
const [sortBy, setSortBy] = useState<Sort>(Sort.DateNewestFirst);
const [sortBy, setSortBy] = useState<Sort>(
Number(localStorage.getItem("sortBy")) ?? Sort.DateNewestFirst
);
useLinks({
const { links, data } = usePublicLinks({
sort: sortBy,
searchQueryString: router.query.q
? decodeURIComponent(router.query.q as string)
@@ -68,7 +66,13 @@ export default function PublicCollections() {
useEffect(() => {
if (router.query.id) {
getPublicCollectionData(Number(router.query.id), setCollection);
getPublicCollectionData(Number(router.query.id), setCollection).then(
(res) => {
if (res.status === 400) {
router.push("/dashboard");
}
}
);
}
}, [collections]);
@@ -86,8 +90,8 @@ export default function PublicCollections() {
const [editCollectionSharingModal, setEditCollectionSharingModal] =
useState(false);
const [viewMode, setViewMode] = useState<string>(
localStorage.getItem("viewMode") || ViewMode.Card
const [viewMode, setViewMode] = useState<ViewMode>(
(localStorage.getItem("viewMode") as ViewMode) || ViewMode.Card
);
const linkView = {
@@ -105,9 +109,8 @@ export default function PublicCollections() {
<div
className="h-96"
style={{
backgroundImage: `linear-gradient(${collection?.color}30 10%, ${
settings.theme === "dark" ? "#262626" : "#f3f4f6"
} 13rem, ${settings.theme === "dark" ? "#171717" : "#ffffff"} 100%)`,
backgroundImage: `linear-gradient(${collection?.color}30 10%, ${settings.theme === "dark" ? "#262626" : "#f3f4f6"
} 13rem, ${settings.theme === "dark" ? "#171717" : "#ffffff"} 100%)`,
}}
>
{collection && (
@@ -178,20 +181,20 @@ export default function PublicCollections() {
<p className="text-neutral text-sm">
{collection.members.length > 0 &&
collection.members.length === 1
collection.members.length === 1
? t("by_author_and_other", {
author: collectionOwner.name,
count: collection.members.length,
})
: collection.members.length > 0 &&
collection.members.length !== 1
? t("by_author_and_others", {
author: collectionOwner.name,
count: collection.members.length,
})
: collection.members.length > 0 &&
collection.members.length !== 1
? t("by_author_and_others", {
author: collectionOwner.name,
count: collection.members.length,
})
: t("by_author", {
author: collectionOwner.name,
})}
author: collectionOwner.name,
})}
</p>
</div>
</div>
@@ -215,28 +218,30 @@ export default function PublicCollections() {
placeholder={
collection._count?.links === 1
? t("search_count_link", {
count: collection._count?.links,
})
count: collection._count?.links,
})
: t("search_count_links", {
count: collection._count?.links,
})
count: collection._count?.links,
})
}
/>
</LinkListOptions>
{links[0] ? (
<LinkComponent
links={links
.filter((e) => e.collectionId === Number(router.query.id))
.map((e, i) => {
const linkWithCollectionData = {
...e,
collection: collection, // Append collection data
};
return linkWithCollectionData;
})}
/>
) : (
<Links
links={
links?.map((e, i) => {
const linkWithCollectionData = {
...e,
collection: collection, // Append collection data
};
return linkWithCollectionData;
}) as any
}
layout={viewMode}
placeholderCount={1}
useData={data}
/>
{!data.isLoading && links && !links[0] && (
<p>{t("collection_is_empty")}</p>
)}
+6 -6
View File
@@ -1,5 +1,4 @@
import React, { useEffect, useState } from "react";
import useLinkStore from "@/store/links";
import { useRouter } from "next/router";
import {
ArchivedFormat,
@@ -7,20 +6,20 @@ import {
} from "@/types/global";
import ReadableView from "@/components/ReadableView";
import getServerSideProps from "@/lib/client/getServerSideProps";
import { useGetLink, useLinks } from "@/hooks/store/links";
export default function Index() {
const { links, getLink } = useLinkStore();
const { links } = useLinks();
const getLink = useGetLink();
const [link, setLink] = useState<LinkIncludingShortenedCollectionAndTags>();
const router = useRouter();
let isPublic = router.pathname.startsWith("/public") ? true : false;
useEffect(() => {
const fetchLink = async () => {
if (router.query.id) {
await getLink(Number(router.query.id), isPublic);
await getLink.mutateAsync(Number(router.query.id));
}
};
@@ -28,7 +27,8 @@ export default function Index() {
}, []);
useEffect(() => {
if (links[0]) setLink(links.find((e) => e.id === Number(router.query.id)));
if (links && links[0])
setLink(links.find((e) => e.id === Number(router.query.id)));
}, [links]);
return (
+26 -39
View File
@@ -1,23 +1,17 @@
import useLinks from "@/hooks/useLinks";
import { useLinks } from "@/hooks/store/links";
import MainLayout from "@/layouts/MainLayout";
import useLinkStore from "@/store/links";
import { Sort, ViewMode } from "@/types/global";
import { useRouter } from "next/router";
import React, { useEffect, useState } from "react";
import CardView from "@/components/LinkViews/Layouts/CardView";
import ListView from "@/components/LinkViews/Layouts/ListView";
import PageHeader from "@/components/PageHeader";
import { GridLoader } from "react-spinners";
import MasonryView from "@/components/LinkViews/Layouts/MasonryView";
import LinkListOptions from "@/components/LinkListOptions";
import getServerSideProps from "@/lib/client/getServerSideProps";
import { useTranslation } from "next-i18next";
import Links from "@/components/LinkViews/Links";
export default function Search() {
const { t } = useTranslation();
const { links } = useLinkStore();
const router = useRouter();
const [searchFilter, setSearchFilter] = useState({
@@ -28,11 +22,13 @@ export default function Search() {
textContent: false,
});
const [viewMode, setViewMode] = useState<string>(
localStorage.getItem("viewMode") || ViewMode.Card
const [viewMode, setViewMode] = useState<ViewMode>(
(localStorage.getItem("viewMode") as ViewMode) || ViewMode.Card
);
const [sortBy, setSortBy] = useState<Sort>(Sort.DateNewestFirst);
const [sortBy, setSortBy] = useState<Sort>(
Number(localStorage.getItem("sortBy")) ?? Sort.DateNewestFirst
);
const [editMode, setEditMode] = useState(false);
@@ -40,7 +36,17 @@ export default function Search() {
if (editMode) return setEditMode(false);
}, [router]);
const { isLoading } = useLinks({
// const { isLoading } = useLink({
// sort: sortBy,
// searchQueryString: decodeURIComponent(router.query.q as string),
// searchByName: searchFilter.name,
// searchByUrl: searchFilter.url,
// searchByDescription: searchFilter.description,
// searchByTextContent: searchFilter.textContent,
// searchByTags: searchFilter.tags,
// });
const { links, data } = useLinks({
sort: sortBy,
searchQueryString: decodeURIComponent(router.query.q as string),
searchByName: searchFilter.name,
@@ -50,15 +56,6 @@ export default function Search() {
searchByTags: searchFilter.tags,
});
const linkView = {
[ViewMode.Card]: CardView,
[ViewMode.List]: ListView,
[ViewMode.Masonry]: MasonryView,
};
// @ts-ignore
const LinkComponent = linkView[viewMode];
return (
<MainLayout>
<div className="p-5 flex flex-col gap-5 w-full h-full">
@@ -76,24 +73,14 @@ export default function Search() {
<PageHeader icon={"bi-search"} title={"Search Results"} />
</LinkListOptions>
{!isLoading && !links[0] ? (
<p>{t("nothing_found")}</p>
) : links[0] ? (
<LinkComponent
editMode={editMode}
links={links}
isLoading={isLoading}
/>
) : (
isLoading && (
<GridLoader
color="oklch(var(--p))"
loading={true}
size={20}
className="m-auto py-10"
/>
)
)}
{!data.isLoading && links && !links[0] && <p>{t("nothing_found")}</p>}
<Links
editMode={editMode}
links={links}
layout={viewMode}
placeholderCount={1}
useData={data}
/>
</div>
</MainLayout>
);
+3 -11
View File
@@ -1,11 +1,11 @@
import SettingsLayout from "@/layouts/SettingsLayout";
import React, { useEffect, useState } from "react";
import React, { useState } from "react";
import NewTokenModal from "@/components/ModalContent/NewTokenModal";
import RevokeTokenModal from "@/components/ModalContent/RevokeTokenModal";
import { AccessToken } from "@prisma/client";
import useTokenStore from "@/store/tokens";
import { useTranslation } from "next-i18next";
import getServerSideProps from "@/lib/client/getServerSideProps";
import { useTokens } from "@/hooks/store/tokens";
export default function AccessTokens() {
const [newTokenModal, setNewTokenModal] = useState(false);
@@ -18,15 +18,7 @@ export default function AccessTokens() {
setRevokeTokenModal(true);
};
const { setTokens, tokens } = useTokenStore();
useEffect(() => {
fetch("/api/v1/tokens")
.then((res) => res.json())
.then((data) => {
if (data.response) setTokens(data.response as AccessToken[]);
});
}, []);
const { data: tokens = [] } = useTokens();
return (
<SettingsLayout>
+56 -37
View File
@@ -1,5 +1,4 @@
import { useState, useEffect, ChangeEvent } from "react";
import useAccountStore from "@/store/account";
import { AccountSettings } from "@/types/global";
import { toast } from "react-hot-toast";
import SettingsLayout from "@/layouts/SettingsLayout";
@@ -17,6 +16,7 @@ import Button from "@/components/ui/Button";
import { i18n } from "next-i18next.config";
import { useTranslation } from "next-i18next";
import getServerSideProps from "@/lib/client/getServerSideProps";
import { useUpdateUser, useUser } from "@/hooks/store/user";
const emailEnabled = process.env.NEXT_PUBLIC_EMAIL_PROVIDER;
@@ -24,24 +24,25 @@ export default function Account() {
const [emailChangeVerificationModal, setEmailChangeVerificationModal] =
useState(false);
const [submitLoader, setSubmitLoader] = useState(false);
const { account, updateAccount } = useAccountStore();
const { data: account } = useUser();
const updateUser = useUpdateUser();
const [user, setUser] = useState<AccountSettings>(
!objectIsEmpty(account)
? account
: ({
// @ts-ignore
id: null,
name: "",
username: "",
email: "",
emailVerified: null,
password: undefined,
image: "",
isPrivate: true,
// @ts-ignore
createdAt: null,
whitelistedUsers: [],
} as unknown as AccountSettings)
// @ts-ignore
id: null,
name: "",
username: "",
email: "",
emailVerified: null,
password: undefined,
image: "",
isPrivate: true,
// @ts-ignore
createdAt: null,
whitelistedUsers: [],
} as unknown as AccountSettings)
);
const { t } = useTranslation();
@@ -80,25 +81,38 @@ export default function Account() {
const submit = async (password?: string) => {
setSubmitLoader(true);
const load = toast.loading(t("applying_settings"));
const response = await updateAccount({
...user,
// @ts-ignore
password: password ? password : undefined,
});
await updateUser.mutateAsync(
{
...user,
password: password ? password : undefined,
},
{
onSuccess: (data) => {
if (data.response.email !== user.email) {
toast.success(t("email_change_request"));
setEmailChangeVerificationModal(false);
}
},
onSettled: (data, error) => {
toast.dismiss(load);
toast.dismiss(load);
if (error) {
toast.error(error.message);
} else {
if (data.response.email !== user.email) {
toast.success(t("email_change_request"));
setEmailChangeVerificationModal(false);
}
if (response.ok) {
const emailChanged = account.email !== user.email;
toast.success(t("settings_applied"));
if (emailChanged) {
toast.success(t("email_change_request"));
setEmailChangeVerificationModal(false);
toast.success(t("settings_applied"));
}
},
}
} else toast.error(response.data as string);
);
setSubmitLoader(false);
};
@@ -195,17 +209,14 @@ export default function Account() {
<div>
<p className="mb-2">{t("language")}</p>
<select
value={user.locale || ""}
onChange={(e) => {
setUser({ ...user, locale: e.target.value });
}}
className="select border border-neutral-content focus:outline-none focus:border-primary duration-100 w-full bg-base-200 rounded-[0.375rem] min-h-0 h-[2.625rem] leading-4 p-2"
>
{i18n.locales.map((locale) => (
<option
key={locale}
value={locale}
selected={user.locale === locale}
>
<option key={locale} value={locale} className="capitalize">
{new Intl.DisplayNames(locale, { type: "language" }).of(
locale
) || ""}
@@ -237,9 +248,13 @@ export default function Account() {
<i className="bi-pencil-square text-md duration-100"></i>
{t("edit")}
</Button>
<ul className="shadow menu dropdown-content z-[1] bg-base-200 border border-neutral-content rounded-box mt-1 w-60">
<ul className="shadow menu dropdown-content z-[1] bg-base-200 border border-neutral-content rounded-box mt-1">
<li>
<label tabIndex={0} role="button">
<label
tabIndex={0}
role="button"
className="whitespace-nowrap"
>
{t("upload_new_photo")}
<input
type="file"
@@ -262,6 +277,7 @@ export default function Account() {
image: "",
})
}
className="whitespace-nowrap"
>
{t("remove_photo")}
</div>
@@ -336,13 +352,14 @@ export default function Account() {
{t("import_links")}
</Button>
<ul className="shadow menu dropdown-content z-[1] bg-base-200 border border-neutral-content rounded-box mt-1 w-60">
<ul className="shadow menu dropdown-content z-[1] bg-base-200 border border-neutral-content rounded-box mt-1">
<li>
<label
tabIndex={0}
role="button"
htmlFor="import-linkwarden-file"
title={t("from_linkwarden")}
className="whitespace-nowrap"
>
{t("from_linkwarden")}
<input
@@ -363,6 +380,7 @@ export default function Account() {
role="button"
htmlFor="import-html-file"
title={t("from_html")}
className="whitespace-nowrap"
>
{t("from_html")}
<input
@@ -383,6 +401,7 @@ export default function Account() {
role="button"
htmlFor="import-wallabag-file"
title={t("from_wallabag")}
className="whitespace-nowrap"
>
{t("from_wallabag")}
<input
+23 -16
View File
@@ -1,11 +1,11 @@
import SettingsLayout from "@/layouts/SettingsLayout";
import { useState } from "react";
import useAccountStore from "@/store/account";
import SubmitButton from "@/components/SubmitButton";
import { toast } from "react-hot-toast";
import TextInput from "@/components/TextInput";
import { useTranslation } from "next-i18next";
import getServerSideProps from "@/lib/client/getServerSideProps";
import { useUpdateUser, useUser } from "@/hooks/store/user";
export default function Password() {
const { t } = useTranslation();
@@ -13,7 +13,8 @@ export default function Password() {
const [oldPassword, setOldPassword] = useState("");
const [newPassword, setNewPassword] = useState("");
const [submitLoader, setSubmitLoader] = useState(false);
const { account, updateAccount } = useAccountStore();
const { data: account } = useUser();
const updateUser = useUpdateUser();
const submit = async () => {
if (newPassword === "" || oldPassword === "") {
@@ -23,23 +24,29 @@ export default function Password() {
setSubmitLoader(true);
const load = toast.loading(t("applying_changes"));
const load = toast.loading(t("applying_settings"));
const response = await updateAccount({
...account,
newPassword,
oldPassword,
});
await updateUser.mutateAsync(
{
...account,
newPassword,
oldPassword,
},
{
onSettled: (data, error) => {
toast.dismiss(load);
toast.dismiss(load);
if (error) {
toast.error(error.message);
} else {
setNewPassword("");
setOldPassword("");
if (response.ok) {
toast.success(t("settings_applied"));
setNewPassword("");
setOldPassword("");
} else {
toast.error(response.data as string);
}
toast.success(t("settings_applied"));
}
},
}
);
setSubmitLoader(false);
};
+17 -10
View File
@@ -1,6 +1,5 @@
import SettingsLayout from "@/layouts/SettingsLayout";
import { useState, useEffect } from "react";
import useAccountStore from "@/store/account";
import SubmitButton from "@/components/SubmitButton";
import { toast } from "react-hot-toast";
import Checkbox from "@/components/Checkbox";
@@ -8,12 +7,14 @@ import useLocalSettingsStore from "@/store/localSettings";
import { useTranslation } from "next-i18next";
import getServerSideProps from "@/lib/client/getServerSideProps"; // Import getServerSideProps for server-side data fetching
import { LinksRouteTo } from "@prisma/client";
import { useUpdateUser, useUser } from "@/hooks/store/user";
export default function Appearance() {
const { t } = useTranslation();
const { updateSettings } = useLocalSettingsStore();
const [submitLoader, setSubmitLoader] = useState(false);
const { account, updateAccount } = useAccountStore();
const { data: account } = useUser();
const updateUser = useUpdateUser();
const [user, setUser] = useState(account);
const [preventDuplicateLinks, setPreventDuplicateLinks] = useState<boolean>(
@@ -73,17 +74,23 @@ export default function Appearance() {
const submit = async () => {
setSubmitLoader(true);
const load = toast.loading(t("applying_changes"));
const load = toast.loading(t("applying_settings"));
const response = await updateAccount({ ...user });
await updateUser.mutateAsync(
{ ...user },
{
onSettled: (data, error) => {
toast.dismiss(load);
toast.dismiss(load);
if (error) {
toast.error(error.message);
} else {
toast.success(t("settings_applied"));
}
},
}
);
if (response.ok) {
toast.success(t("settings_applied"));
} else {
toast.error(response.data as string);
}
setSubmitLoader(false);
};
+3 -3
View File
@@ -7,7 +7,7 @@ import { Plan } from "@/types/global";
import Button from "@/components/ui/Button";
import getServerSideProps from "@/lib/client/getServerSideProps";
import { Trans, useTranslation } from "next-i18next";
import useAccountStore from "@/store/account";
import { useUser } from "@/hooks/store/user";
const stripeEnabled = process.env.NEXT_PUBLIC_STRIPE === "true";
@@ -20,11 +20,11 @@ export default function Subscribe() {
const router = useRouter();
const { account } = useAccountStore();
const { data: user = {} } = useUser();
useEffect(() => {
const hasInactiveSubscription =
account.id && !account.subscription?.active && stripeEnabled;
user.id && !user.subscription?.active && stripeEnabled;
if (session.status === "authenticated" && !hasInactiveSubscription) {
router.push("/dashboard");
+58 -49
View File
@@ -1,29 +1,29 @@
import useLinkStore from "@/store/links";
import { useRouter } from "next/router";
import { FormEvent, useEffect, useState } from "react";
import MainLayout from "@/layouts/MainLayout";
import useTagStore from "@/store/tags";
import { Sort, TagIncludingLinkCount, ViewMode } from "@/types/global";
import useLinks from "@/hooks/useLinks";
import { toast } from "react-hot-toast";
import CardView from "@/components/LinkViews/Layouts/CardView";
import ListView from "@/components/LinkViews/Layouts/ListView";
import { useLinks } from "@/hooks/store/links";
import { dropdownTriggerer } from "@/lib/client/utils";
import BulkDeleteLinksModal from "@/components/ModalContent/BulkDeleteLinksModal";
import BulkEditLinksModal from "@/components/ModalContent/BulkEditLinksModal";
import MasonryView from "@/components/LinkViews/Layouts/MasonryView";
import { useTranslation } from "next-i18next";
import getServerSideProps from "@/lib/client/getServerSideProps";
import LinkListOptions from "@/components/LinkListOptions";
import { useRemoveTag, useTags, useUpdateTag } from "@/hooks/store/tags";
import Links from "@/components/LinkViews/Links";
import toast from "react-hot-toast";
export default function Index() {
const { t } = useTranslation();
const router = useRouter();
const { links } = useLinkStore();
const { tags, updateTag, removeTag } = useTagStore();
const { data: tags = [] } = useTags();
const updateTag = useUpdateTag();
const removeTag = useRemoveTag();
const [sortBy, setSortBy] = useState<Sort>(Sort.DateNewestFirst);
const [sortBy, setSortBy] = useState<Sort>(
Number(localStorage.getItem("sortBy")) ?? Sort.DateNewestFirst
);
const [renameTag, setRenameTag] = useState(false);
const [newTagName, setNewTagName] = useState<string>();
@@ -38,10 +38,13 @@ export default function Index() {
if (editMode) return setEditMode(false);
}, [router]);
useLinks({ tagId: Number(router.query.id), sort: sortBy });
const { links, data } = useLinks({
sort: sortBy,
tagId: Number(router.query.id),
});
useEffect(() => {
const tag = tags.find((e) => e.id === Number(router.query.id));
const tag = tags.find((e: any) => e.id === Number(router.query.id));
if (tags.length > 0 && !tag?.id) {
router.push("/dashboard");
@@ -72,21 +75,28 @@ export default function Index() {
setSubmitLoader(true);
const load = toast.loading(t("applying_changes"));
if (activeTag && newTagName) {
const load = toast.loading(t("applying_changes"));
let response;
await updateTag.mutateAsync(
{
...activeTag,
name: newTagName,
},
{
onSettled: (data, error) => {
toast.dismiss(load);
if (activeTag && newTagName)
response = await updateTag({
...activeTag,
name: newTagName,
});
if (error) {
toast.error(error.message);
} else {
toast.success(t("tag_renamed"));
}
},
}
);
}
toast.dismiss(load);
if (response?.ok) {
toast.success(t("tag_renamed"));
} else toast.error(response?.data as string);
setSubmitLoader(false);
setRenameTag(false);
};
@@ -94,35 +104,31 @@ export default function Index() {
const remove = async () => {
setSubmitLoader(true);
const load = toast.loading(t("applying_changes"));
if (activeTag?.id) {
const load = toast.loading(t("applying_changes"));
let response;
await removeTag.mutateAsync(activeTag?.id, {
onSettled: (data, error) => {
toast.dismiss(load);
if (activeTag?.id) response = await removeTag(activeTag?.id);
if (error) {
toast.error(error.message);
} else {
toast.success(t("tag_deleted"));
router.push("/links");
}
},
});
}
toast.dismiss(load);
if (response?.ok) {
toast.success(t("tag_deleted"));
router.push("/links");
} else toast.error(response?.data as string);
setSubmitLoader(false);
setRenameTag(false);
};
const [viewMode, setViewMode] = useState<string>(
localStorage.getItem("viewMode") || ViewMode.Card
const [viewMode, setViewMode] = useState<ViewMode>(
(localStorage.getItem("viewMode") as ViewMode) || ViewMode.Card
);
const linkView = {
[ViewMode.Card]: CardView,
[ViewMode.List]: ListView,
[ViewMode.Masonry]: MasonryView,
};
// @ts-ignore
const LinkComponent = linkView[viewMode];
return (
<MainLayout>
<div className="p-5 flex flex-col gap-5 w-full">
@@ -188,7 +194,7 @@ export default function Index() {
className={"bi-three-dots text-neutral text-2xl"}
></i>
</div>
<ul className="dropdown-content z-[30] menu shadow bg-base-200 border border-neutral-content rounded-box w-36 mt-1">
<ul className="dropdown-content z-[30] menu shadow bg-base-200 border border-neutral-content rounded-box mt-1">
<li>
<div
role="button"
@@ -197,6 +203,7 @@ export default function Index() {
(document?.activeElement as HTMLElement)?.blur();
setRenameTag(true);
}}
className="whitespace-nowrap"
>
{t("rename_tag")}
</div>
@@ -209,6 +216,7 @@ export default function Index() {
(document?.activeElement as HTMLElement)?.blur();
remove();
}}
className="whitespace-nowrap"
>
{t("delete_tag")}
</div>
@@ -222,11 +230,12 @@ export default function Index() {
</div>
</LinkListOptions>
<LinkComponent
<Links
editMode={editMode}
links={links.filter((e) =>
e.tags.some((e) => e.id === Number(router.query.id))
)}
links={links}
layout={viewMode}
placeholderCount={1}
useData={data}
/>
</div>
{bulkDeleteLinksModal && (