Add more user settings

This commit is contained in:
Ategon 2025-02-07 22:41:10 -05:00
parent 7ff860f3cc
commit 6a1f3abaa5
5 changed files with 106 additions and 23 deletions

View file

@ -5,10 +5,7 @@ const nextConfig: NextConfig = {
remotePatterns: [
{
protocol: "https",
hostname: "static-cdn.jtvnw.net",
port: "",
pathname: "/**",
search: "",
hostname: "**",
},
],
},

View file

@ -1,17 +1,24 @@
"use client";
import Editor from "@/components/editor";
import sanitizeHtml from "sanitize-html";
import { getCookie, hasCookie } from "@/helpers/cookie";
import { UserType } from "@/types/UserType";
import { Button, Form, Input } from "@nextui-org/react";
import { redirect, usePathname } from "next/navigation";
import { useEffect, useState } from "react";
import { toast } from "react-toastify";
import { LoaderCircle } from "lucide-react";
export default function UserPage() {
const [user, setUser] = useState<UserType>();
const [profilePicture, setProfilePicture] = useState("");
const [name, setName] = useState("");
const [bannerPicture, setBannerPicture] = useState("");
const [bio, setBio] = useState("");
const [errors] = useState({});
const pathname = usePathname();
const [waitingSave, setWaitingSave] = useState(false);
useEffect(() => {
loadUser();
@ -37,6 +44,9 @@ export default function UserPage() {
setUser(data);
setProfilePicture(data.profilePicture ?? "");
setBannerPicture(data.bannerPicture ?? "");
setBio(data.bio ?? "");
setName(data.name ?? "");
} else {
setUser(undefined);
}
@ -49,14 +59,26 @@ export default function UserPage() {
"Loading settings..."
) : (
<Form
className="w-full max-w-xs flex flex-col gap-4"
className="w-full max-w-2xl flex flex-col gap-4"
validationErrors={errors}
onReset={() => {
setProfilePicture(user.profilePicture ?? "");
setBannerPicture(user.bannerPicture ?? "");
setBio(user.bio ?? "");
setName(user.name ?? "");
}}
onSubmit={async (e) => {
e.preventDefault();
const sanitizedBio = sanitizeHtml(bio);
if (!name) {
toast.error("You need to enter a name");
return;
}
setWaitingSave(true);
const response = await fetch(
process.env.NEXT_PUBLIC_MODE === "PROD"
? "https://d2jam.com/api/v1/user"
@ -64,7 +86,10 @@ export default function UserPage() {
{
body: JSON.stringify({
slug: user.slug,
name: name,
bio: sanitizedBio,
profilePicture: profilePicture,
bannerPicture: bannerPicture,
}),
method: "PUT",
headers: {
@ -77,11 +102,28 @@ export default function UserPage() {
if (response.ok) {
toast.success("Changed settings");
setUser(await response.json());
setWaitingSave(false);
} else {
toast.error("Failed to update settings");
setWaitingSave(false);
}
}}
>
<p className="text-3xl">Settings</p>
<Input
label="Name"
labelPlacement="outside"
name="name"
placeholder="Enter a name"
type="text"
value={name}
onValueChange={setName}
/>
<p>Bio</p>
<Editor content={bio} setContent={setBio} />
<Input
label="Profile Picture"
labelPlacement="outside"
@ -92,9 +134,23 @@ export default function UserPage() {
onValueChange={setProfilePicture}
/>
<Input
label="Banner Picture"
labelPlacement="outside"
name="bannerPicture"
placeholder="Enter a url to an image"
type="text"
value={bannerPicture}
onValueChange={setBannerPicture}
/>
<div className="flex gap-2">
<Button color="primary" type="submit">
Save
{waitingSave ? (
<LoaderCircle className="animate-spin" size={16} />
) : (
<p>Save</p>
)}
</Button>
<Button type="reset" variant="flat">
Reset

View file

@ -1,6 +1,8 @@
"use client";
import { UserType } from "@/types/UserType";
import { Avatar } from "@nextui-org/react";
import Image from "next/image";
import { useParams } from "next/navigation";
import { useEffect, useState } from "react";
@ -24,8 +26,31 @@ export default function UserPage() {
return (
<div>
{user && (
<div>
<p>{user.name}</p>
<div className="border-2 border-[#222224] relative rounded-xl overflow-hidden bg-[#18181a]">
<div className="bg-[#222222] h-28 relative">
{user.bannerPicture && (
<Image
src={user.bannerPicture}
alt={`${user.name}'s profile banner`}
className="object-cover"
fill
/>
)}
</div>
<Avatar
className="absolute rounded-full left-16 top-16 h-24 w-24 bg-transparent"
src={user.profilePicture}
/>
<div className="p-8 mt-8">
<p className="text-3xl">{user.name}</p>
<div
className="prose dark:prose-invert !duration-250 !ease-linear !transition-all"
dangerouslySetInnerHTML={{
__html:
user.bio && user.bio != "<p></p>" ? user.bio : "No user bio",
}}
/>
</div>
</div>
)}
</div>

View file

@ -3,6 +3,7 @@ import {
Dropdown,
DropdownItem,
DropdownMenu,
DropdownSection,
DropdownTrigger,
NavbarItem,
} from "@nextui-org/react";
@ -26,21 +27,23 @@ export default function PCNavbarUser({ user }: NavbarUserProps) {
/>
</DropdownTrigger>
<DropdownMenu>
<DropdownItem
key="profile"
className="text-[#333] dark:text-white"
href={`/u/${user.slug}`}
>
Profile
</DropdownItem>
<DropdownItem
showDivider
key="settings"
className="text-[#333] dark:text-white"
href="/settings"
>
Settings
</DropdownItem>
<DropdownSection title={user.name}>
<DropdownItem
key="profile"
className="text-[#333] dark:text-white"
href={`/u/${user.slug}`}
>
Profile
</DropdownItem>
<DropdownItem
showDivider
key="settings"
className="text-[#333] dark:text-white"
href="/settings"
>
Settings
</DropdownItem>
</DropdownSection>
<DropdownItem
key="logout"
color="danger"

View file

@ -2,7 +2,9 @@ export interface UserType {
id: number;
slug: string;
name: string;
bio: string;
profilePicture: string;
bannerPicture: string;
createdAt: Date;
mod: boolean;
admin: boolean;