mirror of
https://github.com/i701/sarlink-portal.git
synced 2025-02-22 17:02:01 +00:00
- Added a new `bun.lockb` file for dependency management. - Updated `next.config.ts` to set output to "standalone" for better deployment options. - Removed `package-lock.json` to streamline package management. - Modified `package.json` to update dependencies, including `@prisma/client` and `sonner`, and adjusted build scripts for improved functionality. - Enhanced Tailwind CSS configuration to include new animations and color schemes. - Refactored various dashboard components to improve UI consistency, including adding a new `My Wallet` page and updating existing pages to use a unified styling approach. - Introduced a new `BlockDeviceDialog` component for managing device blocking with user-defined reasons. - Improved logging and error handling in payment verification and device management functions. These changes enhance the overall functionality, maintainability, and user experience of the application.
193 lines
6.1 KiB
TypeScript
193 lines
6.1 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,
|
|
};
|
|
|
|
if (devices.length === 0) return null
|
|
return (
|
|
<>
|
|
<Drawer open={isOpen} onOpenChange={setIsOpen}>
|
|
<DrawerTrigger asChild>
|
|
<Button size={"lg"} className="bg-sarLinkOrange absolute bottom-10 w-fit z-20 left-1/2 transform -translate-x-1/2" onClick={() => setIsOpen(!isOpen)} variant="outline">
|
|
<MonitorSmartphone />
|
|
Pay {devices.length > 0 && `(${devices.length})`} Device
|
|
</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([]);
|
|
setIsOpen(!isOpen);
|
|
}}
|
|
variant="outline"
|
|
>
|
|
Clear Selection
|
|
</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>
|
|
);
|
|
}
|