From 0a63e4337ec662e69acf6aee0098fb597a68fe32 Mon Sep 17 00:00:00 2001 From: i701 Date: Mon, 6 Jan 2025 12:49:13 +0500 Subject: [PATCH] Enhance user management and payment processing features - Updated `package.json` to include a new script for launching Prisma Studio. - Modified `signup` function in `auth-actions.ts` to include account number in user data. - Refactored `createPayment` function in `payment.ts` to improve error handling and return structured responses. - Updated UI components in the dashboard to improve layout and responsiveness, including changes to `UserDevices` and `UserPayments` pages. - Introduced new `AdminDevicesTable` and `UsersPaymentsTable` components for better admin functionalities. - Enhanced `DeviceCartDrawer` to provide user feedback during payment processing. - Added account number input to the signup form and updated validation schema accordingly. - Updated Prisma schema to include a new `ninja_user_id` field for user management. These changes improve the overall functionality, maintainability, and user experience of the application, particularly in user management and payment processing. --- actions/auth-actions.ts | 1 + actions/payment.ts | 38 +-- actions/user-actions.ts | 2 +- app/(auth)/login/page.tsx | 2 +- app/(auth)/signup/page.tsx | 4 +- app/(dashboard)/user-devices/page.tsx | 4 +- app/(dashboard)/user-payments/page.tsx | 19 +- app/layout.tsx | 2 +- components/admin/admin-devices-table.tsx | 187 +++++++++++++++ components/admin/user-payments-table.tsx | 222 ++++++++++++++++++ components/auth/application-layout.tsx | 2 +- components/auth/signup-form.tsx | 23 +- components/device-cart.tsx | 30 ++- components/devices-table.tsx | 8 +- components/devices-to-pay.tsx | 4 + lib/schemas.ts | 1 + package.json | 3 +- .../20250106070523_add/migration.sql | 2 + prisma/schema.prisma | 4 +- 19 files changed, 512 insertions(+), 46 deletions(-) create mode 100644 components/admin/admin-devices-table.tsx create mode 100644 components/admin/user-payments-table.tsx create mode 100644 prisma/migrations/20250106070523_add/migration.sql diff --git a/actions/auth-actions.ts b/actions/auth-actions.ts index 3cbfc27..91b4eb9 100644 --- a/actions/auth-actions.ts +++ b/actions/auth-actions.ts @@ -116,6 +116,7 @@ export async function signup(_actionState: ActionState, formData: FormData) { id_card: parsedData.data.id_card, dob: new Date(parsedData.data.dob), role: "USER", + accNo: parsedData.data.accNo, phoneNumber: NUMBER_WITH_COUNTRY_CODE, }, }); diff --git a/actions/payment.ts b/actions/payment.ts index 0625397..ce00069 100644 --- a/actions/payment.ts +++ b/actions/payment.ts @@ -9,24 +9,28 @@ import { redirect } from "next/navigation"; import { addDevicesToGroup } from "./omada-actions"; export async function createPayment(data: PaymentType) { - console.log("data", data); - const payment = await prisma.payment.create({ - data: { - amount: data.amount, - numberOfMonths: data.numberOfMonths, - paid: data.paid, - userId: data.userId, - devices: { - connect: data.deviceIds.map((id) => { - return { - id, - }; - }), + try { + console.log("data", data); + const payment = await prisma.payment.create({ + data: { + amount: data.amount, + numberOfMonths: data.numberOfMonths, + paid: data.paid, + userId: data.userId, + devices: { + connect: data.deviceIds.map((id) => { + return { + id, + }; + }), + }, }, - }, - }); - revalidatePath("/devices"); - return payment; + }); + return { success: true, paymentId: payment.id }; + } catch (error) { + console.error("Error creating payment:", error); + return { success: false, error: "Failed to create payment" }; + } } type VerifyPaymentType = { diff --git a/actions/user-actions.ts b/actions/user-actions.ts index bb91bd4..2afbc25 100644 --- a/actions/user-actions.ts +++ b/actions/user-actions.ts @@ -26,7 +26,7 @@ export async function VerifyUser(userId: string) { verified: true, }, }); - await CreateClient({ + const ninjaClient = await CreateClient({ group_settings_id: "", address1: "", city: user.atoll?.name || "", diff --git a/app/(auth)/login/page.tsx b/app/(auth)/login/page.tsx index 2c2cc74..556aff6 100644 --- a/app/(auth)/login/page.tsx +++ b/app/(auth)/login/page.tsx @@ -13,7 +13,7 @@ export default async function LoginPage() { return redirect("/devices"); } return ( -
+
Sar Link Logo
diff --git a/app/(auth)/signup/page.tsx b/app/(auth)/signup/page.tsx index 315300e..a377acd 100644 --- a/app/(auth)/signup/page.tsx +++ b/app/(auth)/signup/page.tsx @@ -29,8 +29,8 @@ export default async function SignupPage({ }); return ( -
-
+
+
Sar Link Logo - +
); diff --git a/app/(dashboard)/user-payments/page.tsx b/app/(dashboard)/user-payments/page.tsx index b603714..e689978 100644 --- a/app/(dashboard)/user-payments/page.tsx +++ b/app/(dashboard)/user-payments/page.tsx @@ -1,8 +1,20 @@ +import { UsersPaymentsTable } from "@/components/admin/user-payments-table"; import { AdminAuthGuard } from "@/lib/auth-guard"; -import React from "react"; +import React, { Suspense } from "react"; -export default async function UserPayments() { +export default async function UserPayments({ + searchParams, +}: { + searchParams: Promise<{ + query: string; + page: number; + sortBy: string; + status: string; + }>; +}) { await AdminAuthGuard(); + const query = (await searchParams)?.query || ""; + return (
@@ -10,6 +22,9 @@ export default async function UserPayments() { User Payments
+ + +
); } diff --git a/app/layout.tsx b/app/layout.tsx index ccefdb5..5c26fe4 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -25,7 +25,7 @@ export default function RootLayout({ }>) { return ( - + diff --git a/components/admin/admin-devices-table.tsx b/components/admin/admin-devices-table.tsx new file mode 100644 index 0000000..0262e29 --- /dev/null +++ b/components/admin/admin-devices-table.tsx @@ -0,0 +1,187 @@ +import { + Table, + TableBody, + TableCaption, + TableCell, + TableFooter, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table"; +import { auth } from "@/lib/auth"; +import prisma from "@/lib/db"; +import { headers } from "next/headers"; +import Link from "next/link"; +import BlockDeviceDialog from "../block-device-dialog"; +import ClickableRow from "../clickable-row"; +import DeviceCard from "../device-card"; +import Pagination from "../pagination"; + +export async function AdminDevicesTable({ + searchParams, + parentalControl, +}: { + searchParams: Promise<{ + query: string; + page: number; + sortBy: string; + }>; + parentalControl?: boolean; +}) { + const session = await auth.api.getSession({ + headers: await headers() + }) + const isAdmin = session?.user.role === "ADMIN" + const query = (await searchParams)?.query || ""; + const page = (await searchParams)?.page; + const sortBy = (await searchParams)?.sortBy || "asc"; + const totalDevices = await prisma.device.count({ + where: { + OR: [ + { + name: { + contains: query || "", + mode: "insensitive", + }, + }, + { + mac: { + contains: query || "", + mode: "insensitive", + }, + }, + ], + }, + }); + + const totalPages = Math.ceil(totalDevices / 10); + const limit = 10; + const offset = (Number(page) - 1) * limit || 0; + + const devices = await prisma.device.findMany({ + where: { + OR: [ + { + name: { + contains: query || "", + mode: "insensitive", + }, + }, + { + mac: { + contains: query || "", + mode: "insensitive", + }, + }, + ], + }, + + skip: offset, + take: limit, + orderBy: { + name: `${sortBy}` as "asc" | "desc", + }, + }); + + return ( +
+ {devices.length === 0 ? ( +
+

No devices yet.

+
+ ) : ( + <> +
+ + Table of all devices. + + + Device Name + MAC Address + isActive + blocked + blockedBy + expiryDate + + + + {devices.map((device) => ( + + +
+ + {device.name} + + + Active until{" "} + {new Date().toLocaleDateString("en-US", { + month: "short", + day: "2-digit", + year: "numeric", + })} + + {device.blocked && ( +
+ Comment: +

+ blocked because he was watching youtube +

+
+ )} + +
+
+ {device.mac} + + {device.isActive ? "Active" : "Inactive"} + + + {device.blocked ? "Blocked" : "Not Blocked"} + + + {device.blockedBy ? device.blockedBy : "Not Blocked"} + + + {new Date().toLocaleDateString("en-US", { + month: "short", + day: "2-digit", + year: "numeric", + })} + + + + +
+ ))} +
+ + + + {query.length > 0 && ( +

+ Showing {devices.length} locations for "{query} + " +

+ )} +
+ + {totalDevices} devices + +
+
+
+ +
+
+ {devices.map((device) => ( + + ))} +
+ + )} +
+ ); +} diff --git a/components/admin/user-payments-table.tsx b/components/admin/user-payments-table.tsx new file mode 100644 index 0000000..9219835 --- /dev/null +++ b/components/admin/user-payments-table.tsx @@ -0,0 +1,222 @@ +import Pagination from "@/components/pagination"; +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { + Table, + TableBody, + TableCaption, + TableCell, + TableFooter, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table"; +import prisma from "@/lib/db"; +import Link from "next/link"; + +export async function UsersPaymentsTable({ + searchParams, +}: { + searchParams: Promise<{ + query: string; + page: number; + sortBy: string; + status: string; + }>; +}) { + const query = (await searchParams)?.query || ""; + const page = (await searchParams)?.page; + const sortBy = (await searchParams)?.sortBy || "asc"; + const totalPayments = await prisma.payment.count({ + where: { + OR: [ + { + user: { + name: { + contains: query || "", + mode: "insensitive", + } + }, + }, + { + user: { + phoneNumber: { + contains: query || "", + mode: "insensitive", + } + }, + }, + { + user: { + address: { + contains: query || "", + mode: "insensitive", + } + }, + }, + { + user: { + id_card: { + contains: query || "", + mode: "insensitive", + } + }, + }, + ], + }, + }); + + const totalPages = Math.ceil(totalPayments / 10); + const limit = 10; + const offset = (Number(page) - 1) * limit || 0; + + const payments = await prisma.payment.findMany({ + where: { + OR: [ + { + user: { + name: { + contains: query || "", + mode: "insensitive", + } + }, + }, + { + user: { + phoneNumber: { + contains: query || "", + mode: "insensitive", + } + }, + }, + { + user: { + address: { + contains: query || "", + mode: "insensitive", + } + }, + }, + { + user: { + id_card: { + contains: query || "", + mode: "insensitive", + } + }, + }, + ], + }, + include: { + user: true, + devices: true, + }, + skip: offset, + take: limit, + orderBy: { + id: `${sortBy}` as "asc" | "desc", + }, + }); + + // const users = await prisma.user.findMany({ + // where: { + // role: "USER", + // }, + // include: { + // atoll: true, + // island: true, + // }, + // }); + return ( +
+ {payments.length === 0 ? ( +
+

No Users yet.

+
+ ) : ( + <> + + Table of all users. + + + Name + ID Card + Atoll + Island + House Name + Status + Dob + Phone Number + Action + + + + {payments.map((payment) => ( + + {payment.user.name} + {payment.user.id_card} + {payment.user?.name} + {payment.user?.name} + {payment.id} + + + {payment.paid ? ( + + Verified + + ) : ( + + Unverified + + )} + + + {new Date(payment.paidAt ?? "").toLocaleDateString("en-US", { + month: "short", + day: "2-digit", + year: "numeric", + })} + + + {payment.id} + + + + + + + ))} + + + + + {query.length > 0 && ( +

+ Showing {payments.length} locations for "{query} + " +

+ )} +
+ + {totalPayments} payments + +
+
+
+ + + )} +
+ ); +} diff --git a/components/auth/application-layout.tsx b/components/auth/application-layout.tsx index 137fa65..cc38a95 100644 --- a/components/auth/application-layout.tsx +++ b/components/auth/application-layout.tsx @@ -37,7 +37,7 @@ export async function ApplicationLayout({ {session?.user.role === "ADMIN" && ( - + Welcome back {session?.user.name} )} diff --git a/components/auth/signup-form.tsx b/components/auth/signup-form.tsx index 6e20497..355df34 100644 --- a/components/auth/signup-form.tsx +++ b/components/auth/signup-form.tsx @@ -48,7 +48,7 @@ export default function SignUpForm({ atolls }: { atolls: AtollWithIslands[] }) { return (
@@ -208,7 +208,28 @@ export default function SignUpForm({ atolls }: { atolls: AtollWithIslands[] }) { )}
+
+ + + {actionState.errors?.fieldErrors.accNo && ( + + {actionState.errors?.fieldErrors.accNo} + + )} +