From aff9d26e0e379804a2cfcdf649505f9f34ed52d3 Mon Sep 17 00:00:00 2001 From: i701 Date: Sat, 12 Apr 2025 14:35:23 +0500 Subject: [PATCH] refactor: enhance error handling and add pagination to device queries --- actions/payment.ts | 74 ++++++++++++------- app/(dashboard)/payments/[paymentId]/page.tsx | 5 +- components/clickable-row.tsx | 4 +- components/client-error-message.tsx | 22 ++++++ components/device-card.tsx | 11 +-- components/devices-table.tsx | 32 +++++--- lib/backend-types.ts | 7 +- queries/devices.ts | 42 ++++++++--- 8 files changed, 134 insertions(+), 63 deletions(-) create mode 100644 components/client-error-message.tsx diff --git a/actions/payment.ts b/actions/payment.ts index c1af340..030613f 100644 --- a/actions/payment.ts +++ b/actions/payment.ts @@ -1,7 +1,12 @@ "use server"; import { authOptions } from "@/app/auth"; -import type { ApiResponse, NewPayment, Payment } from "@/lib/backend-types"; +import type { + ApiError, + ApiResponse, + NewPayment, + Payment, +} from "@/lib/backend-types"; import type { User } from "@/lib/types/user"; import { tryCatch } from "@/utils/tryCatch"; import { getServerSession } from "next-auth"; @@ -25,9 +30,12 @@ export async function createPayment(data: NewPayment) { }, ); if (!response.ok) { - const errorData = await response.json(); - // Throw an error with the message from the API - throw new Error(errorData.message || "Something went wrong."); + const errorData = (await response.json()) as ApiError; + const errorMessage = + errorData.message || errorData.detail || "An error occurred."; + const error = new Error(errorMessage); + (error as ApiError & { details?: ApiError }).details = errorData; // Attach the errorData to the error object + throw error; } const payment = (await response.json()) as Payment; revalidatePath("/devices"); @@ -47,18 +55,16 @@ export async function getPayment({ id }: { id: string }) { }, ); - if (response.status === 404) { - throw new Error("Payment not found"); - } - if (!response.ok) { - const errorData = await response.json(); - console.log(errorData); - // Throw an error with the message from the API - throw new Error(errorData.message || "Something went wrong."); + const errorData = (await response.json()) as ApiError; + const errorMessage = + errorData.message || errorData.detail || "An error occurred."; + const error = new Error(errorMessage); + (error as ApiError & { details?: ApiError }).details = errorData; // Attach the errorData to the error object + throw error; } - const payment = (await response.json()) as Payment; - return payment; + const data = (await response.json()) as Payment; + return data; } export async function getPayments() { @@ -73,10 +79,13 @@ export async function getPayments() { }, }, ); - console.log("response statys", response.status); - if (response.status === 401) { - // Redirect to the signin page if the user is unauthorized - throw new Error("Unauthorized; redirect to /auth/signin"); + if (!response.ok) { + const errorData = (await response.json()) as ApiError; + const errorMessage = + errorData.message || errorData.detail || "An error occurred."; + const error = new Error(errorMessage); + (error as ApiError & { details?: ApiError }).details = errorData; // Attach the errorData to the error object + throw error; } const data = (await response.json()) as ApiResponse; return data; @@ -94,11 +103,14 @@ export async function cancelPayment({ id }: { id: string }) { }, }, ); - if (response.status === 401) { - // Redirect to the signin page if the user is unauthorized - throw new Error("Unauthorized; redirect to /auth/signin"); + if (!response.ok) { + const errorData = (await response.json()) as ApiError; + const errorMessage = + errorData.message || errorData.detail || "An error occurred."; + const error = new Error(errorMessage); + (error as ApiError & { details?: ApiError }).details = errorData; // Attach the errorData to the error object + throw error; } - // Since the response is 204 No Content, there's no JSON to parse return { message: "Payment successfully canceled." }; } @@ -132,9 +144,12 @@ export async function updatePayment({ ); if (!response.ok) { - const errorData = await response.json(); - // Throw an error with the message from the API - throw new Error(errorData.message || "Something went wrong."); + const errorData = (await response.json()) as ApiError; + const errorMessage = + errorData.message || errorData.detail || "An error occurred."; + const error = new Error(errorMessage); + (error as ApiError & { details?: ApiError }).details = errorData; // Attach the errorData to the error object + throw error; } const payment = (await response.json()) as Payment; return payment; @@ -162,9 +177,12 @@ export async function updateWalletBalance({ ); if (!response.ok) { - const errorData = await response.json(); - // Throw an error with the message from the API - throw new Error(errorData.message || "Something went wrong."); + const errorData = (await response.json()) as ApiError; + const errorMessage = + errorData.message || errorData.detail || "An error occurred."; + const error = new Error(errorMessage); + (error as ApiError & { details?: ApiError }).details = errorData; // Attach the errorData to the error object + throw error; } const message = (await response.json()) as { message: "Wallet balance updated successfully."; diff --git a/app/(dashboard)/payments/[paymentId]/page.tsx b/app/(dashboard)/payments/[paymentId]/page.tsx index 78c49d8..737d8c3 100644 --- a/app/(dashboard)/payments/[paymentId]/page.tsx +++ b/app/(dashboard)/payments/[paymentId]/page.tsx @@ -1,10 +1,12 @@ import { getPayment } from "@/actions/payment"; import { authOptions } from "@/app/auth"; import CancelPaymentButton from "@/components/billing/cancel-payment-button"; +import ClientErrorMessage from "@/components/client-error-message"; import DevicesToPay from "@/components/devices-to-pay"; import { cn } from "@/lib/utils"; import { tryCatch } from "@/utils/tryCatch"; import { getServerSession } from "next-auth"; +import { redirect } from "next/navigation"; export default async function PaymentPage({ params, }: { @@ -15,7 +17,8 @@ export default async function PaymentPage({ const paymentId = (await params).paymentId; const [error, payment] = await tryCatch(getPayment({ id: paymentId })); if (error) { - return Error getting payment: {error.message}; + if (error.message === "Invalid token.") redirect("/auth/signin"); + return ; } return (
diff --git a/components/clickable-row.tsx b/components/clickable-row.tsx index cfb736f..7a32f3e 100644 --- a/components/clickable-row.tsx +++ b/components/clickable-row.tsx @@ -21,7 +21,7 @@ export default function ClickableRow({ className={cn( (parentalControl === false && device.blocked) || device.is_active ? "cursor-not-allowed bg-accent-foreground/10 hover:bg-accent-foreground/10" - : "cursor-pointer hover:bg-muted", + : "cursor-pointer hover:bg-muted-foreground/10", )} onClick={() => { if (device.blocked) return; @@ -66,7 +66,7 @@ export default function ClickableRow({ )} {device.has_a_pending_payment && ( - + Payment Pending diff --git a/components/client-error-message.tsx b/components/client-error-message.tsx new file mode 100644 index 0000000..6f56bac --- /dev/null +++ b/components/client-error-message.tsx @@ -0,0 +1,22 @@ +import { Phone, TriangleAlert } from "lucide-react"; +import Link from "next/link"; +import { Button } from "./ui/button"; + +export default function ClientErrorMessage({ message }: { message: string }) { + return ( +
+
+ +
{message}
+ + Please contact the administrator to give you permissions. + + + + +
+
+ ); +} diff --git a/components/device-card.tsx b/components/device-card.tsx index f28b0d7..7a4380e 100644 --- a/components/device-card.tsx +++ b/components/device-card.tsx @@ -3,7 +3,7 @@ import { deviceCartAtom } from "@/lib/atoms"; import type { Device } from "@/lib/backend-types"; import { cn } from "@/lib/utils"; import { useAtom } from "jotai"; -import { Hourglass } from "lucide-react"; +import { HandCoins, Hourglass } from "lucide-react"; import Link from "next/link"; import AddDevicesToCartButton from "./add-devices-to-cart-button"; import BlockDeviceDialog from "./block-device-dialog"; @@ -39,7 +39,7 @@ export default function DeviceCard({ isChecked ? "bg-accent" : "bg-", device.is_active ? "cursor-not-allowed bg-accent-foreground/10 hover:bg-accent-foreground/10" - : "cursor-pointer hover:bg-muted", + : "cursor-pointer hover:bg-muted-foreground/10", )} >
@@ -50,7 +50,7 @@ export default function DeviceCard({ > {device.name} - + {device.mac}
@@ -74,8 +74,9 @@ export default function DeviceCard({ )} {device.has_a_pending_payment && ( - - Payment Pending + + Payment Pending{" "} + )} diff --git a/components/devices-table.tsx b/components/devices-table.tsx index 6f63198..e2e89b9 100644 --- a/components/devices-table.tsx +++ b/components/devices-table.tsx @@ -12,7 +12,9 @@ import { import { getDevices } from "@/queries/devices"; import { tryCatch } from "@/utils/tryCatch"; import { getServerSession } from "next-auth"; +import { redirect } from "next/navigation"; import ClickableRow from "./clickable-row"; +import ClientErrorMessage from "./client-error-message"; import DeviceCard from "./device-card"; import Pagination from "./pagination"; @@ -30,15 +32,22 @@ export async function DevicesTable({ const session = await getServerSession(authOptions); const isAdmin = session?.user?.is_superuser; const query = (await searchParams)?.query || ""; + const page = (await searchParams)?.page || 1; - const [error, devices] = await tryCatch(getDevices({ query: query })); + const limit = 10; // Items per page + const offset = (page - 1) * limit; // Calculate offset based on page + + const [error, devices] = await tryCatch( + getDevices({ query: query, limit: limit, offset: offset }), + ); if (error) { - return
{JSON.stringify(error, null, 2)}
; + if (error.message === "Invalid token.") redirect("/auth/signin"); + return ; } - const { meta, data } = devices; + const { meta, data, links } = devices; return (
- {data.length === 0 ? ( + {data?.length === 0 ? (

No devices yet.

@@ -55,7 +64,7 @@ export async function DevicesTable({ - {data.map((device) => ( + {data?.map((device) => ( - {query.length > 0 && ( + {query?.length > 0 && (

- Showing {meta.total} locations for "{query} + Showing {meta?.total} locations for "{query} "

)}
- {meta.total} devices + {meta?.total} devices
-
{JSON.stringify(meta, null, 2)}
- {data.map((device) => ( + {data?.map((device) => ( ; return data; } export async function getDevice({ deviceId }: { deviceId: string }) { const session = await checkSession(); - const respose = await fetch( + const response = await fetch( `${process.env.SARLINK_API_BASE_URL}/api/devices/${deviceId}/`, { method: "GET", @@ -44,7 +52,15 @@ export async function getDevice({ deviceId }: { deviceId: string }) { }, }, ); - const device = (await respose.json()) as Device; + if (!response.ok) { + const errorData = (await response.json()) as ApiError; + const errorMessage = + errorData.message || errorData.detail || "An error occurred."; + const error = new Error(errorMessage); + (error as ApiError & { details?: ApiError }).details = errorData; // Attach the errorData to the error object + throw error; + } + const device = (await response.json()) as Device; return device; } @@ -68,13 +84,17 @@ export async function addDevice({ body: JSON.stringify({ name: name, mac: mac, + registered: true, }), }, ); if (!response.ok) { - const errorData = await response.json(); - // Throw an error with the message from the API - throw new Error(errorData.message || "Something went wrong."); + const errorData = (await response.json()) as ApiError; + const errorMessage = + errorData.message || errorData.detail || "An error occurred."; + const error = new Error(errorMessage); + (error as ApiError & { details?: ApiError }).details = errorData; // Attach the errorData to the error object + throw error; } const data = (await response.json()) as SingleDevice; revalidatePath("/devices");