mirror of
https://github.com/i701/sarlink-portal.git
synced 2025-02-22 18:22: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.
189 lines
5.8 KiB
TypeScript
189 lines
5.8 KiB
TypeScript
"use client";
|
|
|
|
import { createPayment } from "@/actions/payment";
|
|
import { Button } from "@/components/ui/button";
|
|
import {
|
|
Drawer,
|
|
DrawerClose,
|
|
DrawerContent,
|
|
DrawerDescription,
|
|
DrawerFooter,
|
|
DrawerHeader,
|
|
DrawerTitle,
|
|
DrawerTrigger,
|
|
} from "@/components/ui/drawer";
|
|
import {
|
|
cartDrawerOpenAtom,
|
|
deviceCartAtom,
|
|
numberOfMonths,
|
|
} from "@/lib/atoms";
|
|
import { authClient } from "@/lib/auth-client";
|
|
import type { PaymentType } from "@/lib/types";
|
|
import type { BillFormula, Device } from "@prisma/client";
|
|
import { useAtom, useAtomValue, useSetAtom } from "jotai";
|
|
import {
|
|
CircleDollarSign,
|
|
Loader2,
|
|
MonitorSmartphone,
|
|
Trash2,
|
|
} from "lucide-react";
|
|
import { usePathname, useRouter } from "next/navigation";
|
|
import { useEffect, useState } from "react";
|
|
import { toast } from "sonner";
|
|
import NumberInput from "./number-input";
|
|
|
|
|
|
|
|
export function DeviceCartDrawer({
|
|
billFormula,
|
|
}: {
|
|
billFormula: BillFormula | null;
|
|
}) {
|
|
const baseAmount = billFormula?.baseAmount || 100;
|
|
const discountPercentage = billFormula?.discountPercentage || 75;
|
|
const session = authClient.useSession();
|
|
const pathname = usePathname();
|
|
const devices = useAtomValue(deviceCartAtom);
|
|
const setDeviceCart = useSetAtom(deviceCartAtom);
|
|
const [months, setMonths] = useAtom(numberOfMonths);
|
|
const [isOpen, setIsOpen] = useAtom(cartDrawerOpenAtom);
|
|
const [message, setMessage] = useState("");
|
|
const [disabled, setDisabled] = useState(false);
|
|
const [total, setTotal] = useState(0);
|
|
const router = useRouter();
|
|
useEffect(() => {
|
|
if (months === 7) {
|
|
setMessage("You will get 1 month free.");
|
|
} else if (months === 12) {
|
|
setMessage("You will get 2 months free.");
|
|
} else {
|
|
setMessage("");
|
|
}
|
|
setTotal(baseAmount + ((devices.length + 1) - 1) * discountPercentage);
|
|
}, [months, devices.length, baseAmount, discountPercentage]);
|
|
|
|
if (pathname === "/payment") {
|
|
return null;
|
|
}
|
|
|
|
const data: PaymentType = {
|
|
numberOfMonths: months,
|
|
userId: session?.data?.user.id ?? "",
|
|
deviceIds: devices.map((device) => device.id),
|
|
amount: Number.parseFloat(total.toFixed(2)),
|
|
paid: false,
|
|
};
|
|
|
|
return (
|
|
<Drawer open={isOpen} onOpenChange={setIsOpen}>
|
|
<DrawerTrigger asChild>
|
|
<Button onClick={() => setIsOpen(!isOpen)} variant="outline">
|
|
<MonitorSmartphone />
|
|
{devices.length > 0 && `(${devices.length})`}
|
|
</Button>
|
|
</DrawerTrigger>
|
|
<DrawerContent>
|
|
<div className="mx-auto w-full max-w-sm">
|
|
<DrawerHeader>
|
|
<DrawerTitle>Selected Devices</DrawerTitle>
|
|
<DrawerDescription>Selected devices pay.</DrawerDescription>
|
|
</DrawerHeader>
|
|
<div className="flex max-h-[calc(100svh-400px)] flex-col overflow-auto px-4 pb-4 gap-4">
|
|
{devices.map((device) => (
|
|
<DeviceCard key={device.id} device={device} />
|
|
))}
|
|
</div>
|
|
<div className="px-4 flex flex-col gap-4">
|
|
<NumberInput
|
|
label="Set No of Months"
|
|
value={months}
|
|
onChange={(value) => setMonths(value)}
|
|
maxAllowed={12}
|
|
isDisabled={devices.length === 0}
|
|
/>
|
|
{message && (
|
|
<span className="title-bg text-lime-800 bg-lime-100/50 dark:text-lime-100 rounded text-center p-2 w-full">
|
|
{message}
|
|
</span>
|
|
)}
|
|
</div>
|
|
<DrawerFooter>
|
|
<Button
|
|
onClick={async () => {
|
|
setDisabled(true)
|
|
const payment = await createPayment(data)
|
|
setDisabled(false)
|
|
setDeviceCart([])
|
|
setMonths(1)
|
|
if (payment) {
|
|
router.push(`/payments/${payment.id}`);
|
|
setTimeout(() => setIsOpen(!isOpen), 2000);
|
|
} else {
|
|
toast.error("Something went wrong.")
|
|
}
|
|
}}
|
|
className="w-full"
|
|
disabled={devices.length === 0 || disabled}
|
|
>
|
|
{disabled ? (
|
|
<>
|
|
<Loader2 className="ml-2 animate-spin" />
|
|
</>
|
|
) : (
|
|
<>
|
|
Go to payment
|
|
<CircleDollarSign />
|
|
</>
|
|
)}
|
|
</Button>
|
|
<DrawerClose asChild>
|
|
<Button variant="outline">Cancel</Button>
|
|
</DrawerClose>
|
|
<Button
|
|
onClick={() => {
|
|
setDeviceCart([]);
|
|
}}
|
|
variant="outline"
|
|
>
|
|
Reset
|
|
</Button>
|
|
</DrawerFooter>
|
|
</div>
|
|
</DrawerContent>
|
|
</Drawer>
|
|
);
|
|
}
|
|
|
|
function DeviceCard({ device }: { device: Device }) {
|
|
const setDeviceCart = useSetAtom(deviceCartAtom);
|
|
return (
|
|
<div className="relative flex h-full w-full items-center pr-4 justify-between rounded-lg border border-input bg-background shadow-sm shadow-black/5 transition-shadow focus-within:border-ring focus-within:outline-none focus-within:ring-[3px] focus-within:ring-ring/20">
|
|
<div>
|
|
<label
|
|
htmlFor="input-33"
|
|
className="block px-3 pt-2 text-xs font-medium text-foreground"
|
|
>
|
|
{device.name}
|
|
</label>
|
|
<input
|
|
className="flex h-10 opacity-50 w-full bg-transparent px-3 pb-2 text-sm text-foreground placeholder:text-muted-foreground/70 focus-visible:outline-none"
|
|
value={device.mac}
|
|
readOnly
|
|
disabled
|
|
placeholder={"MAC Address"}
|
|
/>
|
|
</div>
|
|
|
|
<Button
|
|
onClick={() => {
|
|
setDeviceCart((prev) => prev.filter((d) => d.id !== device.id));
|
|
}}
|
|
variant={"destructive"}
|
|
>
|
|
Remove
|
|
<Trash2 />
|
|
</Button>
|
|
</div>
|
|
);
|
|
}
|