diff --git a/components/block-device-dialog.tsx b/components/block-device-dialog.tsx
index e1fb9b6..55d1f89 100644
--- a/components/block-device-dialog.tsx
+++ b/components/block-device-dialog.tsx
@@ -1,12 +1,9 @@
"use client";
-
-import { zodResolver } from "@hookform/resolvers/zod";
-import { OctagonX } from "lucide-react";
-import { useState } from "react";
-import { type SubmitHandler, useForm } from "react-hook-form";
+import { DialogDescription } from "@radix-ui/react-dialog";
+import { OctagonX, ShieldBan } from "lucide-react";
+import { useActionState, useEffect, useState, useTransition } from "react";
import { toast } from "sonner";
-import { z } from "zod";
import { Button } from "@/components/ui/button";
import {
Dialog,
@@ -19,160 +16,146 @@ import {
import { Label } from "@/components/ui/label";
import type { Device } from "@/lib/backend-types";
import { cn } from "@/lib/utils";
-import { blockDevice } from "@/queries/devices";
+import { blockDeviceAction } from "@/queries/devices";
import { TextShimmer } from "./ui/text-shimmer";
import { Textarea } from "./ui/textarea";
-const validationSchema = z.object({
- reasonForBlocking: z.string().min(5, { message: "Reason is required" }),
-});
+export type BlockDeviceFormState = {
+ message: string;
+ success: boolean;
+ fieldErrors?: {
+ reason_for_blocking?: string[];
+ };
+ payload?: FormData;
+};
+
+const initialState: BlockDeviceFormState = {
+ message: "",
+ success: false,
+ fieldErrors: {},
+};
export default function BlockDeviceDialog({
device,
- admin,
-}: { device: Device; type: "block" | "unblock"; admin?: boolean }) {
- const [disabled, setDisabled] = useState(false);
+ // admin,
+ parentalControl = false,
+}: {
+ device: Device;
+ type: "block" | "unblock";
+ admin?: boolean;
+ parentalControl?: boolean;
+}) {
const [open, setOpen] = useState(false);
- const {
- register,
- handleSubmit,
- formState: { errors },
- } = useForm
>({
- resolver: zodResolver(validationSchema),
- });
+ const [state, formAction, isPending] = useActionState(blockDeviceAction, initialState);
+ const [isTransitioning, startTransition] = useTransition();
- const onSubmit: SubmitHandler> = (data) => {
- setDisabled(true);
- console.log(data);
- toast.promise(
- blockDevice({
- deviceId: String(device.id),
- reason_for_blocking: data.reasonForBlocking,
- blocked_by: "ADMIN",
- }),
- {
- loading: "Blocking...",
- success: () => {
- setDisabled(false);
- setOpen((prev) => !prev);
- return "Blocked!";
- },
- error: (error) => {
- setDisabled(false);
- return error || "Something went wrong";
- },
- },
- );
- setDisabled(false);
+ const handleSimpleBlock = () => {
+ startTransition(() => {
+ const formData = new FormData();
+ formData.append("deviceId", String(device.id));
+ formData.append("reason_for_blocking", "");
+ formData.append("action", "simple-block");
+ formData.append("blocked_by", "PARENT");
+
+ formAction(formData);
+ });
};
+ const handleUnblock = () => {
+ startTransition(() => {
+ const formData = new FormData();
+ formData.append("deviceId", String(device.id));
+ formData.append("reason_for_blocking", "");
+ formData.append("action", "unblock");
+ formData.append("blocked_by", "PARENT");
+
+ formAction(formData);
+ });
+ };
+
+ // Show toast notifications based on state changes
+ useEffect(() => {
+ if (state.message) {
+ if (state.success) {
+ toast.success(state.message);
+ if (open) setOpen(false);
+ } else {
+ toast.error(state.message);
+ }
+ }
+ }, [state, open]);
+
+ const isLoading = isPending || isTransitioning;
+
+ // If device is blocked and user is not admin, show unblock button
+ if (device.blocked && parentalControl) {
+ return (
+
+ );
+ }
+
+ // If device is not blocked and user is not admin, show simple block button
+ if (!device.blocked && parentalControl) {
+ return (
+
+ );
+ }
+
+ // If user is admin, show block with reason dialog
return (
- {device.blocked ? (
-
- ) : (
-
- {!admin ? (
-
- ) : (
-
- )}
-
- )}
+
);
}
\ No newline at end of file
diff --git a/components/clickable-row.tsx b/components/clickable-row.tsx
index 02226cb..4ae8b85 100644
--- a/components/clickable-row.tsx
+++ b/components/clickable-row.tsx
@@ -91,6 +91,7 @@ export default function ClickableRow({
admin={admin}
type={device.blocked ? "unblock" : "block"}
device={device}
+ parentalControl={parentalControl}
/>
)}
diff --git a/components/device-card.tsx b/components/device-card.tsx
index 6349294..1f3fb72 100644
--- a/components/device-card.tsx
+++ b/components/device-card.tsx
@@ -1,10 +1,10 @@
"use client";
-import { deviceCartAtom } from "@/lib/atoms";
-import type { Device } from "@/lib/backend-types";
-import { cn } from "@/lib/utils";
import { useAtom } from "jotai";
import { HandCoins } from "lucide-react";
import Link from "next/link";
+import { deviceCartAtom } from "@/lib/atoms";
+import type { Device } from "@/lib/backend-types";
+import { cn } from "@/lib/utils";
import AddDevicesToCartButton from "./add-devices-to-cart-button";
import BlockDeviceDialog from "./block-device-dialog";
import { Badge } from "./ui/badge";
diff --git a/components/devices-table.tsx b/components/devices-table.tsx
index f110417..c28fdf3 100644
--- a/components/devices-table.tsx
+++ b/components/devices-table.tsx
@@ -21,11 +21,14 @@ import Pagination from "./pagination";
export async function DevicesTable({
searchParams,
parentalControl,
+ additionalFilters = {},
+
}: {
searchParams: Promise<{
[key: string]: unknown;
}>;
parentalControl?: boolean;
+ additionalFilters?: Record;
}) {
const resolvedParams = await searchParams;
const session = await getServerSession(authOptions);
@@ -42,9 +45,15 @@ export async function DevicesTable({
apiParams[key] = typeof value === "number" ? value : String(value);
}
}
+
+ for (const [key, value] of Object.entries(additionalFilters)) {
+ if (value !== undefined && value !== "") {
+ apiParams[key] = typeof value === "number" ? value : String(value);
+ }
+ }
apiParams.limit = limit;
apiParams.offset = offset;
-
+ console.log("API Params:", apiParams);
const [error, devices] = await tryCatch(
getDevices(apiParams),
);
diff --git a/queries/devices.ts b/queries/devices.ts
index a81d65f..a6cffd3 100644
--- a/queries/devices.ts
+++ b/queries/devices.ts
@@ -3,6 +3,7 @@
import { revalidatePath } from "next/cache";
import { getServerSession } from "next-auth";
import { authOptions } from "@/app/auth";
+import { BlockDeviceFormState } from "@/components/block-device-dialog";
import type { AddDeviceFormState, initialState } from "@/components/user/add-device-dialog";
import type { ApiError, ApiResponse, Device } from "@/lib/backend-types";
import { checkSession } from "@/utils/session";
@@ -114,33 +115,77 @@ export async function addDeviceAction(
}
}
-export async function blockDevice({
- deviceId,
- reason_for_blocking,
- blocked_by,
-}: {
- deviceId: string;
- reason_for_blocking: string;
- blocked_by: "ADMIN" | "PARENT";
-}) {
- console.log("Blocking device:", deviceId, reason_for_blocking, blocked_by);
- const session = await getServerSession(authOptions);
- const response = await fetch(
- `${process.env.SARLINK_API_BASE_URL}/api/devices/${deviceId}/block/`,
- {
- method: "PUT",
- headers: {
- "Content-Type": "application/json",
- Authorization: `Token ${session?.apiToken}`,
+export async function blockDeviceAction(
+ prevState: BlockDeviceFormState,
+ formData: FormData
+): Promise {
+ const deviceId = formData.get("deviceId") as string;
+ const reason_for_blocking = formData.get("reason_for_blocking") as string;
+ const action = formData.get("action") as "block" | "unblock" | "simple-block";
+ const blocked_by = formData.get("blocked_by") as "ADMIN" | "PARENT";
+
+ try {
+ const session = await getServerSession(authOptions);
+ if (!session?.apiToken) {
+ return {
+ success: false,
+ message: "Authentication required.",
+ fieldErrors: {},
+ payload: formData
+ };
+ }
+
+ // Validation only for admin block with reason
+ if (action === "block" && session?.user?.is_superuser && (!reason_for_blocking || reason_for_blocking.trim().length < 5)) {
+ return {
+ success: false,
+ message: "Reason for blocking is required and must be at least 5 characters.",
+ fieldErrors: {
+ reason_for_blocking: ["Reason is required and must be at least 5 characters."]
+ },
+ payload: formData
+ };
+ }
+
+ const isBlocking = action === "block" || action === "simple-block";
+
+ const response = await fetch(
+ `${process.env.SARLINK_API_BASE_URL}/api/devices/${deviceId}/block/`,
+ {
+ method: "PUT",
+ headers: {
+ "Content-Type": "application/json",
+ Authorization: `Token ${session.apiToken}`,
+ },
+ body: JSON.stringify({
+ blocked: isBlocking,
+ reason_for_blocking: session?.user?.is_superuser
+ ? reason_for_blocking || (action === "simple-block" ? "Blocked by admin" : "")
+ : isBlocking ? "Blocked by parent" : "",
+ blocked_by: session?.user?.is_superuser ? blocked_by : "PARENT",
+ }),
},
- body: JSON.stringify({
- blocked: true,
- reason_for_blocking: session?.user?.is_superuser
- ? reason_for_blocking
- : "Blocked by parent",
- blocked_by: session?.user?.is_superuser ? "ADMIN" : "PARENT",
- }),
- },
- );
- return handleApiResponse(response, "blockDevice");
-}
+ );
+
+ const result = await handleApiResponse(response, "blockDeviceAction");
+
+ revalidatePath("/devices");
+ revalidatePath("/parental-control");
+
+ return {
+ success: true,
+ message: isBlocking ? "Device blocked successfully!" : "Device unblocked successfully!",
+ fieldErrors: {},
+ payload: formData
+ };
+
+ } catch (error: unknown) {
+ console.error("Block Device Action Error:", error);
+ return {
+ success: false,
+ message: (error as ApiError).message || "An unexpected error occurred.",
+ fieldErrors: {},
+ payload: formData
+ };
+ }
+}
\ No newline at end of file