sarlink-portal/components/device-cart.tsx
i701 0a63e4337e Enhance user management and payment processing features
- Updated `package.json` to include a new script for launching Prisma Studio.
- Modified `signup` function in `auth-actions.ts` to include account number in user data.
- Refactored `createPayment` function in `payment.ts` to improve error handling and return structured responses.
- Updated UI components in the dashboard to improve layout and responsiveness, including changes to `UserDevices` and `UserPayments` pages.
- Introduced new `AdminDevicesTable` and `UsersPaymentsTable` components for better admin functionalities.
- Enhanced `DeviceCartDrawer` to provide user feedback during payment processing.
- Added account number input to the signup form and updated validation schema accordingly.
- Updated Prisma schema to include a new `ninja_user_id` field for user management.

These changes improve the overall functionality, maintainability, and user experience of the application, particularly in user management and payment processing.
2025-01-06 12:49:13 +05:00

201 lines
6.5 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">
<pre>{JSON.stringify(isOpen, null, 2)}</pre>
{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);
toast.promise(
createPayment(data).then((result) => {
if (result.success) {
setDeviceCart([]);
setMonths(1);
setDisabled(false);
if (isOpen) router.push(`/payments/${result.paymentId}`);
setIsOpen(!isOpen);
return "Payment created!";
}
}),
{
loading: "Processing payment...",
success: "Payment created!",
error: (err) => err.message || "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>
);
}