mirror of
https://github.com/i701/sarlink-portal.git
synced 2025-02-22 18:22:00 +00:00
- 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.
201 lines
6.5 KiB
TypeScript
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>
|
|
);
|
|
}
|