bug fixed + redesigned profile settings page

This commit is contained in:
daniel31x13 2023-10-17 16:02:07 -04:00
parent 6259405045
commit f5eaee8dc0
7 changed files with 197 additions and 14 deletions

View File

@ -20,6 +20,7 @@ import usePermissions from "@/hooks/usePermissions";
import { toast } from "react-hot-toast"; import { toast } from "react-hot-toast";
import isValidUrl from "@/lib/client/isValidUrl"; import isValidUrl from "@/lib/client/isValidUrl";
import Link from "next/link"; import Link from "next/link";
import unescapeString from "@/lib/client/unescapeString";
type Props = { type Props = {
link: LinkIncludingShortenedCollectionAndTags; link: LinkIncludingShortenedCollectionAndTags;
@ -161,7 +162,7 @@ export default function LinkCard({ link, count, className }: Props) {
{count + 1} {count + 1}
</p> </p>
<p className="text-lg text-black dark:text-white truncate capitalize w-full pr-8"> <p className="text-lg text-black dark:text-white truncate capitalize w-full pr-8">
{link.name || link.description} {unescapeString(link.name || link.description)}
</p> </p>
</div> </div>
<Link <Link

View File

@ -3,6 +3,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import Image from "next/image"; import Image from "next/image";
import { Link as LinkType, Tag } from "@prisma/client"; import { Link as LinkType, Tag } from "@prisma/client";
import isValidUrl from "@/lib/client/isValidUrl"; import isValidUrl from "@/lib/client/isValidUrl";
import unescapeString from "@/lib/client/unescapeString";
interface LinksIncludingTags extends LinkType { interface LinksIncludingTags extends LinkType {
tags: Tag[]; tags: Tag[];
@ -60,12 +61,12 @@ export default function LinkCard({ link, count }: Props) {
<div className="flex items-baseline gap-1"> <div className="flex items-baseline gap-1">
<p className="text-xs text-gray-500">{count + 1}</p> <p className="text-xs text-gray-500">{count + 1}</p>
<p className="text-lg text-black"> <p className="text-lg text-black">
{link.name || link.description} {unescapeString(link.name || link.description)}
</p> </p>
</div> </div>
<p className="text-gray-500 text-sm font-medium"> <p className="text-gray-500 text-sm font-medium">
{link.description} {unescapeString(link.description)}
</p> </p>
<div className="flex gap-3 items-center flex-wrap my-3"> <div className="flex gap-3 items-center flex-wrap my-3">
<div className="flex gap-1 items-center flex-wrap mt-1"> <div className="flex gap-1 items-center flex-wrap mt-1">

View File

@ -34,7 +34,7 @@ export default function SettingsSidebar({ className }: { className?: string }) {
return ( return (
<div <div
className={`bg-gray-100 dark:bg-neutral-800 h-full w-64 xl:w-80 overflow-y-auto border-solid border dark:border-neutral-800 border-r-sky-100 dark:border-r-neutral-700 p-5 z-20 flex flex-col gap-5 justify-between ${className}`} className={`dark:bg-neutral-900 bg-white h-full w-64 xl:w-80 overflow-y-auto border-solid border dark:border-neutral-900 border-r-sky-100 dark:border-r-neutral-700 p-5 z-20 flex flex-col gap-5 justify-between ${className}`}
> >
<div className="flex flex-col gap-1"> <div className="flex flex-col gap-1">
<Link href="/settings/profile"> <Link href="/settings/profile">

View File

@ -27,8 +27,8 @@ export default function SubmitButton({
if (!loading) onClick(); if (!loading) onClick();
}} }}
> >
{icon && <FontAwesomeIcon icon={icon} className="h-5" />} {icon && <FontAwesomeIcon icon={icon} className="h-5 select-none" />}
<p className="text-center w-full">{label}</p> <p className="text-center w-full select-none">{label}</p>
</div> </div>
); );
} }

View File

@ -5,7 +5,8 @@ import useModalStore from "@/store/modals";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import ClickAwayHandler from "@/components/ClickAwayHandler"; import ClickAwayHandler from "@/components/ClickAwayHandler";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faBars } from "@fortawesome/free-solid-svg-icons"; import { faBars, faChevronLeft } from "@fortawesome/free-solid-svg-icons";
import Link from "next/link";
interface Props { interface Props {
children: ReactNode; children: ReactNode;
@ -38,12 +39,12 @@ export default function SettingsLayout({ children }: Props) {
<> <>
<ModalManagement /> <ModalManagement />
<div className="flex"> <div className="flex max-w-screen-lg mx-auto">
<div className="hidden lg:block"> <div className="hidden lg:block">
<SettingsSidebar className="fixed top-0" /> <SettingsSidebar />
</div> </div>
<div className="w-full flex flex-col h-screen lg:ml-64 xl:ml-80 p-5"> <div className="w-full flex flex-col h-screen p-5">
<div className="flex gap-3"> <div className="flex gap-3">
<div <div
onClick={toggleSidebar} onClick={toggleSidebar}
@ -52,11 +53,18 @@ export default function SettingsLayout({ children }: Props) {
<FontAwesomeIcon icon={faBars} className="w-5 h-5" /> <FontAwesomeIcon icon={faBars} className="w-5 h-5" />
</div> </div>
<Link
href="/dashboard"
className="inline-flex gap-1 items-center select-none cursor-pointer p-2 text-gray-500 dark:text-gray-300 rounded-md duration-100 hover:bg-slate-200 dark:hover:bg-neutral-700"
>
<FontAwesomeIcon icon={faChevronLeft} className="w-5 h-5" />
</Link>
<p className="capitalize text-3xl"> <p className="capitalize text-3xl">
{router.asPath.split("/").pop()} Settings {router.asPath.split("/").pop()} Settings
</p> </p>
</div> </div>
<hr className="my-3" /> <hr className="my-3 border-1 border-sky-100 dark:border-neutral-700" />
{children} {children}
{sidebar ? ( {sidebar ? (

View File

@ -52,7 +52,7 @@ export default function App({
reverseOrder={false} reverseOrder={false}
toastOptions={{ toastOptions={{
className: className:
"border border-sky-100 dark:dark:border-neutral-700 dark:bg-neutral-900 dark:text-white", "border border-sky-100 dark:border-neutral-700 dark:bg-neutral-900 dark:text-white",
}} }}
/> />
<Component {...pageProps} /> <Component {...pageProps} />

View File

@ -1,10 +1,183 @@
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 SettingsLayout from "@/layouts/SettingsLayout";
import React from "react"; 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() { 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<AccountSettings>({
...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 ( return (
<SettingsLayout> <SettingsLayout>
<div>profile</div> <div className="flex flex-col gap-3 justify-between sm:w-[35rem] w-80 mx-auto">
<div className="grid sm:grid-cols-2 gap-3 auto-rows-auto">
<div className="flex flex-col gap-3">
<div>
<p className="text-sm text-black dark:text-white mb-2">
Display Name
</p>
<TextInput
value={user.name || ""}
onChange={(e) => setUser({ ...user, name: e.target.value })}
/>
</div>
<div>
<p className="text-sm text-black dark:text-white mb-2">
Username
</p>
<TextInput
value={user.username || ""}
onChange={(e) => setUser({ ...user, username: e.target.value })}
/>
</div>
{emailEnabled ? (
<div>
<p className="text-sm text-black dark:text-white mb-2">Email</p>
<TextInput
value={user.email || ""}
onChange={(e) => setUser({ ...user, email: e.target.value })}
/>
</div>
) : undefined}
{user.email !== account.email ? (
<p className="text-gray-500">
You will need to log back in after you apply this Email.
</p>
) : undefined}
</div>
<div className="sm:row-span-2 sm:justify-self-center mx-auto mb-3">
<p className="text-sm text-black dark:text-white mb-2 text-center">
Profile Photo
</p>
<div className="w-28 h-28 flex items-center justify-center rounded-full relative">
<ProfilePhoto
priority={true}
src={user.profilePic}
className="h-auto border-none w-28"
status={handleProfileStatus}
/>
{profileStatus && (
<div
onClick={() =>
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"
>
<FontAwesomeIcon icon={faClose} className="w-3 h-3" />
</div>
)}
<div className="absolute -bottom-3 left-0 right-0 mx-auto w-fit text-center">
<label className="border border-slate-200 dark:border-neutral-700 rounded-md bg-white dark:bg-neutral-800 px-2 text-center select-none cursor-pointer duration-100 hover:border-sky-300 hover:dark:border-sky-600">
Browse...
<input
type="file"
name="photo"
id="upload-photo"
accept=".png, .jpeg, .jpg"
className="hidden"
onChange={handleImageUpload}
/>
</label>
</div>
</div>
</div>
</div>
<SubmitButton
onClick={submit}
loading={submitLoader}
label="Apply Settings"
icon={faPenToSquare}
className="mt-2 mx-auto lg:mx-0"
/>
</div>
</SettingsLayout> </SettingsLayout>
); );
} }