From 3c982d5dfa6b3a4f80cac6846daad3cd787feb33 Mon Sep 17 00:00:00 2001
From: Alireza Dorrani
Date: Wed, 12 Mar 2025 23:24:32 +0330
Subject: [PATCH 01/13] [ADD] sign up api route
---
src/app/api/auth/sign-up/route.ts | 16 ++++++++++++++++
src/types/api.response.ts | 7 +++++++
src/utils/api.utils.ts | 17 +++++++++++++++++
3 files changed, 40 insertions(+)
create mode 100644 src/app/api/auth/sign-up/route.ts
create mode 100644 src/types/api.response.ts
create mode 100644 src/utils/api.utils.ts
diff --git a/src/app/api/auth/sign-up/route.ts b/src/app/api/auth/sign-up/route.ts
new file mode 100644
index 0000000..447d664
--- /dev/null
+++ b/src/app/api/auth/sign-up/route.ts
@@ -0,0 +1,16 @@
+import { NextResponse } from "next/server";
+
+import prisma from "@/lib/prisma";
+
+import { ApiResponseType } from "@/types/api.response";
+import { wrapWithTryCatch } from "@/utils/api.utils";
+
+export async function POST(request: Request): Promise> {
+ return wrapWithTryCatch(async () => {
+ const body = await request.json();
+
+ await prisma.user.create({ data: body });
+
+ return NextResponse.json({ data: null }, { status: 201 });
+ });
+};
\ No newline at end of file
diff --git a/src/types/api.response.ts b/src/types/api.response.ts
new file mode 100644
index 0000000..a876021
--- /dev/null
+++ b/src/types/api.response.ts
@@ -0,0 +1,7 @@
+import { NextResponse } from "next/server";
+
+export type fetchDataType =
+ | { data: TData; error?: undefined }
+ | { data?: undefined; error: string };
+
+export type ApiResponseType = NextResponse>;
\ No newline at end of file
diff --git a/src/utils/api.utils.ts b/src/utils/api.utils.ts
new file mode 100644
index 0000000..3c09338
--- /dev/null
+++ b/src/utils/api.utils.ts
@@ -0,0 +1,17 @@
+import { NextResponse } from "next/server";
+
+import { ApiResponseType } from "@/types/api.response";
+
+export async function wrapWithTryCatch(
+ callback: () => Promise>,
+): Promise> {
+ try {
+ return await callback()
+ } catch (error) {
+ if (error instanceof Error) {
+ return NextResponse.json({ error: error.message }, { status: 400 });
+ };
+
+ return NextResponse.json({ error: "خطای غیرمنتظره رخ داده." }, { status: 500 });
+ }
+}
\ No newline at end of file
From cca5822f596b0c33d02f8840eb9d88e0b231b651 Mon Sep 17 00:00:00 2001
From: Alireza Dorrani
Date: Wed, 12 Mar 2025 23:46:06 +0330
Subject: [PATCH 02/13] [ADD] parseBody
---
src/app/api/auth/sign-up/route.ts | 11 +++++++++--
src/dto/auth.dto.ts | 3 +++
src/utils/api.utils.ts | 24 ++++++++++++++++++++++++
3 files changed, 36 insertions(+), 2 deletions(-)
create mode 100644 src/dto/auth.dto.ts
diff --git a/src/app/api/auth/sign-up/route.ts b/src/app/api/auth/sign-up/route.ts
index 447d664..e66c035 100644
--- a/src/app/api/auth/sign-up/route.ts
+++ b/src/app/api/auth/sign-up/route.ts
@@ -1,13 +1,20 @@
import { NextResponse } from "next/server";
+import { SignUpDto } from "@/dto/auth.dto";
+
import prisma from "@/lib/prisma";
import { ApiResponseType } from "@/types/api.response";
-import { wrapWithTryCatch } from "@/utils/api.utils";
+
+import { parseBody, wrapWithTryCatch } from "@/utils/api.utils";
export async function POST(request: Request): Promise> {
return wrapWithTryCatch(async () => {
- const body = await request.json();
+ const [parseError, body] = await parseBody(request);
+
+ if (parseError !== null) {
+ return NextResponse.json({ error: parseError }, { status: 400 });
+ }
await prisma.user.create({ data: body });
diff --git a/src/dto/auth.dto.ts b/src/dto/auth.dto.ts
new file mode 100644
index 0000000..031b1ed
--- /dev/null
+++ b/src/dto/auth.dto.ts
@@ -0,0 +1,3 @@
+import Prisma from "@prisma/client";
+
+export type SignUpDto = Omit
\ No newline at end of file
diff --git a/src/utils/api.utils.ts b/src/utils/api.utils.ts
index 3c09338..9c3af5b 100644
--- a/src/utils/api.utils.ts
+++ b/src/utils/api.utils.ts
@@ -2,6 +2,30 @@ import { NextResponse } from "next/server";
import { ApiResponseType } from "@/types/api.response";
+type ParseBodyResult = [error: null, data: T] | [error: string, data: null];
+
+export async function parseBody(
+ request: Request,
+): Promise> {
+ try {
+ const body = await request.json();
+ return [null, body];
+ } catch (error) {
+ if (error instanceof Error) {
+ // return [error.message, null];
+ if (error.name === "SyntaxError") {
+ return ["فرمت body نادرست است.", null]
+ }
+ }
+
+ if (typeof error === "string") {
+ return [error, null];
+ }
+
+ return ["خطای غیر منتظره رخ داده.", null];
+ }
+}
+
export async function wrapWithTryCatch(
callback: () => Promise>,
): Promise> {
From 4d879c46e716065222dd71771841e3409bf0ef5d Mon Sep 17 00:00:00 2001
From: Alireza Dorrani
Date: Sat, 15 Mar 2025 21:41:58 +0330
Subject: [PATCH 03/13] [ADD] username and email validatuion
---
src/app/api/auth/sign-up/route.ts | 22 ++++++++++++++++++++++
1 file changed, 22 insertions(+)
diff --git a/src/app/api/auth/sign-up/route.ts b/src/app/api/auth/sign-up/route.ts
index e66c035..2ae6793 100644
--- a/src/app/api/auth/sign-up/route.ts
+++ b/src/app/api/auth/sign-up/route.ts
@@ -16,6 +16,28 @@ export async function POST(request: Request): Promise> {
return NextResponse.json({ error: parseError }, { status: 400 });
}
+ let foundUser = await prisma.user.findUnique({
+ where: { username: body.username }
+ });
+
+ if (foundUser) {
+ return NextResponse.json(
+ { error: "نام کاربری تکراری است." },
+ { status: 400 }
+ );
+ };
+
+ foundUser = await prisma.user.findUnique({
+ where: { email: body.email }
+ });
+
+ if (foundUser) {
+ return NextResponse.json(
+ { error: "ایمیل تکراری است." },
+ { status: 400 }
+ );
+ };
+
await prisma.user.create({ data: body });
return NextResponse.json({ data: null }, { status: 201 });
From 0e0f865358a0a39a86934569fe90b75390e90f68 Mon Sep 17 00:00:00 2001
From: Alireza Dorrani
Date: Sat, 15 Mar 2025 23:02:04 +0330
Subject: [PATCH 04/13] [ADD] react-toastify
---
package-lock.json | 13 ++++++++
package.json | 1 +
.../sign-up-form/sign-up-form.component.tsx | 32 +++++++++++++++++++
src/app/layout.tsx | 2 ++
src/components/toaster/toaster.component.tsx | 24 ++++++++++++++
.../toaster/toaster/toaster.module.css | 2 ++
6 files changed, 74 insertions(+)
create mode 100644 src/components/toaster/toaster.component.tsx
create mode 100644 src/components/toaster/toaster/toaster.module.css
diff --git a/package-lock.json b/package-lock.json
index e788456..56e3c41 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -14,6 +14,7 @@
"next": "14.2.20",
"react": "^18",
"react-dom": "^18",
+ "react-toastify": "^11.0.3",
"sharp": "^0.33.5"
},
"devDependencies": {
@@ -4524,6 +4525,18 @@
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
"dev": true
},
+ "node_modules/react-toastify": {
+ "version": "11.0.3",
+ "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-11.0.3.tgz",
+ "integrity": "sha512-cbPtHJPfc0sGqVwozBwaTrTu1ogB9+BLLjd4dDXd863qYLj7DGrQ2sg5RAChjFUB4yc3w8iXOtWcJqPK/6xqRQ==",
+ "dependencies": {
+ "clsx": "^2.1.1"
+ },
+ "peerDependencies": {
+ "react": "^18 || ^19",
+ "react-dom": "^18 || ^19"
+ }
+ },
"node_modules/reflect.getprototypeof": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.8.tgz",
diff --git a/package.json b/package.json
index 1a2d696..f0d9f21 100644
--- a/package.json
+++ b/package.json
@@ -20,6 +20,7 @@
"next": "14.2.20",
"react": "^18",
"react-dom": "^18",
+ "react-toastify": "^11.0.3",
"sharp": "^0.33.5"
},
"devDependencies": {
diff --git a/src/app/auth/components/sign-up-form/sign-up-form.component.tsx b/src/app/auth/components/sign-up-form/sign-up-form.component.tsx
index 46681da..5a251d5 100644
--- a/src/app/auth/components/sign-up-form/sign-up-form.component.tsx
+++ b/src/app/auth/components/sign-up-form/sign-up-form.component.tsx
@@ -5,6 +5,8 @@ import { ReactElement, FormEvent } from "react";
import Image from "next/image";
import Link from "next/link";
+import { toast } from "react-toastify";
+
import signUpImage from "@/assets/images/sign-up.webp";
import { ButtonComponent } from "@/components/button/button.component";
@@ -12,6 +14,8 @@ import CardComponent from "@/components/card/card.component";
import NormalInputComponent from "@/components/normal-input/normal-input.component";
import PasswordInputComponent from "@/components/password-input/password-input.component";
+import { SignUpDto } from "@/dto/auth.dto";
+
import MingcuteIncognitoModeLine from "@/icons/MingcuteIncognitoModeLine";
import MingcuteUser3Line from "@/icons/MingcuteUser3Line";
import MingcuteMailLine from "@/icons/MingcuteMailLine";
@@ -23,6 +27,34 @@ const SignUpFormComponent = (): ReactElement => {
e: FormEvent,
): Promise => {
e.preventDefault();
+ const formData = new FormData(e.currentTarget);
+
+ const dto: SignUpDto = {
+ name: formData.get("name") as string,
+ username: formData.get("username") as string,
+ email: formData.get("email") as string,
+ password: formData.get("password") as string,
+ };
+
+ const response = await fetch("/api/auth/sign-up", {
+ method: "POST",
+ body: JSON.stringify(dto),
+ });
+
+ const result = await response.json();
+
+ if (!response.ok) {
+ let message: string = "خطای غیرمنتظره رخ داد.";
+
+ if ("error" in result) {
+ message = result.error;
+ }
+
+ toast.error(message);
+ return;
+ }
+
+ toast.success("ثبتنام با موفقیت انجام شد.");
};
return (
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index bf70ef8..173a979 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -5,6 +5,7 @@ import localFont from "next/font/local";
import FooterComponent from "@/components/footer/footer.component";
import HeaderComponent from "@/components/header/header.component";
+import ToasterComponent from "@/components/toaster/toaster.component";
import "@/styles/typography.css";
import "./globals.css";
@@ -79,6 +80,7 @@ export default function RootLayout({
نوبت دهی پزشکی، سامانه نوبت دهی اینترنتی بیمارستان و پزشکان
+