mirror of
				https://github.com/i701/sarlink-portal.git
				synced 2025-11-04 00:16:59 +00:00 
			
		
		
		
	refactor: update axios client import, enhance device and payment handling, and add cancel payment button component
	
		
			
	
		
	
	
		
	
		
			Some checks failed
		
		
	
	
		
			
				
	
				Build and Push Docker Images / Build and Push Docker Images (push) Failing after 6m28s
				
			
		
		
	
	
				
					
				
			
		
			Some checks failed
		
		
	
	Build and Push Docker Images / Build and Push Docker Images (push) Failing after 6m28s
				
			This commit is contained in:
		@@ -24,7 +24,6 @@ export async function createPayment(data: NewPayment) {
 | 
			
		||||
			body: JSON.stringify(data),
 | 
			
		||||
		},
 | 
			
		||||
	);
 | 
			
		||||
 | 
			
		||||
	if (!response.ok) {
 | 
			
		||||
		const errorData = await response.json();
 | 
			
		||||
		// Throw an error with the message from the API
 | 
			
		||||
@@ -32,7 +31,7 @@ export async function createPayment(data: NewPayment) {
 | 
			
		||||
	}
 | 
			
		||||
	const payment = (await response.json()) as Payment;
 | 
			
		||||
	revalidatePath("/devices");
 | 
			
		||||
	redirect(`/payments/${payment.id}`);
 | 
			
		||||
	return payment;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function getPayment({ id }: { id: string }) {
 | 
			
		||||
@@ -48,8 +47,13 @@ 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.");
 | 
			
		||||
	}
 | 
			
		||||
@@ -59,7 +63,7 @@ export async function getPayment({ id }: { id: string }) {
 | 
			
		||||
 | 
			
		||||
export async function getPayments() {
 | 
			
		||||
	const session = await getServerSession(authOptions);
 | 
			
		||||
	const respose = await fetch(
 | 
			
		||||
	const response = await fetch(
 | 
			
		||||
		`${process.env.SARLINK_API_BASE_URL}/api/billing/payment/`,
 | 
			
		||||
		{
 | 
			
		||||
			method: "GET",
 | 
			
		||||
@@ -69,10 +73,35 @@ export async function getPayments() {
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	);
 | 
			
		||||
	const data = (await respose.json()) as ApiResponse<Payment>;
 | 
			
		||||
	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");
 | 
			
		||||
	}
 | 
			
		||||
	const data = (await response.json()) as ApiResponse<Payment>;
 | 
			
		||||
	return data;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function cancelPayment({ id }: { id: string }) {
 | 
			
		||||
	const session = await getServerSession(authOptions);
 | 
			
		||||
	const response = await fetch(
 | 
			
		||||
		`${process.env.SARLINK_API_BASE_URL}/api/billing/payment/${id}/delete/`,
 | 
			
		||||
		{
 | 
			
		||||
			method: "DELETE",
 | 
			
		||||
			headers: {
 | 
			
		||||
				"Content-Type": "application/json",
 | 
			
		||||
				Authorization: `Token ${session?.apiToken}`,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	);
 | 
			
		||||
	if (response.status === 401) {
 | 
			
		||||
		// Redirect to the signin page if the user is unauthorized
 | 
			
		||||
		throw new Error("Unauthorized; redirect to /auth/signin");
 | 
			
		||||
	}
 | 
			
		||||
	// Since the response is 204 No Content, there's no JSON to parse
 | 
			
		||||
	return { message: "Payment successfully canceled." };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type UpdatePayment = Pick<
 | 
			
		||||
	Payment,
 | 
			
		||||
	"id" | "paid" | "paid_at" | "method" | "number_of_months"
 | 
			
		||||
 
 | 
			
		||||
@@ -1,11 +1,12 @@
 | 
			
		||||
import { getPayment } from "@/actions/payment";
 | 
			
		||||
import { authOptions } from "@/app/auth";
 | 
			
		||||
import CancelPaymentButton from "@/components/billing/cancel-payment-button";
 | 
			
		||||
import DevicesToPay from "@/components/devices-to-pay";
 | 
			
		||||
import { Button } from "@/components/ui/button";
 | 
			
		||||
import { cn } from "@/lib/utils";
 | 
			
		||||
import { tryCatch } from "@/utils/tryCatch";
 | 
			
		||||
import { Trash2 } from "lucide-react";
 | 
			
		||||
import { getServerSession } from "next-auth";
 | 
			
		||||
import { headers } from "next/headers";
 | 
			
		||||
import React from "react";
 | 
			
		||||
export default async function PaymentPage({
 | 
			
		||||
	params,
 | 
			
		||||
}: {
 | 
			
		||||
@@ -16,12 +17,13 @@ export default async function PaymentPage({
 | 
			
		||||
	const paymentId = (await params).paymentId;
 | 
			
		||||
	const [error, payment] = await tryCatch(getPayment({ id: paymentId }));
 | 
			
		||||
	if (error) {
 | 
			
		||||
		return <div>Error getting payment: {error.message}</div>;
 | 
			
		||||
		return <span>Error getting payment: {error.message}</span>;
 | 
			
		||||
	}
 | 
			
		||||
	return (
 | 
			
		||||
		<div>
 | 
			
		||||
			<div className="flex justify-between items-center border-[1px] rounded-md border-dashed font-bold title-bg py-4 px-2 mb-4">
 | 
			
		||||
			<div className="flex justify-between items-center border-[1px] rounded-md border-dashed font-bold title-bg py-4 px-4 mb-4 mx-2">
 | 
			
		||||
				<h3 className="text-sarLinkOrange text-2xl">Payment</h3>
 | 
			
		||||
				<div className="flex gap-4 items-center">
 | 
			
		||||
					<span
 | 
			
		||||
						className={cn(
 | 
			
		||||
							"text-sm border px-4 py-2 rounded-md uppercase font-semibold",
 | 
			
		||||
@@ -32,6 +34,8 @@ export default async function PaymentPage({
 | 
			
		||||
					>
 | 
			
		||||
						{payment?.paid ? "Paid" : "Pending"}
 | 
			
		||||
					</span>
 | 
			
		||||
					<CancelPaymentButton paymentId={paymentId} />
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
 | 
			
		||||
			<div
 | 
			
		||||
 
 | 
			
		||||
@@ -16,9 +16,7 @@ export default async function Devices({
 | 
			
		||||
	return (
 | 
			
		||||
		<div>
 | 
			
		||||
			<div className="flex justify-between items-center border-[1px] rounded-md border-dashed font-bold title-bg py-4 px-2 mb-4">
 | 
			
		||||
        <h3 className="text-sarLinkOrange text-2xl">
 | 
			
		||||
          My Payments
 | 
			
		||||
        </h3>
 | 
			
		||||
				<h3 className="text-sarLinkOrange text-2xl">My Payments</h3>
 | 
			
		||||
			</div>
 | 
			
		||||
 | 
			
		||||
			<div
 | 
			
		||||
 
 | 
			
		||||
@@ -10,6 +10,9 @@ export default function AddDevicesToCartButton({ device }: { device: Device }) {
 | 
			
		||||
 | 
			
		||||
	const isChecked = devices.some((d) => d.id === device.id);
 | 
			
		||||
 | 
			
		||||
	if (device.has_a_pending_payment || device.is_active) {
 | 
			
		||||
		return null;
 | 
			
		||||
	}
 | 
			
		||||
	return (
 | 
			
		||||
		<input
 | 
			
		||||
			type="checkbox"
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										35
									
								
								components/billing/cancel-payment-button.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								components/billing/cancel-payment-button.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,35 @@
 | 
			
		||||
"use client";
 | 
			
		||||
 | 
			
		||||
import { cancelPayment } from "@/actions/payment";
 | 
			
		||||
import { tryCatch } from "@/utils/tryCatch";
 | 
			
		||||
import { Loader2, Trash2 } from "lucide-react";
 | 
			
		||||
import { useRouter } from "next/navigation";
 | 
			
		||||
import React from "react";
 | 
			
		||||
import { toast } from "sonner";
 | 
			
		||||
import { Button } from "../ui/button";
 | 
			
		||||
 | 
			
		||||
export default function CancelPaymentButton({
 | 
			
		||||
	paymentId,
 | 
			
		||||
}: { paymentId: string }) {
 | 
			
		||||
	const router = useRouter();
 | 
			
		||||
	const [loading, setLoading] = React.useState(false);
 | 
			
		||||
	return (
 | 
			
		||||
		<Button
 | 
			
		||||
			onClick={async () => {
 | 
			
		||||
				setLoading(true);
 | 
			
		||||
				const [error, _] = await tryCatch(cancelPayment({ id: paymentId }));
 | 
			
		||||
				if (error) {
 | 
			
		||||
					toast.error(error.message);
 | 
			
		||||
					setLoading(false);
 | 
			
		||||
				} else {
 | 
			
		||||
					router.replace("/devices");
 | 
			
		||||
				}
 | 
			
		||||
			}}
 | 
			
		||||
			disabled={loading}
 | 
			
		||||
			variant={"destructive"}
 | 
			
		||||
		>
 | 
			
		||||
			Cancel Payment
 | 
			
		||||
			{loading ? <Loader2 className="animate-spin" /> : <Trash2 />}
 | 
			
		||||
		</Button>
 | 
			
		||||
	);
 | 
			
		||||
}
 | 
			
		||||
@@ -5,6 +5,7 @@ import type { Device } from "@/lib/backend-types";
 | 
			
		||||
import { cn } from "@/lib/utils";
 | 
			
		||||
import { pl } from "date-fns/locale";
 | 
			
		||||
import { useAtom } from "jotai";
 | 
			
		||||
import { Hourglass } from "lucide-react";
 | 
			
		||||
import Link from "next/link";
 | 
			
		||||
import AddDevicesToCartButton from "./add-devices-to-cart-button";
 | 
			
		||||
import BlockDeviceDialog from "./block-device-dialog";
 | 
			
		||||
@@ -20,12 +21,13 @@ export default function ClickableRow({
 | 
			
		||||
			key={device.id}
 | 
			
		||||
			className={cn(
 | 
			
		||||
				(parentalControl === false && device.blocked) || device.is_active
 | 
			
		||||
					? "cursor-not-allowed title-bg"
 | 
			
		||||
					? "cursor-not-allowed bg-accent-foreground/10 hover:bg-accent-foreground/10"
 | 
			
		||||
					: "cursor-pointer hover:bg-muted",
 | 
			
		||||
			)}
 | 
			
		||||
			onClick={() => {
 | 
			
		||||
				if (device.blocked) return;
 | 
			
		||||
				if (device.is_active === true) return;
 | 
			
		||||
				if (device.has_a_pending_payment === true) return;
 | 
			
		||||
				if (parentalControl === true) return;
 | 
			
		||||
				setDeviceCart((prev) =>
 | 
			
		||||
					devices.some((d) => d.id === device.id)
 | 
			
		||||
@@ -47,22 +49,34 @@ export default function ClickableRow({
 | 
			
		||||
						{device.name}
 | 
			
		||||
					</Link>
 | 
			
		||||
					{device.is_active ? (
 | 
			
		||||
						<span className="text-muted-foreground">
 | 
			
		||||
						<div className="text-muted-foreground">
 | 
			
		||||
							Active until{" "}
 | 
			
		||||
							{new Date(device.expiry_date || "").toLocaleDateString("en-US", {
 | 
			
		||||
							<span className="font-semibold">
 | 
			
		||||
								{new Date(device.expiry_date || "").toLocaleDateString(
 | 
			
		||||
									"en-US",
 | 
			
		||||
									{
 | 
			
		||||
										month: "short",
 | 
			
		||||
										day: "2-digit",
 | 
			
		||||
										year: "numeric",
 | 
			
		||||
							})}
 | 
			
		||||
									},
 | 
			
		||||
								)}
 | 
			
		||||
							</span>
 | 
			
		||||
						</div>
 | 
			
		||||
					) : (
 | 
			
		||||
						<p className="text-muted-foreground">Device Inactive</p>
 | 
			
		||||
					)}
 | 
			
		||||
					{device.has_a_pending_payment && (
 | 
			
		||||
						<Link href={`/payments/${device.pending_payment_id}`}>
 | 
			
		||||
							<span className="flex hover:underline items-center justify-center gap-2 text-muted-foreground text-yellow-600">
 | 
			
		||||
								Payment Pending <Hourglass size={14} />
 | 
			
		||||
							</span>
 | 
			
		||||
						</Link>
 | 
			
		||||
					)}
 | 
			
		||||
 | 
			
		||||
					{device.blocked_by === "ADMIN" && device.blocked && (
 | 
			
		||||
						<div className="p-2 rounded border my-2">
 | 
			
		||||
							<span>Comment: </span>
 | 
			
		||||
							<p className="text-neutral-500">{device?.reason_for_blocking}</p>
 | 
			
		||||
						<div className="p-2 rounded border my-2 bg-white dark:bg-neutral-800 shadow">
 | 
			
		||||
							<span className="font-semibold">Comment</span>
 | 
			
		||||
							<p className="text-neutral-400">{device?.reason_for_blocking}</p>
 | 
			
		||||
						</div>
 | 
			
		||||
					)}
 | 
			
		||||
				</div>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,15 +1,19 @@
 | 
			
		||||
'use client'
 | 
			
		||||
import { deviceCartAtom } from '@/lib/atoms'
 | 
			
		||||
import { cn } from '@/lib/utils'
 | 
			
		||||
import type { Device } from '@prisma/client'
 | 
			
		||||
import { useAtom } from 'jotai'
 | 
			
		||||
import Link from 'next/link'
 | 
			
		||||
import AddDevicesToCartButton from './add-devices-to-cart-button'
 | 
			
		||||
import BlockDeviceDialog from './block-device-dialog'
 | 
			
		||||
import { Badge } from './ui/badge'
 | 
			
		||||
"use client";
 | 
			
		||||
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 Link from "next/link";
 | 
			
		||||
import AddDevicesToCartButton from "./add-devices-to-cart-button";
 | 
			
		||||
import BlockDeviceDialog from "./block-device-dialog";
 | 
			
		||||
import { Badge } from "./ui/badge";
 | 
			
		||||
 | 
			
		||||
export default function DeviceCard({ device, parentalControl }: { device: Device, parentalControl?: boolean }) {
 | 
			
		||||
  const [devices, setDeviceCart] = useAtom(deviceCartAtom)
 | 
			
		||||
export default function DeviceCard({
 | 
			
		||||
	device,
 | 
			
		||||
	parentalControl,
 | 
			
		||||
}: { device: Device; parentalControl?: boolean }) {
 | 
			
		||||
	const [devices, setDeviceCart] = useAtom(deviceCartAtom);
 | 
			
		||||
 | 
			
		||||
	const isChecked = devices.some((d) => d.id === device.id);
 | 
			
		||||
 | 
			
		||||
@@ -17,17 +21,28 @@ export default function DeviceCard({ device, parentalControl }: { device: Device
 | 
			
		||||
		<div
 | 
			
		||||
			onKeyUp={() => {}}
 | 
			
		||||
			onClick={() => {
 | 
			
		||||
        if (parentalControl === true) return
 | 
			
		||||
				if (device.blocked) return;
 | 
			
		||||
				if (device.is_active === true) return;
 | 
			
		||||
				if (device.has_a_pending_payment === true) return;
 | 
			
		||||
				if (parentalControl === true) return;
 | 
			
		||||
				setDeviceCart((prev) =>
 | 
			
		||||
					devices.some((d) => d.id === device.id)
 | 
			
		||||
						? prev.filter((d) => d.id !== device.id)
 | 
			
		||||
            : [...prev, device]
 | 
			
		||||
        )
 | 
			
		||||
      }
 | 
			
		||||
      }
 | 
			
		||||
      className="w-full">
 | 
			
		||||
      <div className={cn("flex text-sm justify-between items-center my-2 p-4 border rounded-md", isChecked ? "bg-accent" : "bg-")}>
 | 
			
		||||
        <div className=''>
 | 
			
		||||
						: [...prev, device],
 | 
			
		||||
				);
 | 
			
		||||
			}}
 | 
			
		||||
			className="w-full"
 | 
			
		||||
		>
 | 
			
		||||
			<div
 | 
			
		||||
				className={cn(
 | 
			
		||||
					"flex text-sm justify-between items-center my-2 p-4 border rounded-md",
 | 
			
		||||
					isChecked ? "bg-accent" : "bg-",
 | 
			
		||||
					device.is_active
 | 
			
		||||
						? "cursor-not-allowed bg-accent-foreground/10 hover:bg-accent-foreground/10"
 | 
			
		||||
						: "cursor-pointer hover:bg-muted",
 | 
			
		||||
				)}
 | 
			
		||||
			>
 | 
			
		||||
				<div className="">
 | 
			
		||||
					<div className="font-semibold flex flex-col items-start gap-2 mb-2">
 | 
			
		||||
						<Link
 | 
			
		||||
							className="font-medium hover:underline"
 | 
			
		||||
@@ -36,41 +51,54 @@ export default function DeviceCard({ device, parentalControl }: { device: Device
 | 
			
		||||
							{device.name}
 | 
			
		||||
						</Link>
 | 
			
		||||
						<Badge variant={"secondary"}>
 | 
			
		||||
              <span className="font-medium">
 | 
			
		||||
                {device.mac}
 | 
			
		||||
              </span>
 | 
			
		||||
							<span className="font-medium">{device.mac}</span>
 | 
			
		||||
						</Badge>
 | 
			
		||||
					</div>
 | 
			
		||||
 | 
			
		||||
          {device.isActive && (
 | 
			
		||||
            <span className="text-muted-foreground">
 | 
			
		||||
					{device.is_active ? (
 | 
			
		||||
						<div className="text-muted-foreground">
 | 
			
		||||
							Active until{" "}
 | 
			
		||||
              {new Date().toLocaleDateString("en-US", {
 | 
			
		||||
							<span className="font-semibold">
 | 
			
		||||
								{new Date(device.expiry_date || "").toLocaleDateString(
 | 
			
		||||
									"en-US",
 | 
			
		||||
									{
 | 
			
		||||
										month: "short",
 | 
			
		||||
										day: "2-digit",
 | 
			
		||||
										year: "numeric",
 | 
			
		||||
              })}
 | 
			
		||||
									},
 | 
			
		||||
								)}
 | 
			
		||||
							</span>
 | 
			
		||||
						</div>
 | 
			
		||||
					) : (
 | 
			
		||||
						<p className="text-muted-foreground">Device Inactive</p>
 | 
			
		||||
					)}
 | 
			
		||||
					{device.has_a_pending_payment && (
 | 
			
		||||
						<Link href={`/payments/${device.pending_payment_id}`}>
 | 
			
		||||
							<span className="flex hover:underline items-center justify-center gap-2 text-muted-foreground text-yellow-600">
 | 
			
		||||
								Payment Pending <Hourglass size={14} />
 | 
			
		||||
							</span>
 | 
			
		||||
						</Link>
 | 
			
		||||
					)}
 | 
			
		||||
 | 
			
		||||
          {(device.blocked && device.blockedBy === "ADMIN") && (
 | 
			
		||||
					{device.blocked && device.blocked_by === "ADMIN" && (
 | 
			
		||||
						<div className="p-2 rounded border my-2 w-full">
 | 
			
		||||
              <span className='uppercase text-red-500'>Blocked by admin </span>
 | 
			
		||||
              <p className="text-neutral-500">
 | 
			
		||||
                {device?.reasonForBlocking}
 | 
			
		||||
              </p>
 | 
			
		||||
							<span className="uppercase text-red-500">Blocked by admin </span>
 | 
			
		||||
							<p className="text-neutral-500">{device?.reason_for_blocking}</p>
 | 
			
		||||
						</div>
 | 
			
		||||
					)}
 | 
			
		||||
 | 
			
		||||
				</div>
 | 
			
		||||
				<div>
 | 
			
		||||
					{!parentalControl ? (
 | 
			
		||||
						<AddDevicesToCartButton device={device} />
 | 
			
		||||
					) : (
 | 
			
		||||
            <BlockDeviceDialog admin={false} type={device.blocked ? "unblock" : "block"} device={device} />
 | 
			
		||||
						<BlockDeviceDialog
 | 
			
		||||
							admin={false}
 | 
			
		||||
							type={device.blocked ? "unblock" : "block"}
 | 
			
		||||
							device={device}
 | 
			
		||||
						/>
 | 
			
		||||
					)}
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
  )
 | 
			
		||||
	);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -10,7 +10,7 @@ import { tryCatch } from "@/utils/tryCatch";
 | 
			
		||||
import { useAtom, useAtomValue, useSetAtom } from "jotai";
 | 
			
		||||
import { CircleDollarSign, Loader2 } from "lucide-react";
 | 
			
		||||
import { useSession } from "next-auth/react";
 | 
			
		||||
import { usePathname } from "next/navigation";
 | 
			
		||||
import { redirect, usePathname } from "next/navigation";
 | 
			
		||||
import { useEffect, useState } from "react";
 | 
			
		||||
import { toast } from "sonner";
 | 
			
		||||
export default function DevicesForPayment() {
 | 
			
		||||
@@ -35,7 +35,7 @@ export default function DevicesForPayment() {
 | 
			
		||||
		setTotal(baseAmount + (devices.length + 1 - 1) * discountPercentage);
 | 
			
		||||
	}, [months, devices.length]);
 | 
			
		||||
 | 
			
		||||
	if (pathname === "/payment") {
 | 
			
		||||
	if (pathname === "/payments") {
 | 
			
		||||
		return null;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -45,6 +45,9 @@ export default function DevicesForPayment() {
 | 
			
		||||
		device_ids: devices.map((device) => device.id),
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	if (disabled) {
 | 
			
		||||
		return "Please wait...";
 | 
			
		||||
	}
 | 
			
		||||
	return (
 | 
			
		||||
		<div className="max-w-lg mx-auto space-y-4 px-4">
 | 
			
		||||
			<div className="flex max-h-[calc(100svh-400px)] flex-col overflow-auto pb-4 gap-4">
 | 
			
		||||
@@ -69,15 +72,15 @@ export default function DevicesForPayment() {
 | 
			
		||||
			<Button
 | 
			
		||||
				onClick={async () => {
 | 
			
		||||
					setDisabled(true);
 | 
			
		||||
					const [error, respose] = await tryCatch(createPayment(data));
 | 
			
		||||
					const [error, response] = await tryCatch(createPayment(data));
 | 
			
		||||
					if (error) {
 | 
			
		||||
						setDisabled(false);
 | 
			
		||||
						toast.error(error.message);
 | 
			
		||||
						toast.error(JSON.stringify(error, null, 2));
 | 
			
		||||
						return;
 | 
			
		||||
					}
 | 
			
		||||
					setDeviceCart([]);
 | 
			
		||||
					setMonths(1);
 | 
			
		||||
					setDisabled(false);
 | 
			
		||||
					redirect(`/payments/${response.id}`);
 | 
			
		||||
				}}
 | 
			
		||||
				className="w-full"
 | 
			
		||||
				disabled={devices.length === 0 || disabled}
 | 
			
		||||
 
 | 
			
		||||
@@ -56,43 +56,6 @@ export async function DevicesTable({
 | 
			
		||||
							</TableHeader>
 | 
			
		||||
							<TableBody className="overflow-scroll">
 | 
			
		||||
								{data.map((device) => (
 | 
			
		||||
									// <TableRow key={device.id}>
 | 
			
		||||
									// 	<TableCell>
 | 
			
		||||
									// 		<div className="flex flex-col items-start">
 | 
			
		||||
									// 			<Link
 | 
			
		||||
									// 				className="font-medium hover:underline"
 | 
			
		||||
									// 				href={`/devices/${device.id}`}
 | 
			
		||||
									// 			>
 | 
			
		||||
									// 				{device.name}
 | 
			
		||||
									// 			</Link>
 | 
			
		||||
									// 			<span className="text-muted-foreground">
 | 
			
		||||
									// 				Active until{" "}
 | 
			
		||||
									// 				{new Date().toLocaleDateString("en-US", {
 | 
			
		||||
									// 					month: "short",
 | 
			
		||||
									// 					day: "2-digit",
 | 
			
		||||
									// 					year: "numeric",
 | 
			
		||||
									// 				})}
 | 
			
		||||
									// 			</span>
 | 
			
		||||
									// 			{parentalControl && (
 | 
			
		||||
									// 				<div className="p-2 rounded border my-2">
 | 
			
		||||
									// 					<span>Comment: </span>
 | 
			
		||||
									// 					<p className="text-neutral-500">
 | 
			
		||||
									// 						blocked because he was watching youtube
 | 
			
		||||
									// 					</p>
 | 
			
		||||
									// 				</div>
 | 
			
		||||
									// 			)}
 | 
			
		||||
 | 
			
		||||
									// 		</div>
 | 
			
		||||
									// 	</TableCell>
 | 
			
		||||
									// 	<TableCell className="font-medium">{device.mac}</TableCell>
 | 
			
		||||
									// 	<TableCell>
 | 
			
		||||
									// 		{!parentalControl ? (
 | 
			
		||||
									// 			<AddDevicesToCartButton device={device} />
 | 
			
		||||
									// 		) : (
 | 
			
		||||
									// 			<BlockDeviceButton device={device} />
 | 
			
		||||
									// 		)}
 | 
			
		||||
									// 	</TableCell>
 | 
			
		||||
									// </TableRow>
 | 
			
		||||
									<ClickableRow
 | 
			
		||||
										admin={isAdmin}
 | 
			
		||||
										key={device.id}
 | 
			
		||||
 
 | 
			
		||||
@@ -15,6 +15,7 @@ import type { Payment } from "@/lib/backend-types";
 | 
			
		||||
import { cn } from "@/lib/utils";
 | 
			
		||||
import { tryCatch } from "@/utils/tryCatch";
 | 
			
		||||
import { Calendar } from "lucide-react";
 | 
			
		||||
import { redirect } from "next/navigation";
 | 
			
		||||
import Pagination from "./pagination";
 | 
			
		||||
import { Badge } from "./ui/badge";
 | 
			
		||||
import { Button } from "./ui/button";
 | 
			
		||||
@@ -86,8 +87,12 @@ export async function PaymentsTable({
 | 
			
		||||
	const [error, payments] = await tryCatch(getPayments());
 | 
			
		||||
 | 
			
		||||
	if (error) {
 | 
			
		||||
		if (error.message.includes("Unauthorized")) {
 | 
			
		||||
			redirect("/auth/signin");
 | 
			
		||||
		} else {
 | 
			
		||||
			return <pre>{JSON.stringify(error, null, 2)}</pre>;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	const { data, meta, links } = payments;
 | 
			
		||||
	return (
 | 
			
		||||
		<div>
 | 
			
		||||
 
 | 
			
		||||
@@ -35,6 +35,8 @@ export interface Device {
 | 
			
		||||
	name: string;
 | 
			
		||||
	mac: string;
 | 
			
		||||
	reason_for_blocking: string | null;
 | 
			
		||||
	has_a_pending_payment: boolean;
 | 
			
		||||
	pending_payment_id: string | null;
 | 
			
		||||
	is_active: boolean;
 | 
			
		||||
	registered: boolean;
 | 
			
		||||
	blocked: boolean;
 | 
			
		||||
 
 | 
			
		||||
@@ -2,6 +2,7 @@
 | 
			
		||||
 | 
			
		||||
import { authOptions } from "@/app/auth";
 | 
			
		||||
import type { Api400Error, ApiResponse, Device } from "@/lib/backend-types";
 | 
			
		||||
import { AxiosClient } from "@/utils/axios-client";
 | 
			
		||||
import { checkSession } from "@/utils/session";
 | 
			
		||||
import { getServerSession } from "next-auth";
 | 
			
		||||
import { revalidatePath } from "next/cache";
 | 
			
		||||
@@ -14,7 +15,7 @@ type GetDevicesProps = {
 | 
			
		||||
};
 | 
			
		||||
export async function getDevices({ query }: GetDevicesProps) {
 | 
			
		||||
	const session = await checkSession();
 | 
			
		||||
	const respose = await fetch(
 | 
			
		||||
	const response = await fetch(
 | 
			
		||||
		`${process.env.SARLINK_API_BASE_URL}/api/devices/?name=${query}`,
 | 
			
		||||
		{
 | 
			
		||||
			method: "GET",
 | 
			
		||||
@@ -24,7 +25,10 @@ export async function getDevices({ query }: GetDevicesProps) {
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	);
 | 
			
		||||
	const data = (await respose.json()) as ApiResponse<Device>;
 | 
			
		||||
	if (response.status === 401) {
 | 
			
		||||
		throw new Error("Unauthorized; redirect to /auth/signin");
 | 
			
		||||
	}
 | 
			
		||||
	const data = (await response.json()) as ApiResponse<Device>;
 | 
			
		||||
	return data;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
"use server";
 | 
			
		||||
 | 
			
		||||
import { AxiosClient } from "@/utils/AxiosClient";
 | 
			
		||||
import { AxiosClient } from "@/utils/axios-client";
 | 
			
		||||
 | 
			
		||||
export async function getIslands() {
 | 
			
		||||
	const response = await AxiosClient.get("/islands/");
 | 
			
		||||
 
 | 
			
		||||
@@ -1,12 +1,13 @@
 | 
			
		||||
import { authOptions } from "@/app/auth";
 | 
			
		||||
import axios, { type AxiosError } from "axios";
 | 
			
		||||
import type { Session } from "next-auth";
 | 
			
		||||
import { type Session, getServerSession } from "next-auth";
 | 
			
		||||
import { getSession } from "next-auth/react";
 | 
			
		||||
import { redirect } from "next/navigation";
 | 
			
		||||
 | 
			
		||||
axios.defaults.xsrfCookieName = "csrftoken";
 | 
			
		||||
axios.defaults.xsrfHeaderName = "X-CSRFToken";
 | 
			
		||||
 | 
			
		||||
const ApiClient = () => {
 | 
			
		||||
const APIClient = () => {
 | 
			
		||||
	const instance = axios.create({
 | 
			
		||||
		baseURL: process.env.SARLINK_API_BASE_URL,
 | 
			
		||||
		headers: {
 | 
			
		||||
@@ -15,11 +16,13 @@ const ApiClient = () => {
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	let lastSession: Session | null = null;
 | 
			
		||||
	console.log("Last session: ", lastSession);
 | 
			
		||||
 | 
			
		||||
	instance.interceptors.request.use(
 | 
			
		||||
		async (request) => {
 | 
			
		||||
			if (lastSession == null || Date.now() > Date.parse(lastSession.expires)) {
 | 
			
		||||
				const session = await getSession();
 | 
			
		||||
				const session = await getServerSession(authOptions);
 | 
			
		||||
				console.log("Server session: ", session);
 | 
			
		||||
				lastSession = session;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
@@ -27,23 +30,24 @@ const ApiClient = () => {
 | 
			
		||||
				request.headers.Authorization = `Token ${lastSession.apiToken}`;
 | 
			
		||||
			} else {
 | 
			
		||||
				request.headers.Authorization = undefined;
 | 
			
		||||
				return redirect("/auth/signin");
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return request;
 | 
			
		||||
		},
 | 
			
		||||
		(error) => {
 | 
			
		||||
			console.error("API Error: ", error);
 | 
			
		||||
			console.error("API Request Error: ", error);
 | 
			
		||||
			throw error;
 | 
			
		||||
		},
 | 
			
		||||
	);
 | 
			
		||||
 | 
			
		||||
	instance.interceptors.response.use(
 | 
			
		||||
		async (response) => {
 | 
			
		||||
			return response;
 | 
			
		||||
		},
 | 
			
		||||
		async (error: AxiosError) => {
 | 
			
		||||
			if (error?.response?.status === 401) {
 | 
			
		||||
				return redirect("/auth/signin");
 | 
			
		||||
				// Redirect to the signin page if the user is unauthorized
 | 
			
		||||
				redirect("/auth/signin");
 | 
			
		||||
			}
 | 
			
		||||
			return Promise.reject(error);
 | 
			
		||||
		},
 | 
			
		||||
@@ -52,4 +56,4 @@ const ApiClient = () => {
 | 
			
		||||
	return instance;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const AxiosClient = ApiClient();
 | 
			
		||||
export const AxiosClient = APIClient();
 | 
			
		||||
		Reference in New Issue
	
	Block a user