Skip to content

Commit

Permalink
Add file upload and delete hooks to profilePictures bucket, implement…
Browse files Browse the repository at this point in the history
… deleteUser function, and add password update route
  • Loading branch information
bartosz-skejcik committed Dec 22, 2023
1 parent d259c34 commit 9b097f2
Show file tree
Hide file tree
Showing 7 changed files with 339 additions and 76 deletions.
9 changes: 8 additions & 1 deletion app/api/edgestore/[...edgestore]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,14 @@ const es = initEdgeStore.create();
* This is the main router for the Edge Store buckets.
*/
const edgeStoreRouter = es.router({
profilePictures: es.fileBucket(),
profilePictures: es
.fileBucket()
.beforeUpload(({ ctx, input, fileInfo }) => {
return true; // allow upload
})
.beforeDelete(({ ctx, fileInfo }) => {
return true; // allow delete
}),
});

const handler = createEdgeStoreNextHandler({
Expand Down
63 changes: 63 additions & 0 deletions app/api/user/password/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { NextResponse } from "next/server";
import bcrypt from "bcrypt";
import { prisma } from "@/prisma/client";

type Props = {
currentPassword: string;
newPassword: string;
userId: number;
};

export async function PATCH(req: Request) {
const body = await req.json();

const { currentPassword, newPassword, userId } = body as Props;

try {
const user = await prisma.user.findUnique({
where: { id: userId },
});

if (user === null) {
return new NextResponse(
JSON.stringify({
error: "Nie znaleziono użytkownika.",
}),
{ status: 404 }
);
}

const passwordMatch = await bcrypt.compare(
currentPassword,
user.password
);

if (!passwordMatch) {
return new NextResponse(
JSON.stringify({
error: "Niepoprawne hasło.",
}),
{ status: 400 }
);
}

const salt = await bcrypt.genSalt(10);
const hash = await bcrypt.hash(newPassword, salt);

await prisma.user.update({
where: { id: userId },
data: {
password: hash,
},
});

return new NextResponse(null, { status: 200 });
} catch (e: any) {
return new NextResponse(
JSON.stringify({
error: e,
}),
{ status: 500 }
);
}
}
21 changes: 21 additions & 0 deletions app/api/user/route.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { prisma } from "@/prisma/client";
import { deleteUser } from "@/utils/user";
import { NextResponse } from "next/server";

type Props = {
Expand Down Expand Up @@ -26,3 +27,23 @@ export async function PATCH(req: Request) {

return new NextResponse(null, { status: 200 });
}

export async function DELETE(req: Request) {
const body = await req.json();

const { userId } = body as Props;

try {
await deleteUser(userId);

return new NextResponse(null, { status: 200 });
} catch (e) {
console.log(e);
return new NextResponse(
JSON.stringify({
error: e,
}),
{ status: 500 }
);
}
}
115 changes: 41 additions & 74 deletions app/panel/settings/account.tsx
Original file line number Diff line number Diff line change
@@ -1,81 +1,17 @@
"use client";

import type { Session } from "next-auth";
import { signOut, useSession } from "next-auth/react";
import Image from "next/image";
import { useEffect, useState } from "react";
import { toast } from "react-toastify";
import { useEdgeStore } from "@/lib/edgestore";
import clsx from "clsx";
import { Loader2 } from "lucide-react";

async function handleUserChange(
e: FormData,
session: Session,
setLoading: (loading: boolean) => void,
edgestore: any,
avatarFile: File | undefined
) {
setLoading(true);
let avatarUrl = "";

if (session.user.avatar === "" && avatarFile !== undefined) {
// upload the new avatar
const res = await edgestore.profilePictures.upload({
file: avatarFile,
});

avatarUrl = res.url;
} else if (session.user.avatar !== "" && avatarFile !== undefined) {
const res = await edgestore.profilePictures.upload({
file: avatarFile,
options: {
replaceTargetUrl: session.user.avatar,
},
});

avatarUrl = res.url;
}

if (
session?.user?.firstName !== e.get("first-name") ||
session?.user?.lastName !== e.get("last-name") ||
session?.user?.email !== e.get("email") ||
avatarUrl !== ""
) {
const res = await fetch("/api/user", {
method: "PATCH",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
firstName: e.get("first-name"),
lastName: e.get("last-name"),
email: e.get("email"),
avatar: avatarUrl,
userId: session.user.id,
}),
});

if (res.status === 200) {
setLoading(false);
toast.success(
"Zaktualizowano informacje. Za chwilę zostaniesz wylogowany."
);
await new Promise((resolve) => setTimeout(resolve, 2500));
signOut({
callbackUrl: "/",
redirect: true,
});
} else {
setLoading(false);
toast.error("Wystąpił błąd podczas aktualizacji informacji.");
}
} else {
setLoading(false);
toast.info("Nie zmieniono żadnych informacji.");
}
}
import {
handleDeleteAccount,
handlePasswordChange,
handleUserChange,
} from "./functions";

type Props = {};

Expand Down Expand Up @@ -111,7 +47,9 @@ function Account({}: Props) {
session!,
setLoading,
edgestore,
avatarFile
avatarFile,
toast,
signOut
);
}}
className="md:col-span-2"
Expand Down Expand Up @@ -253,7 +191,17 @@ function Account({}: Props) {
</p>
</div>

<form className="md:col-span-2">
<form
action={async (event: FormData) => {
await handlePasswordChange(
session!,
event,
setLoading,
toast
);
}}
className="md:col-span-2"
>
<div className="grid grid-cols-1 gap-x-6 gap-y-8 sm:max-w-xl sm:grid-cols-6">
<div className="col-span-full">
<label
Expand Down Expand Up @@ -313,9 +261,17 @@ function Account({}: Props) {
<div className="mt-8 flex">
<button
type="submit"
className="rounded-md bg-green-200 text-green-600 px-3 py-2 text-sm font-semibold shadow-sm hover:bg-green-300 transition-colors duration-200 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-green-500"
className={clsx(
loading
? "cursor-not-allowed bg-neutral-200 text-neutral-600"
: "bg-green-200 text-green-600 hover:bg-green-300 transition-colors duration-200",
"rounded-md px-3 py-2 text-sm font-semibold shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-green-500 flex items-center"
)}
>
Zapisz
{loading && (
<Loader2 className="w-5 h-5 mr-2 animate-spin" />
)}
{loading ? "Zmienianie..." : "Zmień"}
</button>
</div>
</form>
Expand All @@ -333,7 +289,18 @@ function Account({}: Props) {
</p>
</div>

<form className="flex items-start md:col-span-2">
<form
action={async () =>
await handleDeleteAccount(
Number(session?.user.id!),
signOut,
toast,
edgestore,
userAvatar!
)
}
className="flex items-start md:col-span-2"
>
<button
type="submit"
className="rounded-md bg-red-200 text-red-600 px-3 py-2 text-sm font-semibold shadow-sm hover:bg-red-300/80 transition-colors duration-200"
Expand Down
Loading

0 comments on commit 9b097f2

Please sign in to comment.