+ className={`border primary-btn-gradient select-none duration-100 bg-black border-[#0071B7] hover:border-[#059bf8] rounded-lg text-center px-4 py-2 text-slate-200 hover:text-white `}
+ >
+
Already have an account?
diff --git a/pages/search/[query].tsx b/pages/search/[query].tsx
deleted file mode 100644
index ad04ed7..0000000
--- a/pages/search/[query].tsx
+++ /dev/null
@@ -1,117 +0,0 @@
-import FilterSearchDropdown from "@/components/FilterSearchDropdown";
-import LinkCard from "@/components/LinkCard";
-import SortDropdown from "@/components/SortDropdown";
-import useLinks from "@/hooks/useLinks";
-import MainLayout from "@/layouts/MainLayout";
-import useLinkStore from "@/store/links";
-import { Sort } from "@/types/global";
-import { faFilter, faSearch, faSort } from "@fortawesome/free-solid-svg-icons";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { useRouter } from "next/router";
-import { useState } from "react";
-
-export default function Links() {
- const { links } = useLinkStore();
-
- const router = useRouter();
-
- const [searchFilter, setSearchFilter] = useState({
- name: true,
- url: true,
- description: true,
- tags: true,
- });
-
- const [filterDropdown, setFilterDropdown] = useState(false);
- const [sortDropdown, setSortDropdown] = useState(false);
- const [sortBy, setSortBy] = useState(Sort.DateNewestFirst);
-
- useLinks({
- sort: sortBy,
- searchQueryString: router.query.query as string,
- searchByName: searchFilter.name,
- searchByUrl: searchFilter.url,
- searchByDescription: searchFilter.description,
- searchByTags: searchFilter.tags,
- });
-
- return (
-
-
-
-
-
-
-
-
setFilterDropdown(!filterDropdown)}
- id="filter-dropdown"
- className="inline-flex rounded-md cursor-pointer hover:bg-slate-200 hover:dark:bg-neutral-700 duration-100 p-1"
- >
-
-
-
- {filterDropdown ? (
-
- ) : null}
-
-
-
-
setSortDropdown(!sortDropdown)}
- id="sort-dropdown"
- className="inline-flex rounded-md cursor-pointer hover:bg-slate-200 hover:dark:bg-neutral-700 duration-100 p-1"
- >
-
-
-
- {sortDropdown ? (
-
setSortDropdown(!sortDropdown)}
- />
- ) : null}
-
-
-
- {links[0] ? (
-
- {links.map((e, i) => {
- return ;
- })}
-
- ) : (
-
- Nothing found.{" "}
-
- ¯\_(ツ)_/¯
-
-
- )}
-
-
- );
-}
diff --git a/pages/search/index.tsx b/pages/search/index.tsx
index f1b1777..01ca6ef 100644
--- a/pages/search/index.tsx
+++ b/pages/search/index.tsx
@@ -1,10 +1,117 @@
+import FilterSearchDropdown from "@/components/FilterSearchDropdown";
+import LinkCard from "@/components/LinkCard";
+import SortDropdown from "@/components/SortDropdown";
+import useLinks from "@/hooks/useLinks";
+import MainLayout from "@/layouts/MainLayout";
+import useLinkStore from "@/store/links";
+import { Sort } from "@/types/global";
+import { faFilter, faSearch, faSort } from "@fortawesome/free-solid-svg-icons";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useRouter } from "next/router";
-import { useEffect } from "react";
+import { useState } from "react";
+
+export default function Search() {
+ const { links } = useLinkStore();
-export default function Home() {
const router = useRouter();
- useEffect(() => {
- router.push("/links");
- }, []);
+ const [searchFilter, setSearchFilter] = useState({
+ name: true,
+ url: true,
+ description: true,
+ tags: true,
+ });
+
+ const [filterDropdown, setFilterDropdown] = useState(false);
+ const [sortDropdown, setSortDropdown] = useState(false);
+ const [sortBy, setSortBy] = useState(Sort.DateNewestFirst);
+
+ useLinks({
+ sort: sortBy,
+ searchQueryString: decodeURIComponent(router.query.q as string),
+ searchByName: searchFilter.name,
+ searchByUrl: searchFilter.url,
+ searchByDescription: searchFilter.description,
+ searchByTags: searchFilter.tags,
+ });
+
+ return (
+
+
+
+
+
+
+
+
setFilterDropdown(!filterDropdown)}
+ id="filter-dropdown"
+ className="inline-flex rounded-md cursor-pointer hover:bg-slate-200 hover:dark:bg-neutral-700 duration-100 p-1"
+ >
+
+
+
+ {filterDropdown ? (
+
+ ) : null}
+
+
+
+
setSortDropdown(!sortDropdown)}
+ id="sort-dropdown"
+ className="inline-flex rounded-md cursor-pointer hover:bg-slate-200 hover:dark:bg-neutral-700 duration-100 p-1"
+ >
+
+
+
+ {sortDropdown ? (
+
setSortDropdown(!sortDropdown)}
+ />
+ ) : null}
+
+
+
+ {links[0] ? (
+
+ {links.map((e, i) => {
+ return ;
+ })}
+
+ ) : (
+
+ Nothing found.{" "}
+
+ ¯\_(ツ)_/¯
+
+
+ )}
+
+
+ );
}
diff --git a/pages/settings/account.tsx b/pages/settings/account.tsx
index 5850efa..20434ea 100644
--- a/pages/settings/account.tsx
+++ b/pages/settings/account.tsx
@@ -269,7 +269,7 @@ export default function Account() {
Import your data from other platforms.
setImportDropdown(!importDropdown)}
+ onClick={() => setImportDropdown(true)}
className="w-fit relative"
id="import-dropdown"
>
@@ -387,6 +387,33 @@ export default function Account() {
label="Save"
className="mt-2 mx-auto lg:mx-0"
/>
+
+
+
+
+
+
+
+ This will permanently delete ALL the Links, Collections, Tags, and
+ archived data you own.{" "}
+ {process.env.NEXT_PUBLIC_STRIPE_IS_ACTIVE
+ ? "It will also cancel your subscription. "
+ : undefined}{" "}
+ You will be prompted to enter your password before the deletion
+ process.
+
+
+
+
Delete Your Account
+
+
);
diff --git a/pages/settings/delete.tsx b/pages/settings/delete.tsx
new file mode 100644
index 0000000..e072ab1
--- /dev/null
+++ b/pages/settings/delete.tsx
@@ -0,0 +1,152 @@
+import { useState } from "react";
+import { toast } from "react-hot-toast";
+import TextInput from "@/components/TextInput";
+import CenteredForm from "@/layouts/CenteredForm";
+import { signOut, useSession } from "next-auth/react";
+import Link from "next/link";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { faChevronLeft } from "@fortawesome/free-solid-svg-icons";
+
+export default function Password() {
+ const [password, setPassword] = useState("");
+ const [comment, setComment] = useState
();
+ const [feedback, setFeedback] = useState();
+
+ const [submitLoader, setSubmitLoader] = useState(false);
+
+ const { data } = useSession();
+
+ const submit = async () => {
+ const body = {
+ password,
+ cancellation_details: {
+ comment,
+ feedback,
+ },
+ };
+
+ if (password == "") {
+ return toast.error("Please fill the required fields.");
+ }
+
+ setSubmitLoader(true);
+
+ const load = toast.loading("Deleting everything, please wait...");
+
+ const response = await fetch(`/api/v1/users/${data?.user.id}`, {
+ method: "DELETE",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify(body),
+ });
+
+ const message = (await response.json()).response;
+
+ toast.dismiss(load);
+
+ if (response.ok) {
+ signOut();
+ } else toast.error(message);
+
+ setSubmitLoader(false);
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+
+ This will permanently delete all the Links, Collections, Tags, and
+ archived data you own. It will also log you out
+ {process.env.NEXT_PUBLIC_STRIPE_IS_ACTIVE
+ ? " and cancel your subscription"
+ : undefined}
+ . This action is irreversible!
+
+
+
+
+ Confirm Your Password
+
+
+
setPassword(e.target.value)}
+ placeholder="••••••••••••••"
+ type="password"
+ />
+
+
+ {process.env.NEXT_PUBLIC_STRIPE_IS_ACTIVE ? (
+
+ ) : undefined}
+
+
+
+
+ );
+}
diff --git a/pages/settings/password.tsx b/pages/settings/password.tsx
index b68228c..d54a9b0 100644
--- a/pages/settings/password.tsx
+++ b/pages/settings/password.tsx
@@ -16,7 +16,7 @@ export default function Password() {
const submit = async () => {
if (newPassword == "" || newPassword2 == "") {
- toast.error("Please fill all the fields.");
+ return toast.error("Please fill all the fields.");
}
if (newPassword !== newPassword2)
diff --git a/pages/subscribe.tsx b/pages/subscribe.tsx
index 56ef8f6..7d44614 100644
--- a/pages/subscribe.tsx
+++ b/pages/subscribe.tsx
@@ -15,7 +15,7 @@ export default function Subscribe() {
const { data, status } = useSession();
const router = useRouter();
- async function loginUser() {
+ async function submit() {
setSubmitLoader(true);
const redirectionToast = toast.loading("Redirecting to Stripe...");
@@ -32,11 +32,13 @@ export default function Subscribe() {
process.env.NEXT_PUBLIC_TRIAL_PERIOD_DAYS || 14
}-day free trial, cancel anytime!`}
>
-
-
+
+
Subscribe to Linkwarden!
+
+
You will be redirected to Stripe, feel free to reach out to us at{" "}
@@ -84,24 +86,27 @@ export default function Subscribe() {
Billed {plan === Plan.monthly ? "Monthly" : "Yearly"}
-
-
Total:
-
-
- {process.env.NEXT_PUBLIC_TRIAL_PERIOD_DAYS}-day free trial, then
- ${plan === Plan.monthly ? "4 per month" : "36 annually"}
-
-
+ VAT if applicable
-
-
+
-
+
signOut()}
diff --git a/pages/tags/[id].tsx b/pages/tags/[id].tsx
index 537b65f..b8435f5 100644
--- a/pages/tags/[id].tsx
+++ b/pages/tags/[id].tsx
@@ -86,7 +86,7 @@ export default function Index() {
-
+
) : null}
diff --git a/styles/globals.css b/styles/globals.css
index c85afa2..bf7e9a9 100644
--- a/styles/globals.css
+++ b/styles/globals.css
@@ -231,3 +231,11 @@
.sky-shadow {
box-shadow: 0px 0px 3px #0ea5e9;
}
+
+.primary-btn-gradient {
+ box-shadow: inset 0px -10px 10px #0071b7;
+}
+
+.primary-btn-gradient:hover {
+ box-shadow: inset 0px -15px 10px #059bf8;
+}
diff --git a/types/enviornment.d.ts b/types/enviornment.d.ts
index 8e4c9db..985958f 100644
--- a/types/enviornment.d.ts
+++ b/types/enviornment.d.ts
@@ -8,6 +8,7 @@ declare global {
PAGINATION_TAKE_COUNT?: string;
STORAGE_FOLDER?: string;
AUTOSCROLL_TIMEOUT?: string;
+ IMPORT_SIZE_LIMIT?: string;
SPACES_KEY?: string;
SPACES_SECRET?: string;
diff --git a/types/global.ts b/types/global.ts
index 4bec3db..09959c5 100644
--- a/types/global.ts
+++ b/types/global.ts
@@ -1,4 +1,5 @@
import { Collection, Link, Tag, User } from "@prisma/client";
+import Stripe from "stripe";
type OptionalExcluding
= Partial &
Pick;
@@ -96,3 +97,11 @@ export enum Plan {
monthly,
yearly,
}
+
+export type DeleteUserBody = {
+ password: string;
+ cancellation_details?: {
+ comment?: string;
+ feedback?: Stripe.SubscriptionCancelParams.CancellationDetails.Feedback;
+ };
+};