281 lines
9.0 KiB
TypeScript
281 lines
9.0 KiB
TypeScript
import type {
|
|
ActionFunction,
|
|
ActionFunctionArgs,
|
|
LoaderFunctionArgs,
|
|
} from "@remix-run/node";
|
|
import {
|
|
json,
|
|
redirect,
|
|
unstable_parseMultipartFormData,
|
|
} from "@remix-run/node";
|
|
import { Form, Link, useActionData } from "@remix-run/react";
|
|
import { Button } from "~/components/Button";
|
|
import { Card } from "~/components/Card";
|
|
import { FormInput, FormLabel } from "~/components/Form";
|
|
import { Title, Text } from "~/components/Typography";
|
|
import { editUser } from "~/models/user.server";
|
|
import { theme } from "~/utils/cookie.server";
|
|
import { profilePictureUploadHandler } from "~/utils/file.server";
|
|
import { commitSession, getSession } from "~/utils/session.server";
|
|
|
|
const themes = ["latte", "frappe", "macchiato", "mocha", "none"];
|
|
|
|
export async function action({ request }: ActionFunctionArgs) {
|
|
const cookieHeader = request.headers.get("Cookie");
|
|
const session = await getSession(cookieHeader);
|
|
if (!session.has("userId")) {
|
|
return redirect("/home");
|
|
}
|
|
|
|
let req = request.clone();
|
|
let formData = await request
|
|
.formData()
|
|
.then((data) => Object.fromEntries(data));
|
|
|
|
switch (formData.intent) {
|
|
case "pfp":
|
|
try {
|
|
formData = Object.fromEntries(
|
|
await unstable_parseMultipartFormData(
|
|
req,
|
|
profilePictureUploadHandler
|
|
)
|
|
);
|
|
} catch (e) {
|
|
console.log(e);
|
|
}
|
|
break;
|
|
|
|
case "theme":
|
|
if (!themes.includes(String(formData.theme))) {
|
|
return {
|
|
errors: {
|
|
theme: "Theme not found",
|
|
},
|
|
};
|
|
}
|
|
|
|
const themeCookie = (await theme.parse(cookieHeader)) || {};
|
|
themeCookie.theme = formData.theme;
|
|
|
|
return json(
|
|
{
|
|
success: {
|
|
theme: `Theme changed successfully!`,
|
|
},
|
|
},
|
|
{
|
|
headers: {
|
|
"Set-Cookie": await theme.serialize(themeCookie),
|
|
},
|
|
}
|
|
);
|
|
default:
|
|
break;
|
|
}
|
|
|
|
const { username, errors } = await editUser(formData, session.get("userId"));
|
|
|
|
if (Object.values(errors).some(Boolean)) {
|
|
return { errors: errors };
|
|
}
|
|
|
|
if (username) {
|
|
session.set("username", username);
|
|
}
|
|
|
|
return json(
|
|
{
|
|
success: {
|
|
[String(formData.intent)]: `${formData.intent} changed successfully!`,
|
|
},
|
|
},
|
|
{
|
|
headers: {
|
|
"Set-Cookie": await commitSession(session),
|
|
},
|
|
}
|
|
);
|
|
}
|
|
|
|
export async function loader({ request }: LoaderFunctionArgs) {
|
|
const session = await getSession(request.headers.get("Cookie"));
|
|
if (!session.has("userId")) {
|
|
return redirect("/home");
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
export default function Settings() {
|
|
const data = useActionData<ActionFunction>();
|
|
|
|
return (
|
|
<div className="flex flex-col gap-5">
|
|
<Title>Settings</Title>
|
|
<div className="flex flex-col gap-5">
|
|
<details className="space-y-4">
|
|
<summary className="text-gray-600 cursor-pointer select-none">
|
|
<span className="text-ctp-text/75">Change username</span>
|
|
</summary>
|
|
<Card>
|
|
<Form method="POST" className="relative flex flex-col gap-3">
|
|
<input type="hidden" name="intent" value="username" />
|
|
<FormLabel>
|
|
<Text>Username</Text> <FormInput type="text" name="username" />{" "}
|
|
<Text type="error">
|
|
{data?.errors?.username ? data.errors.username : ""}
|
|
</Text>
|
|
</FormLabel>
|
|
<Button type="submit">Submit</Button>
|
|
{data?.success?.username ? (
|
|
<Text type="success">{data.success.username}</Text>
|
|
) : (
|
|
""
|
|
)}
|
|
</Form>
|
|
</Card>
|
|
</details>
|
|
<details className="space-y-4">
|
|
<summary className="text-gray-600 cursor-pointer select-none">
|
|
<span className="text-ctp-text/75">Change name</span>
|
|
</summary>
|
|
<Card>
|
|
<Form method="POST" className="relative flex flex-col gap-3">
|
|
<input type="hidden" name="intent" value="name" />
|
|
<FormLabel>
|
|
<Text>Name</Text> <FormInput type="text" name="name" />{" "}
|
|
<Text type="error">
|
|
{data?.errors?.name ? data.errors.name : ""}
|
|
</Text>
|
|
</FormLabel>
|
|
<Button type="submit">Submit</Button>
|
|
{data?.success?.name ? (
|
|
<Text type="success">{data.success.name}</Text>
|
|
) : (
|
|
""
|
|
)}
|
|
</Form>
|
|
</Card>
|
|
</details>
|
|
<details className="space-y-4">
|
|
<summary className="text-gray-600 cursor-pointer select-none">
|
|
<span className="text-ctp-text/75">Change description</span>
|
|
</summary>
|
|
<Card>
|
|
<Form method="POST" className="relative flex flex-col gap-3">
|
|
<input type="hidden" name="intent" value="desc" />
|
|
<FormLabel>
|
|
<Text>Description</Text> <FormInput type="text" name="desc" />{" "}
|
|
<Text type="error">
|
|
{data?.errors?.desc ? data.errors.desc : ""}
|
|
</Text>
|
|
</FormLabel>
|
|
<Button type="submit">Submit</Button>
|
|
{data?.success?.desc ? (
|
|
<Text type="success">{data.success.desc}</Text>
|
|
) : (
|
|
""
|
|
)}
|
|
</Form>
|
|
</Card>
|
|
</details>
|
|
<details className="space-y-4">
|
|
<summary className="text-gray-600 cursor-pointer select-none">
|
|
<span className="text-ctp-text/75">Change password</span>
|
|
</summary>
|
|
<Card>
|
|
<Form method="POST" className="relative flex flex-col gap-3">
|
|
<input type="hidden" name="intent" value="password" />
|
|
<FormLabel>
|
|
<Text>Old password</Text>{" "}
|
|
<FormInput type="text" name="opassword" />{" "}
|
|
<Text type="error">
|
|
{data?.errors?.opassword ? data.errors.opassword : ""}
|
|
</Text>
|
|
</FormLabel>
|
|
<FormLabel>
|
|
<Text>New password</Text>{" "}
|
|
<FormInput type="text" name="npassword" />{" "}
|
|
<Text type="error">
|
|
{data?.errors?.npassword ? data.errors.npassword : ""}
|
|
</Text>
|
|
</FormLabel>
|
|
<Button type="submit">Submit</Button>
|
|
{data?.success?.password ? (
|
|
<Text type="success">{data.success.password}</Text>
|
|
) : (
|
|
""
|
|
)}
|
|
</Form>
|
|
</Card>
|
|
</details>
|
|
<details className="space-y-4">
|
|
<summary className="text-gray-600 cursor-pointer select-none">
|
|
<span className="text-ctp-text/75">Change profile picture</span>
|
|
</summary>
|
|
<Card>
|
|
<Form
|
|
method="POST"
|
|
encType="multipart/form-data"
|
|
className="relative flex flex-col gap-3"
|
|
>
|
|
<input type="hidden" name="intent" value="pfp" />
|
|
<FormLabel>
|
|
<Text>New profile picture</Text>{" "}
|
|
<FormInput type="file" name="pfp" />{" "}
|
|
<Text type="error">
|
|
{data?.errors?.pfp ? data.errors.pfp : ""}
|
|
</Text>
|
|
</FormLabel>
|
|
<Button type="submit">Submit</Button>
|
|
{data?.success?.pfp ? (
|
|
<Text type="success">{data.success.pfp}</Text>
|
|
) : (
|
|
""
|
|
)}
|
|
</Form>
|
|
</Card>
|
|
</details>
|
|
<details className="space-y-4">
|
|
<summary className="text-gray-600 cursor-pointer select-none">
|
|
<span className="text-ctp-text/75">Change theme</span>
|
|
</summary>
|
|
<Card>
|
|
<Form className="relative flex flex-col gap-3" method="POST">
|
|
<input type="hidden" name="intent" value="theme" />
|
|
<FormLabel>
|
|
<Text>Theme</Text>
|
|
<select
|
|
className="w-fit rounded-lg p-2 bg-ctp-crust border"
|
|
name="theme"
|
|
>
|
|
{themes.map((theme) => (
|
|
<option key={theme} className="capitalize" value={theme}>
|
|
{theme}
|
|
</option>
|
|
))}
|
|
</select>{" "}
|
|
<Text type="error">
|
|
{data?.errors?.theme ? data.errors.theme : ""}
|
|
</Text>
|
|
</FormLabel>
|
|
<Button type="submit">Submit</Button>
|
|
{data?.success?.theme ? (
|
|
<Text type="success">{data.success.theme}</Text>
|
|
) : (
|
|
""
|
|
)}
|
|
</Form>
|
|
</Card>
|
|
</details>
|
|
<div className="w-fit">
|
|
<Link to={`/logout`}>
|
|
<Text type="link">Sign out</Text>
|
|
</Link>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|