mirror of
https://github.com/i701/sarlink-portal.git
synced 2025-02-22 23:42:00 +00:00
- Introduced wallet payment option in verifyPayment function to allow users to pay using their wallet balance. - Added new BlockDeviceDialog component for managing device blocking and unblocking actions. - Updated DeviceCard component to display device status and integrate blocking functionality. - Refactored DevicesTable to utilize DeviceCard for better UI representation of devices. - Implemented Wallet component to manage wallet balance and top-up functionality. - Enhanced API routes and Prisma schema to support wallet transactions and device blocking reasons. - Improved overall user experience with responsive design adjustments and new UI elements. These changes improve user control over payments and device management, enhancing the overall functionality of the application.
190 lines
6.7 KiB
TypeScript
190 lines
6.7 KiB
TypeScript
'use client'
|
||
import { verifyPayment } from "@/actions/payment";
|
||
import {
|
||
Table,
|
||
TableBody,
|
||
TableCaption,
|
||
TableCell,
|
||
TableFooter,
|
||
TableRow,
|
||
} from "@/components/ui/table";
|
||
import { formatDate } from "@/lib/utils";
|
||
import type { BillFormula, Prisma, User } from "@prisma/client";
|
||
import { BadgeDollarSign, Clipboard, ClipboardCheck, Loader2, Wallet } from "lucide-react";
|
||
import { useState } from "react";
|
||
import { toast } from "sonner";
|
||
import { Button } from "./ui/button";
|
||
|
||
type PaymentWithDevices = Prisma.PaymentGetPayload<{
|
||
include: {
|
||
devices: true;
|
||
};
|
||
}>;
|
||
|
||
export default function DevicesToPay({
|
||
billFormula,
|
||
payment,
|
||
user
|
||
}: { billFormula?: BillFormula; payment?: PaymentWithDevices, user?: User }) {
|
||
const [verifying, setVerifying] = useState(false)
|
||
|
||
const devices = payment?.devices;
|
||
if (devices?.length === 0) {
|
||
return null;
|
||
}
|
||
const baseAmount = billFormula?.baseAmount ?? 100;
|
||
const discountPercentage = billFormula?.discountPercentage ?? 75;
|
||
// 100+(n−1)×75
|
||
const total = baseAmount + (devices?.length ?? 1 - 1) * discountPercentage;
|
||
const walletBalance = user?.walletBalance ?? 0;
|
||
const isWalletPayVisible = walletBalance > total;
|
||
|
||
|
||
return (
|
||
<div className="w-full">
|
||
<div className="p-2 flex flex-col gap-2">
|
||
<h3 className="title-bg my-1 p-2 font-semibold text-lg">
|
||
{!payment?.paid ? "Devices to pay" : "Devices Paid"}
|
||
</h3>
|
||
<div className="flex flex-col gap-2">
|
||
{devices?.map((device) => (
|
||
<div
|
||
key={device.id}
|
||
className="bg-muted border rounded p-2 flex gap-2 items-center"
|
||
>
|
||
<div className="flex flex-col">
|
||
<div className="text-sm font-medium">{device.name}</div>
|
||
<div className="text-xs text-muted-foreground">
|
||
{device.mac}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
<div className="m-2 flex items-end justify-end p-2 text-sm text-foreground border rounded">
|
||
<Table>
|
||
<TableCaption>
|
||
<div className="max-w-sm mx-auto">
|
||
<p>Please send the following amount to the payment address</p>
|
||
<AccountInfomation
|
||
accName="Baraveli Dev"
|
||
accountNo="90101400028321000"
|
||
/>
|
||
{payment?.paid ? (
|
||
<Button size={"lg"} variant={"secondary"} disabled className="dark:text-green-200 text-green-900 bg-green-500/20 uppercase font-semibold">Payment Verified</Button>
|
||
) : (
|
||
<div className="flex flex-col gap-2">
|
||
{isWalletPayVisible && (
|
||
<Button
|
||
disabled={verifying}
|
||
onClick={async () => {
|
||
setVerifying(true);
|
||
await verifyPayment({
|
||
userId: user?.id ?? "",
|
||
paymentId: payment?.id,
|
||
benefName: user?.name ?? "",
|
||
accountNo: user?.accNo ?? "",
|
||
absAmount: String(total),
|
||
time: formatDate(new Date(payment?.createdAt || "")),
|
||
type: "WALLET",
|
||
});
|
||
setVerifying(false);
|
||
}}
|
||
variant={"secondary"} size={"lg"}>
|
||
{verifying ? "Paying..." : "Pay with wallet"}
|
||
<Wallet />
|
||
</Button>
|
||
)}
|
||
<Button
|
||
disabled={verifying}
|
||
onClick={async () => {
|
||
setVerifying(true);
|
||
const res = await verifyPayment({
|
||
userId: user?.id ?? "",
|
||
paymentId: payment?.id,
|
||
benefName: user?.name ?? "",
|
||
accountNo: user?.accNo ?? "",
|
||
absAmount: String(total),
|
||
type: "TRANSFER",
|
||
time: formatDate(new Date(payment?.createdAt || "")),
|
||
});
|
||
setVerifying(false);
|
||
switch (true) {
|
||
case res?.success === true:
|
||
toast.success(res.message);
|
||
break;
|
||
case res.success === false:
|
||
toast.error(res.message);
|
||
break;
|
||
default:
|
||
toast.error("Unexpected error occurred.");
|
||
}
|
||
}}
|
||
size={"lg"} className="mb-4">
|
||
{verifying ? "Verifying..." : "Verify Payment"}
|
||
{verifying ? <Loader2 className="animate-spin" /> : <BadgeDollarSign />}
|
||
</Button>
|
||
</div>
|
||
|
||
)}
|
||
|
||
</div>
|
||
</TableCaption>
|
||
<TableBody className="">
|
||
<TableRow>
|
||
<TableCell>Total Devices</TableCell>
|
||
<TableCell className="text-right text-xl">{devices?.length}</TableCell>
|
||
</TableRow>
|
||
</TableBody>
|
||
<TableFooter>
|
||
<TableRow className="">
|
||
<TableCell colSpan={1}>Total Due</TableCell>
|
||
<TableCell className="text-right text-3xl font-bold">{total.toFixed(2)}</TableCell>
|
||
</TableRow>
|
||
</TableFooter>
|
||
</Table>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
function AccountInfomation({
|
||
accountNo,
|
||
accName,
|
||
}: {
|
||
accountNo: string;
|
||
accName: string;
|
||
}) {
|
||
const [accNo, setAccNo] = useState(false)
|
||
return (
|
||
<div className="justify-start items-start border my-4 flex flex-col gap-2 p-2 rounded-md">
|
||
<h6 className="title-bg uppercase p-2 border rounded w-full font-semibold">
|
||
Account Information
|
||
</h6>
|
||
<div className="border justify-start flex flex-col items-start bg-white/10 w-full p-2 rounded">
|
||
<div className="text-sm font-semibold">Account Name</div>
|
||
<span>{accName}</span>
|
||
</div>
|
||
<div className="border flex justify-between items-start gap-2 bg-white/10 w-full p-2 rounded">
|
||
<div className="flex flex-col items-start justify-start">
|
||
<p className="text-sm font-semibold">Account No</p>
|
||
<span>{accountNo}</span>
|
||
</div>
|
||
<Button
|
||
onClick={() => {
|
||
setTimeout(() => {
|
||
setAccNo(true)
|
||
navigator.clipboard.writeText(accountNo)
|
||
}, 2000)
|
||
toast.success("Account number copied!")
|
||
setAccNo((prev) => !prev)
|
||
}}
|
||
variant={"link"}>
|
||
{accNo ? <Clipboard /> : <ClipboardCheck color="green" />}
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|