twitter-clone/app/routes/settings.tsx

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>
);
}