diff --git a/components/Modal/User/PrivacySettings.tsx b/components/Modal/User/PrivacySettings.tsx
index df28907..ce61782 100644
--- a/components/Modal/User/PrivacySettings.tsx
+++ b/components/Modal/User/PrivacySettings.tsx
@@ -12,7 +12,6 @@ import SubmitButton from "../../SubmitButton";
import { toast } from "react-hot-toast";
import Link from "next/link";
import ClickAwayHandler from "@/components/ClickAwayHandler";
-import useInitialData from "@/hooks/useInitialData";
type Props = {
toggleSettingsModal: Function;
diff --git a/components/SettingsSidebar.tsx b/components/SettingsSidebar.tsx
index 795132a..d45b01e 100644
--- a/components/SettingsSidebar.tsx
+++ b/components/SettingsSidebar.tsx
@@ -34,13 +34,13 @@ export default function SettingsSidebar({ className }: { className?: string }) {
return (
+
@@ -60,11 +60,13 @@ export default function SettingsLayout({ children }: Props) {
-
+
{router.asPath.split("/").pop()} Settings
+
+
{children}
{sidebar ? (
diff --git a/pages/settings/account.tsx b/pages/settings/account.tsx
new file mode 100644
index 0000000..6108aeb
--- /dev/null
+++ b/pages/settings/account.tsx
@@ -0,0 +1,393 @@
+import { useState, useEffect } from "react";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { faClose, faPenToSquare } from "@fortawesome/free-solid-svg-icons";
+import useAccountStore from "@/store/account";
+import { AccountSettings } from "@/types/global";
+import { toast } from "react-hot-toast";
+import SettingsLayout from "@/layouts/SettingsLayout";
+import TextInput from "@/components/TextInput";
+import { resizeImage } from "@/lib/client/resizeImage";
+import ProfilePhoto from "@/components/ProfilePhoto";
+import SubmitButton from "@/components/SubmitButton";
+import { useSession, signOut } from "next-auth/react";
+import React from "react";
+import { MigrationFormat, MigrationRequest } from "@/types/global";
+import Link from "next/link";
+import ClickAwayHandler from "@/components/ClickAwayHandler";
+import Checkbox from "@/components/Checkbox";
+
+export default function account() {
+ const { update, data } = useSession();
+
+ const emailEnabled = process.env.NEXT_PUBLIC_EMAIL_PROVIDER;
+
+ const [profileStatus, setProfileStatus] = useState(true);
+ const [submitLoader, setSubmitLoader] = useState(false);
+
+ const handleProfileStatus = (e: boolean) => {
+ setProfileStatus(!e);
+ };
+
+ const { account, updateAccount } = useAccountStore();
+
+ const [user, setUser] = useState
(
+ !objectIsEmpty(account)
+ ? account
+ : ({
+ // @ts-ignore
+ id: null,
+ name: "",
+ username: "",
+ email: "",
+ emailVerified: null,
+ image: null,
+ isPrivate: true,
+ // @ts-ignore
+ createdAt: null,
+ whitelistedUsers: [],
+ profilePic: "",
+ } as unknown as AccountSettings)
+ );
+
+ function objectIsEmpty(obj: object) {
+ return Object.keys(obj).length === 0;
+ }
+
+ useEffect(() => {
+ if (!objectIsEmpty(account)) setUser({ ...account });
+ }, [account]);
+
+ const handleImageUpload = async (e: any) => {
+ const file: File = e.target.files[0];
+ const fileExtension = file.name.split(".").pop()?.toLowerCase();
+ const allowedExtensions = ["png", "jpeg", "jpg"];
+ if (allowedExtensions.includes(fileExtension as string)) {
+ const resizedFile = await resizeImage(file);
+ if (
+ resizedFile.size < 1048576 // 1048576 Bytes == 1MB
+ ) {
+ const reader = new FileReader();
+ reader.onload = () => {
+ setUser({ ...user, profilePic: reader.result as string });
+ };
+ reader.readAsDataURL(resizedFile);
+ } else {
+ toast.error("Please select a PNG or JPEG file thats less than 1MB.");
+ }
+ } else {
+ toast.error("Invalid file format.");
+ }
+ };
+
+ const submit = async () => {
+ setSubmitLoader(true);
+
+ const load = toast.loading("Applying...");
+
+ const response = await updateAccount({
+ ...user,
+ });
+
+ toast.dismiss(load);
+
+ if (response.ok) {
+ toast.success("Settings Applied!");
+
+ if (user.email !== account.email) {
+ update({
+ id: data?.user.id,
+ });
+
+ signOut();
+ } else if (
+ user.username !== account.username ||
+ user.name !== account.name
+ )
+ update({
+ id: data?.user.id,
+ });
+ } else toast.error(response.data as string);
+ setSubmitLoader(false);
+ };
+
+ const [importDropdown, setImportDropdown] = useState(false);
+
+ const importBookmarks = async (e: any, format: MigrationFormat) => {
+ const file: File = e.target.files[0];
+
+ if (file) {
+ var reader = new FileReader();
+ reader.readAsText(file, "UTF-8");
+ reader.onload = async function (e) {
+ const load = toast.loading("Importing...");
+
+ const request: string = e.target?.result as string;
+
+ const body: MigrationRequest = {
+ format,
+ data: request,
+ };
+
+ const response = await fetch("/api/migration", {
+ method: "POST",
+ body: JSON.stringify(body),
+ });
+
+ const data = await response.json();
+
+ toast.dismiss(load);
+
+ toast.success("Imported the Bookmarks! Reloading the page...");
+
+ setImportDropdown(false);
+
+ setTimeout(() => {
+ location.reload();
+ }, 2000);
+ };
+ reader.onerror = function (e) {
+ console.log("Error:", e);
+ };
+ }
+ };
+
+ const [whitelistedUsersTextbox, setWhiteListedUsersTextbox] = useState("");
+
+ useEffect(() => {
+ setWhiteListedUsersTextbox(account?.whitelistedUsers?.join(", "));
+ }, [account]);
+
+ useEffect(() => {
+ setUser({
+ ...user,
+ whitelistedUsers: stringToArray(whitelistedUsersTextbox),
+ });
+ }, [whitelistedUsersTextbox]);
+
+ const stringToArray = (str: string) => {
+ const stringWithoutSpaces = str?.replace(/\s+/g, "");
+
+ const wordsArray = stringWithoutSpaces?.split(",");
+
+ return wordsArray;
+ };
+
+ return (
+
+
+
+
+
+
+ Display Name
+
+
setUser({ ...user, name: e.target.value })}
+ />
+
+
+
+ Username
+
+
setUser({ ...user, username: e.target.value })}
+ />
+
+
+ {emailEnabled ? (
+
+
Email
+
setUser({ ...user, email: e.target.value })}
+ />
+
+ ) : undefined}
+
+ {user.email !== account.email ? (
+
+ You will need to log back in after you apply this Email.
+
+ ) : undefined}
+
+
+
+
+ Profile Photo
+
+
+
+ {profileStatus && (
+
+ setUser({
+ ...user,
+ profilePic: "",
+ })
+ }
+ className="absolute top-1 left-1 w-5 h-5 flex items-center justify-center border p-1 border-slate-200 dark:border-neutral-700 rounded-full bg-white dark:bg-neutral-800 text-center select-none cursor-pointer duration-100 hover:text-red-500"
+ >
+
+
+ )}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Import your data from other platforms.
+
+
+
+
+
+
+ Download your data instantly.
+
+
+
+ Export Data
+
+
+
+
+
+
+
+
+
+ Profile Visibility
+
+
+
+
+
+
setUser({ ...user, isPrivate: !user.isPrivate })}
+ />
+
+
+ This will limit who can find and add you to other Collections.
+
+
+ {user.isPrivate && (
+
+
+ Whitelisted Users
+
+
+ Please provide the Username of the users you wish to grant
+ visibility to your profile. Separated by comma.
+
+
+ )}
+
+
+
+
+
+ );
+}
diff --git a/pages/settings/appearance.tsx b/pages/settings/appearance.tsx
index 5eb17f9..ea5f0a3 100644
--- a/pages/settings/appearance.tsx
+++ b/pages/settings/appearance.tsx
@@ -1,10 +1,36 @@
import SettingsLayout from "@/layouts/SettingsLayout";
-import React from "react";
+import { useTheme } from "next-themes";
export default function appearance() {
+ const { theme, setTheme } = useTheme();
+
+ const handleToggle = () => {
+ if (theme === "dark") {
+ setTheme("light");
+ } else {
+ setTheme("dark");
+ }
+ };
+
return (
- appearance
+
+
setTheme("dark")}
+ >
+
Dark Theme
+
+ {/*
*/}
+
+
setTheme("light")}
+ >
+
Light Theme
+ {/*
*/}
+
+
);
}
diff --git a/pages/settings/billing.tsx b/pages/settings/billing.tsx
index cc9f040..c2094c0 100644
--- a/pages/settings/billing.tsx
+++ b/pages/settings/billing.tsx
@@ -12,7 +12,29 @@ export default function billing() {
return (
- Billing
+
);
}
diff --git a/pages/settings/migration.tsx b/pages/settings/migration.tsx
deleted file mode 100644
index 023c2ca..0000000
--- a/pages/settings/migration.tsx
+++ /dev/null
@@ -1,10 +0,0 @@
-import SettingsLayout from "@/layouts/SettingsLayout";
-import React from "react";
-
-export default function appearance() {
- return (
-
- Migration
-
- );
-}
diff --git a/pages/settings/password.tsx b/pages/settings/password.tsx
index bffea57..d17d23f 100644
--- a/pages/settings/password.tsx
+++ b/pages/settings/password.tsx
@@ -1,10 +1,83 @@
import SettingsLayout from "@/layouts/SettingsLayout";
-import React from "react";
+import { useState } from "react";
+import useAccountStore from "@/store/account";
+import { faPenToSquare } from "@fortawesome/free-regular-svg-icons";
+import SubmitButton from "@/components/SubmitButton";
+import { toast } from "react-hot-toast";
+import TextInput from "@/components/TextInput";
export default function password() {
+ const [newPassword, setNewPassword1] = useState("");
+ const [newPassword2, setNewPassword2] = useState("");
+
+ const [submitLoader, setSubmitLoader] = useState(false);
+
+ const { account, updateAccount } = useAccountStore();
+
+ const submit = async () => {
+ if (newPassword == "" || newPassword2 == "") {
+ toast.error("Please fill all the fields.");
+ }
+
+ if (newPassword !== newPassword2)
+ return toast.error("Passwords do not match.");
+ else if (newPassword.length < 8)
+ return toast.error("Passwords must be at least 8 characters.");
+
+ setSubmitLoader(true);
+
+ const load = toast.loading("Applying...");
+
+ const response = await updateAccount({
+ ...account,
+ newPassword,
+ });
+
+ toast.dismiss(load);
+
+ if (response.ok) {
+ toast.success("Settings Applied!");
+ setNewPassword1("");
+ setNewPassword2("");
+ } else toast.error(response.data as string);
+
+ setSubmitLoader(false);
+ };
+
return (
- password
+
+ To change your password, please fill out the following. Your password
+ should be at least 8 characters.
+
+
+
New Password
+
+
setNewPassword1(e.target.value)}
+ placeholder="••••••••••••••"
+ type="password"
+ />
+
+
+ Confirm New Password
+
+
+ setNewPassword2(e.target.value)}
+ placeholder="••••••••••••••"
+ type="password"
+ />
+
+
+
);
}
diff --git a/pages/settings/privacy.tsx b/pages/settings/privacy.tsx
deleted file mode 100644
index b57f9d5..0000000
--- a/pages/settings/privacy.tsx
+++ /dev/null
@@ -1,10 +0,0 @@
-import SettingsLayout from "@/layouts/SettingsLayout";
-import React from "react";
-
-export default function privacy() {
- return (
-
- privacy
-
- );
-}
diff --git a/pages/settings/profile.tsx b/pages/settings/profile.tsx
deleted file mode 100644
index 7cc3f8b..0000000
--- a/pages/settings/profile.tsx
+++ /dev/null
@@ -1,183 +0,0 @@
-import { useState, useEffect } from "react";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { faClose, faPenToSquare } from "@fortawesome/free-solid-svg-icons";
-import useAccountStore from "@/store/account";
-import { AccountSettings } from "@/types/global";
-import { toast } from "react-hot-toast";
-import SettingsLayout from "@/layouts/SettingsLayout";
-import TextInput from "@/components/TextInput";
-import { resizeImage } from "@/lib/client/resizeImage";
-import ProfilePhoto from "@/components/ProfilePhoto";
-import SubmitButton from "@/components/SubmitButton";
-import { useSession, signOut } from "next-auth/react";
-
-export default function profile() {
- const { update, data } = useSession();
-
- const emailEnabled = process.env.NEXT_PUBLIC_EMAIL_PROVIDER;
-
- const [profileStatus, setProfileStatus] = useState(true);
- const [submitLoader, setSubmitLoader] = useState(false);
-
- const handleProfileStatus = (e: boolean) => {
- setProfileStatus(!e);
- };
-
- const { account, updateAccount } = useAccountStore();
-
- const [user, setUser] = useState({
- ...account,
- });
-
- useEffect(() => {
- setUser({ ...account });
- }, [account]);
-
- const handleImageUpload = async (e: any) => {
- const file: File = e.target.files[0];
- const fileExtension = file.name.split(".").pop()?.toLowerCase();
- const allowedExtensions = ["png", "jpeg", "jpg"];
- if (allowedExtensions.includes(fileExtension as string)) {
- const resizedFile = await resizeImage(file);
- if (
- resizedFile.size < 1048576 // 1048576 Bytes == 1MB
- ) {
- const reader = new FileReader();
- reader.onload = () => {
- setUser({ ...user, profilePic: reader.result as string });
- };
- reader.readAsDataURL(resizedFile);
- } else {
- toast.error("Please select a PNG or JPEG file thats less than 1MB.");
- }
- } else {
- toast.error("Invalid file format.");
- }
- };
-
- const submit = async () => {
- setSubmitLoader(true);
-
- const load = toast.loading("Applying...");
-
- const response = await updateAccount({
- ...user,
- });
-
- toast.dismiss(load);
-
- if (response.ok) {
- toast.success("Settings Applied!");
-
- if (user.email !== account.email) {
- update({
- id: data?.user.id,
- });
-
- signOut();
- } else if (
- user.username !== account.username ||
- user.name !== account.name
- )
- update({
- id: data?.user.id,
- });
-
- setUser({ ...user, newPassword: undefined });
- } else toast.error(response.data as string);
- setSubmitLoader(false);
- };
-
- return (
-
-
-
-
-
-
- Display Name
-
-
setUser({ ...user, name: e.target.value })}
- />
-
-
-
- Username
-
-
setUser({ ...user, username: e.target.value })}
- />
-
-
- {emailEnabled ? (
-
-
Email
-
setUser({ ...user, email: e.target.value })}
- />
-
- ) : undefined}
-
- {user.email !== account.email ? (
-
- You will need to log back in after you apply this Email.
-
- ) : undefined}
-
-
-
-
- Profile Photo
-
-
-
- {profileStatus && (
-
- setUser({
- ...user,
- profilePic: "",
- })
- }
- className="absolute top-1 left-1 w-5 h-5 flex items-center justify-center border p-1 border-slate-200 dark:border-neutral-700 rounded-full bg-white dark:bg-neutral-800 text-center select-none cursor-pointer duration-100 hover:text-red-500"
- >
-
-
- )}
-
-
-
-
-
-
-
-
-
-
- );
-}