119 lines
3.8 KiB
TypeScript
119 lines
3.8 KiB
TypeScript
import type { ActionFunctionArgs, LoaderFunctionArgs } from "@remix-run/node";
|
|
import { json, redirect } from "@remix-run/node";
|
|
import { Form, useActionData, useLoaderData } from "@remix-run/react";
|
|
import { Alert, Button, Card, Col, Container, Form as BootstrapForm, Row } from "react-bootstrap";
|
|
|
|
import { hashPassword, verifyPassword } from "~/auth/validate";
|
|
import { enhance } from "~/utils/db.server";
|
|
|
|
export async function loader({ request }: LoaderFunctionArgs) {
|
|
const { session, dbe } = await enhance(request);
|
|
|
|
if (!session.has("user_id")) {
|
|
return redirect("/login");
|
|
}
|
|
|
|
const user = await dbe.user.findUnique({
|
|
where: { id: Number(session.get("user_id")) },
|
|
select: { id: true, username: true, type: true },
|
|
});
|
|
|
|
if (!user) {
|
|
return redirect("/logout");
|
|
}
|
|
|
|
return json({ user });
|
|
}
|
|
|
|
export async function action({ request }: ActionFunctionArgs) {
|
|
const { session, dbe } = await enhance(request);
|
|
|
|
if (!session.has("user_id")) {
|
|
return redirect("/login");
|
|
}
|
|
|
|
const userId = Number(session.get("user_id"));
|
|
const form = await request.formData();
|
|
const currentPassword = form.get("currentPassword")?.toString() ?? "";
|
|
const newPassword = form.get("newPassword")?.toString() ?? "";
|
|
const confirmPassword = form.get("confirmPassword")?.toString() ?? "";
|
|
|
|
const user = await dbe.user.findUnique({
|
|
where: { id: userId },
|
|
select: { id: true, password: true },
|
|
});
|
|
|
|
if (!user) {
|
|
return json({ error: "User not found." }, { status: 404 });
|
|
}
|
|
|
|
const passwordMatches = verifyPassword(currentPassword, user.password);
|
|
if (!passwordMatches) {
|
|
return json({ error: "Current password is incorrect." });
|
|
}
|
|
|
|
if (newPassword.length < 4) {
|
|
return json({ error: "New password must be at least 4 characters long." });
|
|
}
|
|
|
|
if (newPassword !== confirmPassword) {
|
|
return json({ error: "New passwords do not match." });
|
|
}
|
|
|
|
await dbe.user.update({
|
|
where: { id: user.id },
|
|
data: { password: hashPassword(newPassword) },
|
|
});
|
|
|
|
return json({ success: "Password updated successfully." });
|
|
}
|
|
|
|
export default function UsersRoute() {
|
|
const loaderData = useLoaderData<typeof loader>();
|
|
const actionData = useActionData<typeof action>();
|
|
|
|
return (
|
|
<Container className="py-3">
|
|
<Row className="justify-content-center">
|
|
<Col md={8} lg={6}>
|
|
<Card>
|
|
<Card.Body>
|
|
<Card.Title>Account settings</Card.Title>
|
|
<Card.Subtitle className="mb-3 text-muted">
|
|
Manage the password for {loaderData.user.username}
|
|
</Card.Subtitle>
|
|
|
|
{actionData?.error ? (
|
|
<Alert variant="danger">{actionData.error}</Alert>
|
|
) : null}
|
|
|
|
{actionData?.success ? (
|
|
<Alert variant="success">{actionData.success}</Alert>
|
|
) : null}
|
|
|
|
<Form method="post">
|
|
<BootstrapForm.Group className="mb-3">
|
|
<BootstrapForm.Label>Current password</BootstrapForm.Label>
|
|
<BootstrapForm.Control type="password" name="currentPassword" required />
|
|
</BootstrapForm.Group>
|
|
|
|
<BootstrapForm.Group className="mb-3">
|
|
<BootstrapForm.Label>New password</BootstrapForm.Label>
|
|
<BootstrapForm.Control type="password" name="newPassword" required />
|
|
</BootstrapForm.Group>
|
|
|
|
<BootstrapForm.Group className="mb-3">
|
|
<BootstrapForm.Label>Confirm new password</BootstrapForm.Label>
|
|
<BootstrapForm.Control type="password" name="confirmPassword" required />
|
|
</BootstrapForm.Group>
|
|
|
|
<Button type="submit">Save password</Button>
|
|
</Form>
|
|
</Card.Body>
|
|
</Card>
|
|
</Col>
|
|
</Row>
|
|
</Container>
|
|
);
|
|
}
|