From 9e2a2f430ee1e659c035ba9b81d98ae1080a586b Mon Sep 17 00:00:00 2001 From: i701 Date: Sat, 5 Apr 2025 23:25:17 +0500 Subject: [PATCH] refactor: update payment types and user interface, enhance error handling, and adjust API base URL --- actions/payment.ts | 385 ++++++++++++------ app/(dashboard)/payments/[paymentId]/page.tsx | 33 +- app/next-auth.d.ts | 2 + components/auth/account-popver.tsx | 11 +- components/auth/application-layout.tsx | 4 +- components/clickable-row.tsx | 129 +++--- components/devices-for-payment.tsx | 21 +- components/devices-to-pay.tsx | 362 ++++++++-------- components/payments-table.tsx | 36 +- components/wallet.tsx | 2 +- lib/backend-types.ts | 20 + lib/types.ts | 8 - lib/types/user.ts | 2 + queries/authentication.ts | 2 +- utils/axiosInstance.ts | 2 +- 15 files changed, 596 insertions(+), 423 deletions(-) diff --git a/actions/payment.ts b/actions/payment.ts index 3132773..4c140aa 100644 --- a/actions/payment.ts +++ b/actions/payment.ts @@ -1,29 +1,146 @@ "use server"; -import type { PaymentType } from "@/lib/types"; -import { formatMacAddress } from "@/lib/utils"; +import { authOptions } from "@/app/auth"; +import type { ApiResponse, NewPayment, Payment } from "@/lib/backend-types"; +import type { User } from "@/lib/types/user"; +import { tryCatch } from "@/utils/tryCatch"; +import { getServerSession } from "next-auth"; import { revalidatePath } from "next/cache"; import { redirect } from "next/navigation"; -import { addDevicesToGroup } from "./omada-actions"; -export async function createPayment(data: PaymentType) { +export async function createPayment(data: NewPayment) { + const session = await getServerSession(authOptions); 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, - // }; - // }), - // }, - // }, - // }); - // redirect(`/payments/${payment.id}`); + const response = await fetch( + `${ + process.env.SARLINK_API_BASE_URL // }); + }/api/billing/payment/`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Token ${session?.apiToken}`, + }, + body: JSON.stringify(data), + }, + ); + + if (!response.ok) { + const errorData = await response.json(); + // Throw an error with the message from the API + throw new Error(errorData.message || "Something went wrong."); + } + const payment = (await response.json()) as Payment; + revalidatePath("/devices"); + redirect(`/payments/${payment.id}`); +} + +export async function getPayment({ id }: { id: string }) { + const session = await getServerSession(authOptions); + const response = await fetch( + `${process.env.SARLINK_API_BASE_URL}/api/billing/payment/${id}`, + { + method: "GET", + headers: { + "Content-Type": "application/json", + Authorization: `Token ${session?.apiToken}`, + }, + }, + ); + + if (!response.ok) { + const errorData = await response.json(); + // Throw an error with the message from the API + throw new Error(errorData.message || "Something went wrong."); + } + const payment = (await response.json()) as Payment; + return payment; +} + +export async function getPayments() { + const session = await getServerSession(authOptions); + const respose = await fetch( + `${process.env.SARLINK_API_BASE_URL}/api/billing/payment/`, + { + method: "GET", + headers: { + "Content-Type": "application/json", + Authorization: `Token ${session?.apiToken}`, + }, + }, + ); + const data = (await respose.json()) as ApiResponse; + return data; +} + +type UpdatePayment = Pick< + Payment, + "id" | "paid" | "paid_at" | "method" | "number_of_months" +>; +export async function updatePayment({ + id, + method, + paid, + paid_at, + number_of_months, +}: UpdatePayment) { + const session = await getServerSession(authOptions); + const response = await fetch( + `${process.env.SARLINK_API_BASE_URL}/api/billing/payment/${id}/update/`, + { + method: "PUT", + headers: { + "Content-Type": "application/json", + Authorization: `Token ${session?.apiToken}`, + }, + body: JSON.stringify({ + method, + paid, + paid_at, + number_of_months, + }), + }, + ); + + if (!response.ok) { + const errorData = await response.json(); + // Throw an error with the message from the API + throw new Error(errorData.message || "Something went wrong."); + } + const payment = (await response.json()) as Payment; + return payment; +} + +type TUpdateWalletBalance = Pick; +export async function updateWalletBalance({ + id, + wallet_balance, +}: TUpdateWalletBalance) { + const session = await getServerSession(authOptions); + console.log("wallet bal in server action", wallet_balance); + const response = await fetch( + `${process.env.SARLINK_API_BASE_URL}/api/auth/update-wallet/${id}/`, + { + method: "PUT", + headers: { + "Content-Type": "application/json", + Authorization: `Token ${session?.apiToken}`, + }, + body: JSON.stringify({ + wallet_balance: Number.parseFloat(wallet_balance?.toFixed(2) ?? "0"), + }), + }, + ); + + if (!response.ok) { + const errorData = await response.json(); + // Throw an error with the message from the API + throw new Error(errorData.message || "Something went wrong."); + } + const message = (await response.json()) as { + message: "Wallet balance updated successfully."; + }; + return message; } type VerifyPaymentType = { @@ -43,45 +160,45 @@ class InsufficientFundsError extends Error { } } -async function processWalletPayment( - user: { id: string; walletBalance: number } | null, - payment: PaymentWithDevices | null, - amount: number, -) { - if (!user || !payment) { +export async function processWalletPayment({ + payment, + amount, +}: { payment: Payment | undefined; amount: number }) { + const session = await getServerSession(authOptions); + if (!session?.user || !payment) { throw new Error("User or payment not found"); } - const walletBalance = user.walletBalance ?? 0; + const walletBalance = session.user.wallet_balance ?? 0; if (walletBalance < amount) { throw new InsufficientFundsError(); } - const expiryDate = new Date(); - expiryDate.setMonth(expiryDate.getMonth() + payment.numberOfMonths); - await prisma.$transaction([ - prisma.payment.update({ - where: { id: payment.id }, - data: { - paid: true, - paidAt: new Date(), - method: "WALLET", - devices: { - updateMany: payment.devices.map((device) => ({ - where: { id: device.id }, - data: { - isActive: true, - expiryDate: expiryDate, - }, - })), - }, - }, + const [updatePaymentError, _] = await tryCatch( + updatePayment({ + id: payment.id, + method: "WALLET", + paid: true, + paid_at: new Date().toISOString(), + number_of_months: payment.number_of_months, }), - prisma.user.update({ - where: { id: user.id }, - data: { walletBalance: walletBalance - amount }, + ); + if (updatePaymentError) { + throw new Error(updatePaymentError.message); + } + console.log("Wallet balance before update:", walletBalance); + const updated_balance = walletBalance - amount; + const [walletUpdateError, response] = await tryCatch( + updateWalletBalance({ + id: session.user.id, + wallet_balance: Number.parseFloat(updated_balance?.toFixed(2) ?? "0"), }), - ]); + ); + if (walletUpdateError) { + throw new Error(walletUpdateError.message); + } + revalidatePath("/payments/[paymentsId]", "page"); + return response; } type VerifyPaymentResponse = @@ -99,98 +216,98 @@ type VerifyPaymentResponse = }; }; -async function verifyExternalPayment( - data: VerifyPaymentType, - payment: PaymentWithDevices | null, -): Promise { - console.log("payment verify data ->", data); - const response = await fetch( - "https://verifypaymentsapi.baraveli.dev/verify-payment", - { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(data), - }, - ); +// async function verifyExternalPayment( +// data: VerifyPaymentType, +// payment: PaymentWithDevices | null, +// ): Promise { +// console.log("payment verify data ->", data); +// const response = await fetch( +// "https://verifypaymentsapi.baraveli.dev/verify-payment", +// { +// method: "POST", +// headers: { "Content-Type": "application/json" }, +// body: JSON.stringify(data), +// }, +// ); - const json = await response.json(); - console.log(json); - if (!payment) { - throw new Error("Payment verification failed or payment not found"); - } +// const json = await response.json(); +// console.log(json); +// if (!payment) { +// throw new Error("Payment verification failed or payment not found"); +// } - if (json.success) { - const expiryDate = new Date(); - expiryDate.setMonth(expiryDate.getMonth() + payment.numberOfMonths); - await prisma.payment.update({ - where: { id: payment.id }, - data: { - paid: true, - paidAt: new Date(), - method: "TRANSFER", - devices: { - updateMany: payment.devices.map((device) => ({ - where: { id: device.id }, - data: { - isActive: true, - expiryDate: expiryDate, - }, - })), - }, - }, - }); - } +// if (json.success) { +// const expiryDate = new Date(); +// expiryDate.setMonth(expiryDate.getMonth() + payment.numberOfMonths); +// await prisma.payment.update({ +// where: { id: payment.id }, +// data: { +// paid: true, +// paidAt: new Date(), +// method: "TRANSFER", +// devices: { +// updateMany: payment.devices.map((device) => ({ +// where: { id: device.id }, +// data: { +// isActive: true, +// expiryDate: expiryDate, +// }, +// })), +// }, +// }, +// }); +// } - return json; -} +// return json; +// } -async function updateDevices(payment: PaymentWithDevices | null) { - if (!payment) return; +// async function updateDevices(payment: PaymentWithDevices | null) { +// if (!payment) return; - const newDevices = payment.devices.map((d) => ({ - name: d.name, - macAddress: formatMacAddress(d.mac), - })); +// const newDevices = payment.devices.map((d) => ({ +// name: d.name, +// macAddress: formatMacAddress(d.mac), +// })); - return await addDevicesToGroup({ - groupId: process.env.OMADA_GROUP_ID, - siteId: process.env.OMADA_SITE_ID, - newDevices, - }); -} +// return await addDevicesToGroup({ +// groupId: process.env.OMADA_GROUP_ID, +// siteId: process.env.OMADA_SITE_ID, +// newDevices, +// }); +// } -export async function verifyPayment(data: VerifyPaymentType) { - try { - const [payment, user] = await Promise.all([ - prisma.payment.findUnique({ - where: { id: data.paymentId }, - include: { devices: true }, - }), - prisma.user.findUnique({ - where: { id: data.userId }, - }), - ]); +// export async function verifyPayment(data: VerifyPaymentType) { +// try { +// const [payment, user] = await Promise.all([ +// prisma.payment.findUnique({ +// where: { id: data.paymentId }, +// include: { devices: true }, +// }), +// prisma.user.findUnique({ +// where: { id: data.userId }, +// }), +// ]); - if (data.type === "WALLET") { - console.log("WALLET"); - await processWalletPayment(user, payment, Number(data.absAmount)); - redirect("/payments"); - } - if (data.type === "TRANSFER") { - console.log({ data, payment }); - const verificationResult = await verifyExternalPayment(data, payment); - await updateDevices(payment); +// if (data.type === "WALLET") { +// console.log("WALLET"); +// await processWalletPayment(user, payment, Number(data.absAmount)); +// redirect("/payments"); +// } +// if (data.type === "TRANSFER") { +// console.log({ data, payment }); +// const verificationResult = await verifyExternalPayment(data, payment); +// await updateDevices(payment); - revalidatePath("/payment[paymentId]"); +// revalidatePath("/payment[paymentId]"); - return verificationResult; - } - } catch (error) { - console.error("Payment verification failed:", error); - throw error; // Re-throw to handle at a higher level - } -} +// return verificationResult; +// } +// } catch (error) { +// console.error("Payment verification failed:", error); +// throw error; // Re-throw to handle at a higher level +// } +// } -export async function addDevicesToOmada() { - console.log("hi"); -} +// export async function addDevicesToOmada() { +// console.log("hi"); +// } diff --git a/app/(dashboard)/payments/[paymentId]/page.tsx b/app/(dashboard)/payments/[paymentId]/page.tsx index 198d2cd..b77fc34 100644 --- a/app/(dashboard)/payments/[paymentId]/page.tsx +++ b/app/(dashboard)/payments/[paymentId]/page.tsx @@ -1,7 +1,9 @@ +import { getPayment } from "@/actions/payment"; +import { authOptions } from "@/app/auth"; import DevicesToPay from "@/components/devices-to-pay"; -import { auth } from "@/app/auth"; -import prisma from "@/lib/db"; import { cn } from "@/lib/utils"; +import { tryCatch } from "@/utils/tryCatch"; +import { getServerSession } from "next-auth"; import { headers } from "next/headers"; import React from "react"; export default async function PaymentPage({ @@ -9,23 +11,13 @@ export default async function PaymentPage({ }: { params: Promise<{ paymentId: string }>; }) { - const session = await auth.api.getSession({ - headers: await headers(), - }); - const user = await prisma.user.findUnique({ - where: { - id: session?.session.userId, - }, - }); + const session = await getServerSession(authOptions); + const paymentId = (await params).paymentId; - const payment = await prisma.payment.findUnique({ - where: { - id: paymentId, - }, - include: { - devices: true, - }, - }); + const [error, payment] = await tryCatch(getPayment({ id: paymentId })); + if (error) { + return
Error getting payment: {error.message}
; + } return (
@@ -46,7 +38,10 @@ export default async function PaymentPage({ id="user-filters" className="pb-4 gap-4 flex sm:flex-row flex-col items-start justify-start" > - +
); diff --git a/app/next-auth.d.ts b/app/next-auth.d.ts index 93470f4..0cce531 100644 --- a/app/next-auth.d.ts +++ b/app/next-auth.d.ts @@ -16,6 +16,8 @@ declare module "next-auth" { username?: string; user_permissions?: { id: number; name: string }[]; id_card?: string; + mobile?: string; + wallet_balance?: number; first_name?: string; last_name?: string; last_login?: string; diff --git a/components/auth/account-popver.tsx b/components/auth/account-popver.tsx index efdda2a..fa60667 100644 --- a/components/auth/account-popver.tsx +++ b/components/auth/account-popver.tsx @@ -27,15 +27,16 @@ export function AccountPopover() { - +

- {session.data?.user?.name} + {session.data?.user?.first_name} {session.data?.user?.last_name}

-

- {session.data?.user?.id_card} -

+
+

{session.data?.user?.id_card}

+

{session.data?.user?.mobile}

+
-
- - ); + const [accNo, setAccNo] = useState(false); + return ( +
+
+ Account Information +
+
+
Account Name
+ {accName} +
+
+
+

Account No

+ {accountNo} +
+ +
+
+ ); } diff --git a/components/payments-table.tsx b/components/payments-table.tsx index bd3eeda..4752a53 100644 --- a/components/payments-table.tsx +++ b/components/payments-table.tsx @@ -10,7 +10,10 @@ import { } from "@/components/ui/table"; import Link from "next/link"; +import { getPayments } from "@/actions/payment"; +import type { Payment } from "@/lib/backend-types"; import { cn } from "@/lib/utils"; +import { tryCatch } from "@/utils/tryCatch"; import { Calendar } from "lucide-react"; import Pagination from "./pagination"; import { Badge } from "./ui/badge"; @@ -26,6 +29,7 @@ export async function PaymentsTable({ sortBy: string; }>; }) { + const query = (await searchParams)?.query || ""; // const session = await auth.api.getSession({ // headers: await headers(), // }); @@ -79,11 +83,15 @@ export async function PaymentsTable({ // createdAt: "desc", // }, // }); + const [error, payments] = await tryCatch(getPayments()); - return null; + if (error) { + return
{JSON.stringify(error, null, 2)}
; + } + const { data, meta, links } = payments; return (
- {payments.length === 0 ? ( + {data?.length === 0 ? (

No Payments yet.

@@ -101,7 +109,7 @@ export async function PaymentsTable({ - {payments.map((payment) => ( + {payments?.data?.map((payment) => (
- {new Date(payment.createdAt).toLocaleDateString( + {new Date(payment.created_at).toLocaleDateString( "en-US", { month: "short", @@ -162,7 +170,7 @@ export async function PaymentsTable({
- {payment.numberOfMonths} Months + {payment.number_of_months} Months @@ -178,21 +186,25 @@ export async function PaymentsTable({ {query.length > 0 && (

- Showing {payments.length} locations for "{query} + Showing {payments?.data?.length} locations for " + {query} "

)}
- {totalPayments} payments + {meta.total} payments
- +
- {payments.map((payment) => ( + {data.map((payment) => ( ))}
@@ -202,7 +214,7 @@ export async function PaymentsTable({ ); } -function MobilePaymentDetails({ payment }: { payment: PaymentWithDevices }) { +function MobilePaymentDetails({ payment }: { payment: Payment }) { return (
- {new Date(payment.createdAt).toLocaleDateString("en-US", { + {new Date(payment.created_at).toLocaleDateString("en-US", { month: "short", day: "2-digit", year: "numeric", @@ -256,7 +268,7 @@ function MobilePaymentDetails({ payment }: { payment: PaymentWithDevices }) {

Duration

- {payment.numberOfMonths} Months + {payment.number_of_months} Months

Amount

diff --git a/components/wallet.tsx b/components/wallet.tsx index ec3f798..808aafe 100644 --- a/components/wallet.tsx +++ b/components/wallet.tsx @@ -37,7 +37,7 @@ export function Wallet({ } const data: TopupType = { - userId: session?.data?.user.id ?? "", + userId: session?.data?.user?.id ?? "", amount: Number.parseFloat(amount.toFixed(2)), paid: false, }; diff --git a/lib/backend-types.ts b/lib/backend-types.ts index e4b73f1..0620689 100644 --- a/lib/backend-types.ts +++ b/lib/backend-types.ts @@ -50,3 +50,23 @@ export interface Api400Error { message: string; }; } + +export interface Payment { + id: string; + devices: Device[]; + number_of_months: number; + amount: number; + paid: boolean; + paid_at: string | null; + method: string; + expires_at: string | null; + created_at: string; + updated_at: string; + user: number; +} + +export interface NewPayment { + device_ids: number[]; + number_of_months: number; + amount: number; +} diff --git a/lib/types.ts b/lib/types.ts index 54b1a01..5fcbb85 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -1,11 +1,3 @@ -export type PaymentType = { - numberOfMonths: number; - userId: string; - deviceIds: string[]; - amount: number; - paid: boolean; -}; - export type TopupType = { amount: number; userId: string; diff --git a/lib/types/user.ts b/lib/types/user.ts index 41de3f4..936c004 100644 --- a/lib/types/user.ts +++ b/lib/types/user.ts @@ -19,6 +19,8 @@ export interface User { user_permissions: Permission[]; first_name: string; last_name: string; + mobile?: string; + wallet_balance?: number; is_superuser: boolean; date_joined: string; last_login: string; diff --git a/queries/authentication.ts b/queries/authentication.ts index 2f012ad..73e0bd8 100644 --- a/queries/authentication.ts +++ b/queries/authentication.ts @@ -28,7 +28,7 @@ export async function login({ export async function logout({ token }: { token: string }) { const response = await fetch( - `${process.env.NEXT_PUBLIC_API_URL}/auth/logout/`, + `${process.env.SARLINK_API_BASE_URL}/auth/logout/`, { method: "POST", headers: { diff --git a/utils/axiosInstance.ts b/utils/axiosInstance.ts index 68cef36..d9ef976 100644 --- a/utils/axiosInstance.ts +++ b/utils/axiosInstance.ts @@ -4,7 +4,7 @@ axios.defaults.xsrfCookieName = "csrftoken"; axios.defaults.xsrfHeaderName = "X-CSRFToken"; const axiosInstance = axios.create({ - baseURL: process.env.NEXT_PUBLIC_API_URL, + baseURL: process.env.SARLINK_API_BASE_URL, validateStatus: (status) => { return status < 500; // Resolve only if the status code is less than 500 },