feat: add loading skeleton for devices table and improve payment processing logic
All checks were successful
Build and Push Docker Images / Build and Push Docker Images (push) Successful in 4m30s

This commit is contained in:
i701 2025-04-12 17:01:37 +05:00
parent 067acef49c
commit 0d578c9add
5 changed files with 95 additions and 27 deletions

View File

@ -8,6 +8,7 @@ import type {
Payment, Payment,
} from "@/lib/backend-types"; } from "@/lib/backend-types";
import type { User } from "@/lib/types/user"; import type { User } from "@/lib/types/user";
import { checkSession } from "@/utils/session";
import { tryCatch } from "@/utils/tryCatch"; import { tryCatch } from "@/utils/tryCatch";
import { getServerSession } from "next-auth"; import { getServerSession } from "next-auth";
import { revalidatePath } from "next/cache"; import { revalidatePath } from "next/cache";
@ -200,27 +201,15 @@ type VerifyPaymentType = {
type?: "TRANSFER" | "WALLET"; type?: "TRANSFER" | "WALLET";
}; };
class InsufficientFundsError extends Error {
constructor() {
super("Insufficient funds in wallet");
this.name = "InsufficientFundsError";
}
}
export async function processWalletPayment({ export async function processWalletPayment({
payment, payment,
amount, amount,
}: { payment: Payment | undefined; amount: number }) { }: { payment: Payment | undefined; amount: number }) {
await checkSession();
const session = await getServerSession(authOptions); const session = await getServerSession(authOptions);
if (!session?.user || !payment) { const walletBalance = session?.user?.wallet_balance ?? 0;
throw new Error("User or payment not found"); console.log("processing wallet payment >>>", walletBalance);
} if (!payment) return;
const walletBalance = session.user.wallet_balance ?? 0;
if (walletBalance < amount) {
throw new InsufficientFundsError();
}
const [updatePaymentError, _] = await tryCatch( const [updatePaymentError, _] = await tryCatch(
updatePayment({ updatePayment({
id: payment.id, id: payment.id,
@ -235,9 +224,10 @@ export async function processWalletPayment({
} }
console.log("Wallet balance before update:", walletBalance); console.log("Wallet balance before update:", walletBalance);
const updated_balance = walletBalance - amount; const updated_balance = walletBalance - amount;
if (!session?.user?.id) return;
const [walletUpdateError, response] = await tryCatch( const [walletUpdateError, response] = await tryCatch(
updateWalletBalance({ updateWalletBalance({
id: session.user.id, id: session?.user?.id,
wallet_balance: Number.parseFloat(updated_balance?.toFixed(2) ?? "0"), wallet_balance: Number.parseFloat(updated_balance?.toFixed(2) ?? "0"),
}), }),
); );

View File

@ -0,0 +1,78 @@
import { Skeleton } from "@/components/ui/skeleton";
import {
Table,
TableBody,
TableCaption,
TableCell,
TableFooter,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import { cn } from "@/lib/utils";
export default function DevicesTableSkeleton() {
return (
<>
<div className="hidden sm:block">
<Table className="overflow-scroll">
<TableCaption>Table of all devices.</TableCaption>
<TableHeader>
<TableRow>
<TableHead>Device Name</TableHead>
<TableHead>MAC Address</TableHead>
<TableHead>#</TableHead>
</TableRow>
</TableHeader>
<TableBody className="overflow-scroll">
{Array.from({ length: 10 }).map((_, i) => (
<TableRow key={`${i + 1}`}>
<TableCell>
<Skeleton className="w-full h-10 rounded" />
</TableCell>
<TableCell>
<Skeleton className="w-full h-10 rounded" />
</TableCell>
<TableCell>
<Skeleton className="w-full h-10 rounded" />
</TableCell>
</TableRow>
))}
</TableBody>
<TableFooter>
<TableRow>
<TableCell colSpan={2}>
<Skeleton className="w-full h-4 rounded" />
</TableCell>
<TableCell className="text-muted-foreground">
<Skeleton className="w-20 h-4 rounded" />
</TableCell>
</TableRow>
</TableFooter>
</Table>
</div>
<div className="sm:hidden my-4">
{Array.from({ length: 10 }).map((_, i) => (
<DeviceCardSkeleton key={`${i + 1}`} />
))}
</div>
</>
);
}
function DeviceCardSkeleton() {
return (
<div
className={cn(
"flex text-sm justify-between items-center my-2 p-4 border rounded-md bg-gray-100",
)}
>
<div className="font-semibold flex w-full flex-col items-start gap-2 mb-2 relative">
<Skeleton className="w-32 h-6" />
<Skeleton className="w-36 h-6" />
<Skeleton className="w-32 h-4" />
<Skeleton className="w-40 h-8" />
</div>
</div>
);
}

View File

@ -4,6 +4,7 @@ import Search from "@/components/search";
import AddDeviceDialogForm from "@/components/user/add-device-dialog"; import AddDeviceDialogForm from "@/components/user/add-device-dialog";
import { getServerSession } from "next-auth"; import { getServerSession } from "next-auth";
import React, { Suspense } from "react"; import React, { Suspense } from "react";
import DevicesTableSkeleton from "./device-table-skeleton";
export default async function Devices({ export default async function Devices({
searchParams, searchParams,
@ -11,11 +12,10 @@ export default async function Devices({
searchParams: Promise<{ searchParams: Promise<{
query: string; query: string;
page: number; page: number;
sortBy: string;
status: string;
}>; }>;
}) { }) {
const query = (await searchParams)?.query || ""; const query = (await searchParams)?.query || "";
const page = (await searchParams)?.page || 1;
const session = await getServerSession(authOptions); const session = await getServerSession(authOptions);
return ( return (
<div> <div>
@ -29,7 +29,7 @@ export default async function Devices({
> >
<Search /> <Search />
</div> </div>
<Suspense key={query} fallback={"loading...."}> <Suspense key={query || page} fallback={<DevicesTableSkeleton />}>
<DevicesTable parentalControl={false} searchParams={searchParams} /> <DevicesTable parentalControl={false} searchParams={searchParams} />
</Suspense> </Suspense>
</div> </div>

View File

@ -4,7 +4,7 @@ import { deviceCartAtom } from "@/lib/atoms";
import type { Device } from "@/lib/backend-types"; import type { Device } from "@/lib/backend-types";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { useAtom } from "jotai"; import { useAtom } from "jotai";
import { Hourglass } from "lucide-react"; import { HandCoins } from "lucide-react";
import Link from "next/link"; import Link from "next/link";
import AddDevicesToCartButton from "./add-devices-to-cart-button"; import AddDevicesToCartButton from "./add-devices-to-cart-button";
import BlockDeviceDialog from "./block-device-dialog"; import BlockDeviceDialog from "./block-device-dialog";
@ -67,7 +67,8 @@ export default function ClickableRow({
{device.has_a_pending_payment && ( {device.has_a_pending_payment && (
<Link href={`/payments/${device.pending_payment_id}`}> <Link href={`/payments/${device.pending_payment_id}`}>
<span className="bg-muted rounded px-2 p-1 mt-2 flex hover:underline items-center justify-center gap-2 text-muted-foreground text-yellow-600"> <span className="bg-muted rounded px-2 p-1 mt-2 flex hover:underline items-center justify-center gap-2 text-muted-foreground text-yellow-600">
Payment Pending <Hourglass size={14} /> Payment Pending{" "}
<HandCoins className="animate-pulse" size={14} />
</span> </span>
</Link> </Link>
)} )}

View File

@ -25,7 +25,6 @@ export async function DevicesTable({
searchParams: Promise<{ searchParams: Promise<{
query: string; query: string;
page: number; page: number;
sortBy: string;
}>; }>;
parentalControl?: boolean; parentalControl?: boolean;
}) { }) {
@ -89,10 +88,6 @@ export async function DevicesTable({
</TableRow> </TableRow>
</TableFooter> </TableFooter>
</Table> </Table>
<Pagination
totalPages={meta?.last_page}
currentPage={meta?.current_page}
/>
</div> </div>
<div className="sm:hidden my-4"> <div className="sm:hidden my-4">
{data?.map((device) => ( {data?.map((device) => (
@ -103,6 +98,10 @@ export async function DevicesTable({
/> />
))} ))}
</div> </div>
<Pagination
totalPages={meta?.last_page}
currentPage={meta?.current_page}
/>
</> </>
)} )}
</div> </div>