diff --git a/src/actions/recipient.actions.ts b/src/actions/recipient.actions.ts
new file mode 100644
index 0000000..e05515b
--- /dev/null
+++ b/src/actions/recipient.actions.ts
@@ -0,0 +1,76 @@
+"use server"
+
+import { revalidatePath } from "next/cache"
+
+import {
+ type CreateRecipientFormType,
+ createRecipientSchema,
+ type UpdateRecipientFormType,
+ updateRecipientSchema,
+} from "@/schemas/recipient.schema"
+import {
+ createRecipientUseCase,
+ updateRecipientUseCase,
+} from "@/use-cases/recipient.use-cases"
+
+export async function createNewRecipient(formData: CreateRecipientFormType) {
+ const validatedFields = createRecipientSchema.safeParse(formData)
+
+ if (!validatedFields.success) {
+ return {
+ success: false,
+ errors: validatedFields.error.flatten().fieldErrors,
+ }
+ }
+
+ try {
+ const result = await createRecipientUseCase(validatedFields.data)
+
+ if (!result.success) {
+ return result
+ }
+
+ revalidatePath("/recipients")
+
+ return {
+ success: true,
+ message: "Recipient created successfully",
+ }
+ } catch (error) {
+ console.error("Database error:", error)
+ return {
+ message: "Failed to create recipient",
+ }
+ }
+}
+
+export async function updateRecipient(formData: UpdateRecipientFormType) {
+ const validatedFields = updateRecipientSchema.safeParse(formData)
+
+ if (!validatedFields.success) {
+ return {
+ success: false,
+ errors: validatedFields.error.flatten().fieldErrors,
+ }
+ }
+
+ try {
+ const result = await updateRecipientUseCase(validatedFields.data)
+
+ if (!result.success) {
+ return result
+ }
+
+ revalidatePath("/recipients")
+
+ return {
+ success: true,
+ message: "Recipient updated successfully",
+ }
+ } catch (error) {
+ console.error("Database error:", error)
+ return {
+ message: "Failed to update recipient",
+ }
+ }
+}
diff --git a/src/app/(dashboard)/recipients/_components/recipient.form.tsx b/src/app/(dashboard)/recipients/_components/recipient.form.tsx
index b6e5955..20e0b75 100644
--- a/src/app/(dashboard)/recipients/_components/recipient.form.tsx
+++ b/src/app/(dashboard)/recipients/_components/recipient.form.tsx
@@ -4,19 +4,18 @@ import { zodResolver } from "@hookform/resolvers/zod"
import { useRouter } from "next/navigation"
import { useForm } from "react-hook-form"
import { toast } from "sonner"
-
-import { SubmitButton } from "@/components/forms/submitButton"
-import { RecipientDepartment } from "@/generated/prisma/client"
import {
createNewRecipient,
updateRecipient,
-} from "@/lib/actions/recipient.actions"
+} from "@/actions/recipient.actions"
+import { SubmitButton } from "@/components/forms/submitButton"
+import { RECIPIENT_DEPARTMENTS } from "@/lib/constants"
import {
type CreateRecipientFormType,
recipientSchema,
type UpdateRecipientFormType,
-} from "@/lib/schemas/recipients.schemas"
-import type { Recipient } from "@/lib/types"
+} from "@/schemas/recipient.schema"
+import type { Recipient } from "@/types"
interface RecipientFormProps {
initialData?: Recipient
@@ -130,7 +129,7 @@ export default function RecipientForm({
className="w-full rounded-lg border px-4 py-2"
>
- {Object.keys(RecipientDepartment).map((department) => (
+ {Object.keys(RECIPIENT_DEPARTMENTS).map((department) => (
diff --git a/src/lib/actions/recipient.actions.ts b/src/lib/actions/recipient.actions.ts
deleted file mode 100644
index a7f71a3..0000000
--- a/src/lib/actions/recipient.actions.ts
+++ /dev/null
@@ -1,138 +0,0 @@
-"use server"
-
-import { revalidatePath } from "next/cache"
-
-import prisma from "@/lib/prisma"
-import {
- type CreateRecipientFormType,
- createRecipientSchema,
- type UpdateRecipientFormType,
- updateRecipientSchema,
-} from "@/lib/schemas/recipients.schemas"
-import { RecipientService } from "@/services/recipient.service"
-
-export async function createNewRecipient(formData: CreateRecipientFormType) {
- const validatedFields = createRecipientSchema.safeParse(formData)
-
- if (!validatedFields.success) {
- return {
- success: false,
- errors: validatedFields.error.flatten().fieldErrors,
- }
- }
-
- const { username, firstName, lastName, department, email, phone } =
- validatedFields.data
-
- try {
- const existingRecipientUsername =
- await RecipientService.findByUsername(username)
-
- if (existingRecipientUsername) {
- return {
- success: false,
- errors: {
- username: ["Username already exists"],
- },
- }
- }
-
- const existingRecipientEmail = await RecipientService.findByEmail(
- email || "",
- )
-
- if (existingRecipientEmail) {
- return {
- success: false,
- errors: {
- email: ["Email already exists"],
- },
- }
- }
-
- await RecipientService.create({
- username: username || (firstName[0] + lastName).toLowerCase(),
- firstName,
- lastName,
- department: department,
- email: email || null,
- phone: phone || null,
- })
-
- revalidatePath("/recipients")
-
- return {
- success: true,
- message: "Recipient created successfully",
- }
- } catch (error) {
- console.error("Database error:", error)
- return {
- message: "Failed to create recipient",
- }
- }
-}
-
-export async function updateRecipient(formData: UpdateRecipientFormType) {
- const validatedFields = updateRecipientSchema.safeParse(formData)
-
- if (!validatedFields.success) {
- return {
- success: false,
- errors: validatedFields.error.flatten().fieldErrors,
- }
- }
-
- const { id, username, firstName, lastName, department, email, phone } =
- validatedFields.data
-
- try {
- const existingRecipient = await RecipientService.findByUsername(username)
-
- if (existingRecipient && existingRecipient.id !== id) {
- return {
- success: false,
- errors: {
- username: ["Username already exists"],
- },
- }
- }
-
- const existingRecipientEmail = await RecipientService.findByEmail(
- email || "",
- )
-
- if (existingRecipientEmail && existingRecipientEmail.id !== id) {
- return {
- success: false,
- errors: {
- email: ["Email already exists"],
- },
- }
- }
-
- await prisma.recipient.update({
- where: { id },
- data: {
- username,
- firstName,
- lastName,
- department: department,
- email: email || null,
- phone: phone || null,
- },
- })
-
- revalidatePath("/recipients")
-
- return {
- success: true,
- message: "Recipient updated successfully",
- }
- } catch (error) {
- console.error("Database error:", error)
- return {
- message: "Failed to update recipient",
- }
- }
-}
diff --git a/src/lib/constants.ts b/src/lib/constants.ts
index 4f7ae1b..c894af7 100644
--- a/src/lib/constants.ts
+++ b/src/lib/constants.ts
@@ -8,6 +8,17 @@ export const SIGN_IN_URL = "/login"
export const TOKEN_EXPIRATION_SECONDS = 60 * 60 * 2 // 2 hour
+export const RECIPIENT_DEPARTMENTS = {
+ IT: "IT",
+ ENGINEERING: "ENGINEERING",
+ LOGISTICS: "LOGISTICS",
+ TRAFFIC: "TRAFFIC",
+ DRIVER: "DRIVER",
+ ADMINISTRATION: "ADMINISTRATION",
+ SALES: "SALES",
+ OTHER: "OTHER",
+} as const
+
export const ITEM_STATUS = {
AVAILABLE: "AVAILABLE",
ASSIGNED: "ASSIGNED",
diff --git a/src/lib/schemas/recipients.schemas.ts b/src/schemas/recipient.schema.ts
similarity index 87%
rename from src/lib/schemas/recipients.schemas.ts
rename to src/schemas/recipient.schema.ts
index 7cc7615..940fc24 100644
--- a/src/lib/schemas/recipients.schemas.ts
+++ b/src/schemas/recipient.schema.ts
@@ -5,15 +5,15 @@ export const recipientSchema = z.object({
username: z
.string()
.min(1, {
- error: "Username is required"
+ error: "Username is required",
})
.nonempty("Username is required"),
firstName: z.string().min(1, {
- error: "First name is required"
-}),
+ error: "First name is required",
+ }),
lastName: z.string().min(1, {
- error: "Last name is required"
-}),
+ error: "Last name is required",
+ }),
department: z.enum(
[
"IT",
@@ -26,7 +26,7 @@ export const recipientSchema = z.object({
"OTHER",
],
{
- error: "Department is required"
+ error: "Department is required",
},
),
email: z.string().optional().nullable(),
diff --git a/src/services/recipient.service.ts b/src/services/recipient.service.ts
index e5b9e62..c9c104c 100644
--- a/src/services/recipient.service.ts
+++ b/src/services/recipient.service.ts
@@ -40,19 +40,39 @@ export const RecipientService = {
return prisma.recipient.count()
},
- findById: async (id: string): Promise => {
- return prisma.recipient.findUnique({ where: { id } })
+ findById: async (
+ id: string,
+ db: Prisma.TransactionClient | typeof prisma = prisma,
+ ): Promise => {
+ return db.recipient.findUnique({ where: { id } })
},
- findByUsername: async (username: string): Promise => {
- return prisma.recipient.findUnique({ where: { username } })
+ findByUsername: async (
+ username: string,
+ db: Prisma.TransactionClient | typeof prisma = prisma,
+ ): Promise => {
+ return db.recipient.findUnique({ where: { username } })
},
- findByEmail: async (email: string): Promise => {
- return prisma.recipient.findUnique({ where: { email } })
+ findByEmail: async (
+ email: string,
+ db: Prisma.TransactionClient | typeof prisma = prisma,
+ ): Promise => {
+ return db.recipient.findUnique({ where: { email } })
},
- create: async (data: Prisma.RecipientCreateInput): Promise => {
- return prisma.recipient.create({ data })
+ create: async (
+ data: Prisma.RecipientCreateInput,
+ db: Prisma.TransactionClient | typeof prisma = prisma,
+ ): Promise => {
+ return db.recipient.create({ data })
+ },
+
+ update: async (
+ id: string,
+ data: Prisma.RecipientUpdateInput,
+ db: Prisma.TransactionClient | typeof prisma = prisma,
+ ): Promise => {
+ return db.recipient.update({ where: { id }, data })
},
}
diff --git a/src/use-cases/recipient.use-cases.ts b/src/use-cases/recipient.use-cases.ts
new file mode 100644
index 0000000..720127a
--- /dev/null
+++ b/src/use-cases/recipient.use-cases.ts
@@ -0,0 +1,155 @@
+import { Prisma } from "@/generated/prisma/client"
+import prisma from "@/lib/prisma"
+import type {
+ CreateRecipientFormType,
+ UpdateRecipientFormType,
+} from "@/schemas/recipient.schema"
+import { RecipientService } from "@/services/recipient.service"
+
+type FieldErrors = Record
+
+type RecipientUseCaseResult =
+ | {
+ success: true
+ }
+ | {
+ success: false
+ errors: FieldErrors
+ }
+
+function recipientError(errors: FieldErrors): RecipientUseCaseResult {
+ return {
+ success: false,
+ errors,
+ }
+}
+
+function uniqueErrorFor(error: unknown): FieldErrors | null {
+ if (
+ !(error instanceof Prisma.PrismaClientKnownRequestError) ||
+ error.code !== "P2002"
+ ) {
+ return null
+ }
+
+ const target = Array.isArray(error.meta?.target) ? error.meta.target : []
+
+ if (target.includes("username")) {
+ return { username: ["Username already exists"] }
+ }
+
+ if (target.includes("email")) {
+ return { email: ["Email already exists"] }
+ }
+
+ return { username: ["Username already exists"] }
+}
+
+export async function createRecipientUseCase(
+ input: CreateRecipientFormType,
+): Promise {
+ const { username, firstName, lastName, department, email, phone } = input
+
+ try {
+ return await prisma.$transaction(async (tx) => {
+ const existingRecipientUsername = await RecipientService.findByUsername(
+ username,
+ tx,
+ )
+
+ if (existingRecipientUsername) {
+ return recipientError({ username: ["Username already exists"] })
+ }
+
+ if (email) {
+ const existingRecipientEmail = await RecipientService.findByEmail(
+ email,
+ tx,
+ )
+
+ if (existingRecipientEmail) {
+ return recipientError({ email: ["Email already exists"] })
+ }
+ }
+
+ await RecipientService.create(
+ {
+ username: username || (firstName[0] + lastName).toLowerCase(),
+ firstName,
+ lastName,
+ department,
+ email: email || null,
+ phone: phone || null,
+ },
+ tx,
+ )
+
+ return {
+ success: true,
+ }
+ })
+ } catch (error) {
+ const errors = uniqueErrorFor(error)
+
+ if (errors) {
+ return recipientError(errors)
+ }
+
+ throw error
+ }
+}
+
+export async function updateRecipientUseCase(
+ input: UpdateRecipientFormType,
+): Promise {
+ const { id, username, firstName, lastName, department, email, phone } = input
+
+ try {
+ return await prisma.$transaction(async (tx) => {
+ const existingRecipient = await RecipientService.findByUsername(
+ username,
+ tx,
+ )
+
+ if (existingRecipient && existingRecipient.id !== id) {
+ return recipientError({ username: ["Username already exists"] })
+ }
+
+ if (email) {
+ const existingRecipientEmail = await RecipientService.findByEmail(
+ email,
+ tx,
+ )
+
+ if (existingRecipientEmail && existingRecipientEmail.id !== id) {
+ return recipientError({ email: ["Email already exists"] })
+ }
+ }
+
+ await RecipientService.update(
+ id,
+ {
+ username,
+ firstName,
+ lastName,
+ department,
+ email: email || null,
+ phone: phone || null,
+ },
+ tx,
+ )
+
+ return {
+ success: true,
+ }
+ })
+ } catch (error) {
+ const errors = uniqueErrorFor(error)
+
+ if (errors) {
+ return recipientError(errors)
+ }
+
+ throw error
+ }
+}