From 5dab74b14bed5ed1a5511326be31d49b749c2ee7 Mon Sep 17 00:00:00 2001 From: i701 Date: Sun, 21 Sep 2025 10:07:16 +0500 Subject: [PATCH] =?UTF-8?q?refactor:=20create=20utility=20function=20to=20?= =?UTF-8?q?hide=20AccountInformation=20component=20for=20topup=20and=20pay?= =?UTF-8?q?ment=20=F0=9F=94=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/(dashboard)/top-ups/[topupId]/page.tsx | 128 ++++---- components/devices-to-pay.tsx | 355 +++++++++++---------- components/topup-to-pay.tsx | 273 ++++++++-------- lib/utils.ts | 78 +++-- 4 files changed, 430 insertions(+), 404 deletions(-) diff --git a/app/(dashboard)/top-ups/[topupId]/page.tsx b/app/(dashboard)/top-ups/[topupId]/page.tsx index 8bc9827..a36e23f 100644 --- a/app/(dashboard)/top-ups/[topupId]/page.tsx +++ b/app/(dashboard)/top-ups/[topupId]/page.tsx @@ -9,72 +9,72 @@ import { TextShimmer } from "@/components/ui/text-shimmer"; import { cn } from "@/lib/utils"; import { tryCatch } from "@/utils/tryCatch"; export default async function TopupPage({ - params, + params, }: { - params: Promise<{ topupId: string }>; + params: Promise<{ topupId: string }>; }) { - const topupId = (await params).topupId; - const [error, topup] = await tryCatch(getTopup({ id: topupId })); - if (error) { - if (error.message === "Invalid token.") redirect("/auth/signin"); - return ; - } + const topupId = (await params).topupId; + const [error, topup] = await tryCatch(getTopup({ id: topupId })); + if (error) { + if (error.message === "Invalid token.") redirect("/auth/signin"); + return ; + } - return ( -
-
-

Topup

-
- {!topup.is_expired && topup.paid && topup.status !== "PENDING" && ( - - )} - {topup.status === "PENDING" && !topup.is_expired && ( - - )} + return ( +
+
+

Topup

+
+ {!topup.is_expired && topup.paid && topup.status !== "PENDING" && ( + + )} + {topup.status === "PENDING" && !topup.is_expired && ( + + )} - {!topup.paid && - (topup.is_expired ? ( - - ) : topup.status === "PENDING" ? ( - - ) : topup.status === "CANCELLED" ? ( - - ) : ( - "" - ))} -
-
- {!topup.paid && topup.status === "PENDING" && !topup.is_expired && ( - - )} -
- -
-
- ); + {!topup.paid && + (topup.is_expired ? ( + + ) : topup.status === "PENDING" ? ( + + ) : topup.status === "CANCELLED" ? ( + + ) : ( + "" + ))} +
+
+ {!topup.paid && topup.status === "PENDING" && !topup.is_expired && ( + + )} +
+ +
+
+ ); } diff --git a/components/devices-to-pay.tsx b/components/devices-to-pay.tsx index 90bbd24..c60f5de 100644 --- a/components/devices-to-pay.tsx +++ b/components/devices-to-pay.tsx @@ -3,198 +3,201 @@ import { BadgeDollarSign, Loader2, Wallet } from "lucide-react"; import { useActionState, useEffect } from "react"; import { toast } from "sonner"; import { - type VerifyDevicePaymentState, - verifyDevicePayment, + type VerifyDevicePaymentState, + verifyDevicePayment, } from "@/actions/payment"; import { - Table, - TableBody, - TableCaption, - TableCell, - TableFooter, - TableRow, + Table, + TableBody, + TableCaption, + TableCell, + TableFooter, + TableRow, } from "@/components/ui/table"; import type { Payment } from "@/lib/backend-types"; import type { User } from "@/lib/types/user"; +import { shouldShowPaymentInfo } from "@/lib/utils"; import { AccountInfomation } from "./account-information"; import { Button } from "./ui/button"; const initialState: VerifyDevicePaymentState = { - message: "", - success: false, - fieldErrors: {}, + message: "", + success: false, + fieldErrors: {}, }; export default function DevicesToPay({ - payment, - user, - disabled, + payment, + user, + disabled, }: { - payment?: Payment; - user?: User; - disabled?: boolean; + payment?: Payment; + user?: User; + disabled?: boolean; }) { - const [state, formAction, isPending] = useActionState( - verifyDevicePayment, - initialState, - ); + const [state, formAction, isPending] = useActionState( + verifyDevicePayment, + initialState, + ); - // Handle toast notifications based on state changes - useEffect(() => { - if (state.success && state.message) { - toast.success("Payment successful!", { - closeButton: true, - description: state.message, - }); - } else if ( - !state.success && - state.message && - state.message !== initialState.message - ) { - toast.error("Payment Verification Failed", { - closeButton: true, - description: state.message, - }); - } - }, [state]); + // Handle toast notifications based on state changes + useEffect(() => { + if (state.success && state.message) { + toast.success("Payment successful!", { + closeButton: true, + description: state.message, + }); + } else if ( + !state.success && + state.message && + state.message !== initialState.message + ) { + toast.error("Payment Verification Failed", { + closeButton: true, + description: state.message, + }); + } + }, [state]); - const devices = payment?.devices; - if (devices?.length === 0) { - return null; - } - // 100+(n−1)×75 - const walletBalance = user?.wallet_balance ?? 0; + const devices = payment?.devices; + if (devices?.length === 0) { + return null; + } + // 100+(n−1)×75 + const walletBalance = user?.wallet_balance ?? 0; - const isWalletPayVisible = walletBalance > (payment?.amount ?? 0); + const isWalletPayVisible = walletBalance > (payment?.amount ?? 0); - return ( -
-
-

- {!payment?.paid ? "Devices to pay" : "Devices Paid"} -

-
- {devices?.map((device) => ( -
-
-
{device.name}
-
- {device.mac} -
-
-
- ))} -
-
-
- - -
-

Please send the following amount to the payment address

- - {payment?.paid ? ( - - ) : ( -
- {isWalletPayVisible && ( -
- - - - - )} -
- - - - -
- )} -
-
- - - Payment created - - {new Date(payment?.created_at ?? "").toLocaleDateString( - "en-US", - { - month: "short", - day: "2-digit", - year: "numeric", - minute: "2-digit", - hour: "2-digit", - second: "2-digit", - }, - )} - - - - Total Devices - - {devices?.length} - - - - Duration - - {payment?.number_of_months} Months - - - - - - Total Due - - {payment?.amount?.toFixed(2)} - - - -
-
-
- ); + return ( +
+
+

+ {!payment?.paid ? "Devices to pay" : "Devices Paid"} +

+
+ {devices?.map((device) => ( +
+
+
{device.name}
+
+ {device.mac} +
+
+
+ ))} +
+
+
+ + + {shouldShowPaymentInfo(payment) && ( +
+

Please send the following amount to the payment address

+ + {payment?.paid ? ( + + ) : ( +
+ {isWalletPayVisible && ( +
+ + + + + )} +
+ + + + +
+ )} +
+ )} +
+ + + Payment created + + {new Date(payment?.created_at ?? "").toLocaleDateString( + "en-US", + { + month: "short", + day: "2-digit", + year: "numeric", + minute: "2-digit", + hour: "2-digit", + second: "2-digit", + }, + )} + + + + Total Devices + + {devices?.length} + + + + Duration + + {payment?.number_of_months} Months + + + + + + Total Due + + {payment?.amount?.toFixed(2)} + + + +
+
+
+ ); } diff --git a/components/topup-to-pay.tsx b/components/topup-to-pay.tsx index fd90e08..1704eb1 100644 --- a/components/topup-to-pay.tsx +++ b/components/topup-to-pay.tsx @@ -3,155 +3,154 @@ import { BadgeDollarSign, Loader2 } from "lucide-react"; import { useActionState, useEffect } from "react"; import { toast } from "sonner"; import { - type VerifyTopupPaymentState, - verifyTopupPayment, + type VerifyTopupPaymentState, + verifyTopupPayment, } from "@/actions/payment"; import { - Table, - TableBody, - TableCaption, - TableCell, - TableFooter, - TableRow, + Table, + TableBody, + TableCaption, + TableCell, + TableFooter, + TableRow, } from "@/components/ui/table"; import type { Topup } from "@/lib/backend-types"; +import { shouldShowTopupPaymentInfo } from "@/lib/utils"; import { AccountInfomation } from "./account-information"; import { Button } from "./ui/button"; const initialState: VerifyTopupPaymentState = { - message: "", - success: false, - fieldErrors: {}, + message: "", + success: false, + fieldErrors: {}, }; export default function TopupToPay({ - topup, - disabled, + topup, + disabled, }: { - topup?: Topup; - disabled?: boolean; + topup?: Topup; + disabled?: boolean; }) { - const [state, formAction, isPending] = useActionState( - verifyTopupPayment, - initialState, - ); + const [state, formAction, isPending] = useActionState( + verifyTopupPayment, + initialState, + ); - // Handle toast notifications based on state changes - useEffect(() => { - if (state.success && state.message) { - toast.success("Topup successful!", { - closeButton: true, - description: state.transaction - ? `Your topup payment has been verified successfully using ${state.transaction.sourceBank} bank transfer on ${state.transaction.trxDate}.` - : state.message, - }); - } else if ( - !state.success && - state.message && - state.message !== initialState.message - ) { - toast.error("Topup Payment Verification Failed", { - closeButton: true, - description: state.message, - }); - } - }, [state]); + // Handle toast notifications based on state changes + useEffect(() => { + if (state.success && state.message) { + toast.success("Topup successful!", { + closeButton: true, + description: state.transaction + ? `Your topup payment has been verified successfully using ${state.transaction.sourceBank} bank transfer on ${state.transaction.trxDate}.` + : state.message, + }); + } else if ( + !state.success && + state.message && + state.message !== initialState.message + ) { + toast.error("Topup Payment Verification Failed", { + closeButton: true, + description: state.message, + }); + } + }, [state]); - return ( -
-
- - - {(!topup?.paid || - !topup?.is_expired || - topup?.status !== "CANCELLED") && ( -
-

Please send the following amount to the payment address

- - {topup?.paid ? ( - - ) : ( -
-
- - - -
- )} -
- )} -
- - - Topup created - - {new Date(topup?.created_at ?? "").toLocaleDateString("en-US", { - month: "short", - day: "2-digit", - year: "numeric", - minute: "2-digit", - hour: "2-digit", - second: "2-digit", - })} - - - - Payment received - - {topup?.paid_at - ? new Date(topup.paid_at).toLocaleDateString("en-US", { - month: "short", - day: "2-digit", - year: "numeric", - minute: "2-digit", - hour: "2-digit", - second: "2-digit", - }) - : "-"} - - - - MIB Reference - - {topup?.mib_reference ? topup.mib_reference : "-"} - - - - - - Total Due - - {topup?.amount?.toFixed(2)} - - - -
-
-
- ); + return ( +
+
+ + + {shouldShowTopupPaymentInfo(topup) && ( +
+

Please send the following amount to the payment address

+ + {topup?.paid ? ( + + ) : ( +
+
+ + + +
+ )} +
+ )} +
+ + + Topup created + + {new Date(topup?.created_at ?? "").toLocaleDateString("en-US", { + month: "short", + day: "2-digit", + year: "numeric", + minute: "2-digit", + hour: "2-digit", + second: "2-digit", + })} + + + + Payment received + + {topup?.paid_at + ? new Date(topup.paid_at).toLocaleDateString("en-US", { + month: "short", + day: "2-digit", + year: "numeric", + minute: "2-digit", + hour: "2-digit", + second: "2-digit", + }) + : "-"} + + + + MIB Reference + + {topup?.mib_reference ? topup.mib_reference : "-"} + + + + + + Total Due + + {topup?.amount?.toFixed(2)} + + + +
+
+
+ ); } diff --git a/lib/utils.ts b/lib/utils.ts index dcaafd8..b2385ba 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -1,48 +1,72 @@ import { type ClassValue, clsx } from "clsx"; import { twMerge } from "tailwind-merge"; +import type { Payment, Topup } from "./backend-types"; export function cn(...inputs: ClassValue[]) { - return twMerge(clsx(inputs)); + return twMerge(clsx(inputs)); } export const formatDate = (date: Date): string => { - const pad = (num: number): string => num.toString().padStart(2, "0"); + const pad = (num: number): string => num.toString().padStart(2, "0"); - const year = date.getFullYear(); - const month = pad(date.getMonth() + 1); // Months are zero-based - const day = pad(date.getDate()); - const hours = pad(date.getHours()); - const minutes = pad(date.getMinutes() + 5); + const year = date.getFullYear(); + const month = pad(date.getMonth() + 1); // Months are zero-based + const day = pad(date.getDate()); + const hours = pad(date.getHours()); + const minutes = pad(date.getMinutes() + 5); - return `${year}-${month}-${day} ${hours}:${minutes}`; + return `${year}-${month}-${day} ${hours}:${minutes}`; }; export const formatMacAddress = (mac: string): string => { - const formatted = mac - .replace(/[^A-Fa-f0-9]/g, "") - .toUpperCase() - .match(/.{2}/g); + const formatted = mac + .replace(/[^A-Fa-f0-9]/g, "") + .toUpperCase() + .match(/.{2}/g); - // Provide a fallback if formatted is null - return formatted ? formatted.join("-") : ""; + // Provide a fallback if formatted is null + return formatted ? formatted.join("-") : ""; }; export function validateApiKey(request: Request) { - // Get API key from environment variable - const validApiKey = process.env.CRON_API_KEY; + // Get API key from environment variable + const validApiKey = process.env.CRON_API_KEY; - if (!validApiKey) { - throw new Error("CRON_API_KEY is not configured"); - } + if (!validApiKey) { + throw new Error("CRON_API_KEY is not configured"); + } - // Get API key from request header - const apiKey = request.headers.get("x-api-key"); + // Get API key from request header + const apiKey = request.headers.get("x-api-key"); - if (!apiKey) { - throw new Error("API key is missing"); - } + if (!apiKey) { + throw new Error("API key is missing"); + } - if (apiKey !== validApiKey) { - throw new Error("Invalid API key"); - } + if (apiKey !== validApiKey) { + throw new Error("Invalid API key"); + } +} + +export function shouldShowTopupPaymentInfo(topup: Topup | undefined): boolean { + if (!topup) return false; + + return !( + topup.paid || + topup.is_expired || + topup.status === "CANCELLED" || + topup.status === "VERIFIED" + ); +} + + +export function shouldShowPaymentInfo(topup: Payment | undefined): boolean { + if (!topup) return false; + + return !( + topup.paid || + topup.is_expired || + topup.status === "CANCELLED" || + topup.status === "PAID" + ); }