mirror of
				https://github.com/i701/sarlink-portal.git
				synced 2025-11-04 06:26:59 +00:00 
			
		
		
		
	refactor: streamline authentication flow by removing unused code, replacing custom auth utilities with NextAuth, and updating session handling in components
	
		
			
	
		
	
	
		
	
		
			Some checks failed
		
		
	
	
		
			
				
	
				Build and Push Docker Images / Build and Push Docker Images (push) Failing after 5m56s
				
			
		
		
	
	
				
					
				
			
		
			Some checks failed
		
		
	
	Build and Push Docker Images / Build and Push Docker Images (push) Failing after 5m56s
				
			This commit is contained in:
		@@ -10,6 +10,11 @@ const formSchema = z.object({
 | 
			
		||||
		.regex(/^[7|9][0-9]{2}-[0-9]{4}$/, "Please enter a valid phone number"),
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
type FilterUserResponse = {
 | 
			
		||||
	ok: boolean;
 | 
			
		||||
	verified: boolean;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export async function signin(previousState: ActionState, formData: FormData) {
 | 
			
		||||
	const phoneNumber = formData.get("phoneNumber") as string;
 | 
			
		||||
	const result = formSchema.safeParse({ phoneNumber });
 | 
			
		||||
@@ -31,7 +36,28 @@ export async function signin(previousState: ActionState, formData: FormData) {
 | 
			
		||||
	const FORMATTED_MOBILE_NUMBER: string = `${phoneNumber.split("-").join("")}`;
 | 
			
		||||
	console.log({ FORMATTED_MOBILE_NUMBER });
 | 
			
		||||
 | 
			
		||||
	const userExistsResponse = await fetch(
 | 
			
		||||
	const user = await fetch(
 | 
			
		||||
		`${process.env.SARLINK_API_BASE_URL}/api/auth/users/filter/?mobile=${FORMATTED_MOBILE_NUMBER}`,
 | 
			
		||||
		{
 | 
			
		||||
			method: "GET",
 | 
			
		||||
			headers: {
 | 
			
		||||
				"Content-Type": "application/json",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	);
 | 
			
		||||
	const userData = (await user.json()) as FilterUserResponse;
 | 
			
		||||
	if (!userData?.ok) {
 | 
			
		||||
		return redirect(`/auth/signup?phone_number=${phoneNumber}`);
 | 
			
		||||
	}
 | 
			
		||||
	if (!userData.verified) {
 | 
			
		||||
		return {
 | 
			
		||||
			message:
 | 
			
		||||
				"Your account is on pending verification. Please wait for a response from admin or contact shihaam.",
 | 
			
		||||
			status: "error",
 | 
			
		||||
		};
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	const sendOTPResponse = await fetch(
 | 
			
		||||
		`${process.env.SARLINK_API_BASE_URL}/auth/mobile/`,
 | 
			
		||||
		{
 | 
			
		||||
			method: "POST",
 | 
			
		||||
@@ -43,23 +69,10 @@ export async function signin(previousState: ActionState, formData: FormData) {
 | 
			
		||||
			}),
 | 
			
		||||
		},
 | 
			
		||||
	);
 | 
			
		||||
	const userExists = await userExistsResponse.json();
 | 
			
		||||
	console.log("user exists", userExists);
 | 
			
		||||
	if (userExists?.non_field_errors) {
 | 
			
		||||
		return redirect(`/signup?phone_number=${phoneNumber}`);
 | 
			
		||||
	}
 | 
			
		||||
	const otpResponse = await sendOTPResponse.json();
 | 
			
		||||
	console.log("otpResponse", otpResponse);
 | 
			
		||||
 | 
			
		||||
	if (!userExists?.verified)
 | 
			
		||||
		return {
 | 
			
		||||
			message:
 | 
			
		||||
				"Your account is on pending verification. Please wait for a response from admin or contact shihaam.",
 | 
			
		||||
			status: "error",
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
	// await authClient.phoneNumber.sendOtp({
 | 
			
		||||
	// 	phoneNumber: NUMBER_WITH_COUNTRY_CODE,
 | 
			
		||||
	// });
 | 
			
		||||
	redirect(`/verify-otp?phone_number=${FORMATTED_MOBILE_NUMBER}`);
 | 
			
		||||
	redirect(`/auth/verify-otp?phone_number=${FORMATTED_MOBILE_NUMBER}`);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ActionState = {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,5 @@
 | 
			
		||||
"use server";
 | 
			
		||||
 | 
			
		||||
import prisma from "@/lib/db";
 | 
			
		||||
import type { GroupProfile, MacAddress, OmadaResponse } from "@/lib/types";
 | 
			
		||||
import { formatMacAddress } from "@/lib/utils";
 | 
			
		||||
import { revalidatePath } from "next/cache";
 | 
			
		||||
@@ -124,11 +123,11 @@ export async function blockDevice({
 | 
			
		||||
	if (!macAddress) {
 | 
			
		||||
		throw new Error("macAddress is a required parameter");
 | 
			
		||||
	}
 | 
			
		||||
	const device = await prisma.device.findFirst({
 | 
			
		||||
		where: {
 | 
			
		||||
			mac: macAddress,
 | 
			
		||||
		},
 | 
			
		||||
	});
 | 
			
		||||
	// const device = await prisma.device.findFirst({
 | 
			
		||||
	// 	where: {
 | 
			
		||||
	// 		mac: macAddress,
 | 
			
		||||
	// 	},
 | 
			
		||||
	// });
 | 
			
		||||
	try {
 | 
			
		||||
		const baseUrl: string = process.env.OMADA_BASE_URL || "";
 | 
			
		||||
		const url: string = `${baseUrl}/api/v2/sites/${process.env.OMADA_SITE_ID}/cmd/clients/${formatMacAddress(macAddress)}/${type}`;
 | 
			
		||||
@@ -146,16 +145,16 @@ export async function blockDevice({
 | 
			
		||||
		if (!response.ok) {
 | 
			
		||||
			throw new Error(`HTTP error! status: ${response.status}`);
 | 
			
		||||
		}
 | 
			
		||||
		await prisma.device.update({
 | 
			
		||||
			where: {
 | 
			
		||||
				id: device?.id,
 | 
			
		||||
			},
 | 
			
		||||
			data: {
 | 
			
		||||
				reasonForBlocking: type === "block" ? reason : "",
 | 
			
		||||
				blocked: type === "block",
 | 
			
		||||
				blockedBy: blockedBy,
 | 
			
		||||
			},
 | 
			
		||||
		});
 | 
			
		||||
		// await prisma.device.update({
 | 
			
		||||
		// 	where: {
 | 
			
		||||
		// 		id: device?.id,
 | 
			
		||||
		// 	},
 | 
			
		||||
		// 	data: {
 | 
			
		||||
		// 		reasonForBlocking: type === "block" ? reason : "",
 | 
			
		||||
		// 		blocked: type === "block",
 | 
			
		||||
		// 		blockedBy: blockedBy,
 | 
			
		||||
		// 	},
 | 
			
		||||
		// });
 | 
			
		||||
		revalidatePath("/parental-control");
 | 
			
		||||
	} catch (error) {
 | 
			
		||||
		console.error("Error blocking device:", error);
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,5 @@
 | 
			
		||||
import LoginForm from "@/components/auth/login-form";
 | 
			
		||||
import { auth } from "@/app/auth";
 | 
			
		||||
import { headers } from "next/headers";
 | 
			
		||||
import Image from "next/image";
 | 
			
		||||
import { redirect } from "next/navigation";
 | 
			
		||||
import React from "react";
 | 
			
		||||
 | 
			
		||||
export default async function LoginPage() {
 | 
			
		||||
	return (
 | 
			
		||||
 
 | 
			
		||||
@@ -1,12 +1,10 @@
 | 
			
		||||
import { authOptions } from "@/app/auth";
 | 
			
		||||
import { DevicesTable } from "@/components/devices-table";
 | 
			
		||||
import Search from "@/components/search";
 | 
			
		||||
import AddDeviceDialogForm from "@/components/user/add-device-dialog";
 | 
			
		||||
import { getCurrentUser } from "@/lib/auth-utils";
 | 
			
		||||
import { getServerSession } from "next-auth";
 | 
			
		||||
import React, { Suspense } from "react";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export default async function Devices({
 | 
			
		||||
	searchParams,
 | 
			
		||||
}: {
 | 
			
		||||
@@ -18,14 +16,12 @@ export default async function Devices({
 | 
			
		||||
	}>;
 | 
			
		||||
}) {
 | 
			
		||||
	const query = (await searchParams)?.query || "";
 | 
			
		||||
	const user = await getCurrentUser()
 | 
			
		||||
	const session = await getServerSession(authOptions);
 | 
			
		||||
	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 Devices
 | 
			
		||||
				</h3>
 | 
			
		||||
				<AddDeviceDialogForm user_id={user?.id} />
 | 
			
		||||
				<h3 className="text-sarLinkOrange text-2xl">My Devices</h3>
 | 
			
		||||
				<AddDeviceDialogForm user_id={session?.user?.id} />
 | 
			
		||||
			</div>
 | 
			
		||||
 | 
			
		||||
			<div
 | 
			
		||||
@@ -33,7 +29,6 @@ export default async function Devices({
 | 
			
		||||
				className=" pb-4 gap-4 flex sm:flex-row flex-col items-start justify-start"
 | 
			
		||||
			>
 | 
			
		||||
				<Search />
 | 
			
		||||
 | 
			
		||||
			</div>
 | 
			
		||||
			<Suspense key={query} fallback={"loading...."}>
 | 
			
		||||
				<DevicesTable parentalControl={false} searchParams={searchParams} />
 | 
			
		||||
 
 | 
			
		||||
@@ -1,56 +1,54 @@
 | 
			
		||||
'use client'
 | 
			
		||||
import { Button } from "@/components/ui/button"
 | 
			
		||||
"use client";
 | 
			
		||||
import { Button } from "@/components/ui/button";
 | 
			
		||||
import {
 | 
			
		||||
  Popover,
 | 
			
		||||
  PopoverContent,
 | 
			
		||||
  PopoverTrigger,
 | 
			
		||||
} from "@/components/ui/popover"
 | 
			
		||||
import { authClient } from "@/lib/auth-client";
 | 
			
		||||
import { Loader2, User as UserIcon } from "lucide-react"
 | 
			
		||||
import { useRouter } from "next/navigation"
 | 
			
		||||
import { useState } from "react"
 | 
			
		||||
	Popover,
 | 
			
		||||
	PopoverContent,
 | 
			
		||||
	PopoverTrigger,
 | 
			
		||||
} from "@/components/ui/popover";
 | 
			
		||||
import { Loader2, User as UserIcon } from "lucide-react";
 | 
			
		||||
import { signOut, useSession } from "next-auth/react";
 | 
			
		||||
import { useRouter } from "next/navigation";
 | 
			
		||||
import { useState } from "react";
 | 
			
		||||
 | 
			
		||||
export function AccountPopover() {
 | 
			
		||||
  const session = authClient.useSession();
 | 
			
		||||
  const [loading, setLoading] = useState(false)
 | 
			
		||||
  const router = useRouter()
 | 
			
		||||
	const session = useSession();
 | 
			
		||||
	const [loading, setLoading] = useState(false);
 | 
			
		||||
	const router = useRouter();
 | 
			
		||||
 | 
			
		||||
  if (session.isPending) {
 | 
			
		||||
    <Button variant={"outline"} disabled>
 | 
			
		||||
      <Loader2 className="animate-spin" />
 | 
			
		||||
    </Button>
 | 
			
		||||
  }
 | 
			
		||||
  return (
 | 
			
		||||
 | 
			
		||||
    <Popover>
 | 
			
		||||
      <PopoverTrigger asChild>
 | 
			
		||||
        <Button className="w-fit px-2" variant="outline">
 | 
			
		||||
          <UserIcon />
 | 
			
		||||
        </Button>
 | 
			
		||||
      </PopoverTrigger>
 | 
			
		||||
      <PopoverContent className="w-fit">
 | 
			
		||||
        <div className="grid gap-4">
 | 
			
		||||
          <div className="space-y-2">
 | 
			
		||||
            <h4 className="font-medium leading-none">{session.data?.user?.name}</h4>
 | 
			
		||||
            <p className="text-sm text-muted-foreground">
 | 
			
		||||
              {session.data?.user?.phoneNumber}
 | 
			
		||||
            </p>
 | 
			
		||||
          </div>
 | 
			
		||||
          <Button disabled={loading} onClick={async () => {
 | 
			
		||||
            setLoading(true)
 | 
			
		||||
            await authClient.signOut({
 | 
			
		||||
              fetchOptions: {
 | 
			
		||||
                onSuccess: () => {
 | 
			
		||||
                  router.push("/login"); // redirect to login page
 | 
			
		||||
                },
 | 
			
		||||
              },
 | 
			
		||||
            })
 | 
			
		||||
            setLoading(false)
 | 
			
		||||
          }}>
 | 
			
		||||
            {loading ? <Loader2 className="animate-spin" /> : "Logout"}
 | 
			
		||||
          </Button>
 | 
			
		||||
        </div>
 | 
			
		||||
      </PopoverContent>
 | 
			
		||||
    </Popover>
 | 
			
		||||
  )
 | 
			
		||||
	if (session.status === "loading") {
 | 
			
		||||
		<Button variant={"outline"} disabled>
 | 
			
		||||
			<Loader2 className="animate-spin" />
 | 
			
		||||
		</Button>;
 | 
			
		||||
	}
 | 
			
		||||
	return (
 | 
			
		||||
		<Popover>
 | 
			
		||||
			<PopoverTrigger asChild>
 | 
			
		||||
				<Button className="w-fit px-2" variant="outline">
 | 
			
		||||
					<UserIcon />
 | 
			
		||||
				</Button>
 | 
			
		||||
			</PopoverTrigger>
 | 
			
		||||
			<PopoverContent className="w-fit">
 | 
			
		||||
				<div className="grid gap-4">
 | 
			
		||||
					<div className="space-y-2">
 | 
			
		||||
						<h4 className="font-medium leading-none">
 | 
			
		||||
							{session.data?.user?.name}
 | 
			
		||||
						</h4>
 | 
			
		||||
						<p className="text-sm text-muted-foreground">
 | 
			
		||||
							{session.data?.user?.phoneNumber}
 | 
			
		||||
						</p>
 | 
			
		||||
					</div>
 | 
			
		||||
					<Button
 | 
			
		||||
						disabled={loading}
 | 
			
		||||
						onClick={async () => {
 | 
			
		||||
							setLoading(true);
 | 
			
		||||
							await signOut();
 | 
			
		||||
							setLoading(false);
 | 
			
		||||
						}}
 | 
			
		||||
					>
 | 
			
		||||
						{loading ? <Loader2 className="animate-spin" /> : "Logout"}
 | 
			
		||||
					</Button>
 | 
			
		||||
				</div>
 | 
			
		||||
			</PopoverContent>
 | 
			
		||||
		</Popover>
 | 
			
		||||
	);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -4,33 +4,26 @@ import { Wallet } from "@/components/wallet";
 | 
			
		||||
import { ModeToggle } from "@/components/theme-toggle";
 | 
			
		||||
import { AppSidebar } from "@/components/ui/app-sidebar";
 | 
			
		||||
 | 
			
		||||
import { authOptions } from "@/app/auth";
 | 
			
		||||
import { Separator } from "@/components/ui/separator";
 | 
			
		||||
import {
 | 
			
		||||
	SidebarInset,
 | 
			
		||||
	SidebarProvider,
 | 
			
		||||
	SidebarTrigger,
 | 
			
		||||
} from "@/components/ui/sidebar";
 | 
			
		||||
import { auth } from "@/app/auth";
 | 
			
		||||
import prisma from "@/lib/db";
 | 
			
		||||
import { getServerSession } from "next-auth";
 | 
			
		||||
import { headers } from "next/headers";
 | 
			
		||||
import { AccountPopover } from "./account-popver";
 | 
			
		||||
 | 
			
		||||
export async function ApplicationLayout({
 | 
			
		||||
	children,
 | 
			
		||||
}: { children: React.ReactNode }) {
 | 
			
		||||
	const session = await auth.api.getSession({
 | 
			
		||||
		headers: await headers(),
 | 
			
		||||
	});
 | 
			
		||||
	const billFormula = await prisma.billFormula.findFirst();
 | 
			
		||||
	const user = await prisma.user.findFirst({
 | 
			
		||||
		where: {
 | 
			
		||||
			id: session?.user?.id,
 | 
			
		||||
		},
 | 
			
		||||
	});
 | 
			
		||||
	const session = await getServerSession(authOptions);
 | 
			
		||||
 | 
			
		||||
	return (
 | 
			
		||||
		<SidebarProvider>
 | 
			
		||||
			<AppSidebar role={session?.user?.role || "USER"} />
 | 
			
		||||
			<DeviceCartDrawer billFormula={billFormula || null} />
 | 
			
		||||
			<AppSidebar role={"admin"} />
 | 
			
		||||
			{/* <DeviceCartDrawer billFormula={billFormula || null} /> */}
 | 
			
		||||
			<SidebarInset>
 | 
			
		||||
				<header className="flex justify-between sticky top-0 bg-background h-16 shrink-0 items-center gap-2 border-b px-4 z-10">
 | 
			
		||||
					<div className="flex items-center gap-2 ">
 | 
			
		||||
@@ -44,7 +37,7 @@ export async function ApplicationLayout({
 | 
			
		||||
					</div>
 | 
			
		||||
 | 
			
		||||
					<div className="flex items-center gap-2">
 | 
			
		||||
						<Wallet walletBalance={user?.walletBalance || 0} />
 | 
			
		||||
						{/* <Wallet walletBalance={user?.walletBalance || 0} /> */}
 | 
			
		||||
						<ModeToggle />
 | 
			
		||||
						<AccountPopover />
 | 
			
		||||
					</div>
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,6 @@
 | 
			
		||||
import { Button } from "@/components/ui/button";
 | 
			
		||||
import { Input } from "@/components/ui/input";
 | 
			
		||||
import { Label } from "@/components/ui/label";
 | 
			
		||||
import { authClient } from "@/lib/auth-client";
 | 
			
		||||
import { zodResolver } from "@hookform/resolvers/zod";
 | 
			
		||||
import { Loader2 } from "lucide-react";
 | 
			
		||||
import Link from "next/link";
 | 
			
		||||
@@ -37,16 +36,16 @@ export default function VerifyOTPForm({
 | 
			
		||||
 | 
			
		||||
	const onSubmit: SubmitHandler<z.infer<typeof OTPSchema>> = (data) => {
 | 
			
		||||
		startTransition(async () => {
 | 
			
		||||
			const isVerified = await authClient.phoneNumber.verify({
 | 
			
		||||
				phoneNumber: phone_number,
 | 
			
		||||
				code: data.pin,
 | 
			
		||||
			});
 | 
			
		||||
			console.log({ isVerified });
 | 
			
		||||
			if (!isVerified.error) {
 | 
			
		||||
				router.push("/devices");
 | 
			
		||||
			} else {
 | 
			
		||||
				toast.error(isVerified.error.message);
 | 
			
		||||
			}
 | 
			
		||||
			// const isVerified = await authClient.phoneNumber.verify({
 | 
			
		||||
			// 	phoneNumber: phone_number,
 | 
			
		||||
			// 	code: data.pin,
 | 
			
		||||
			// });
 | 
			
		||||
			// console.log({ isVerified });
 | 
			
		||||
			// if (!isVerified.error) {
 | 
			
		||||
			// 	router.push("/devices");
 | 
			
		||||
			// } else {
 | 
			
		||||
			// 	toast.error(isVerified.error.message);
 | 
			
		||||
			// }
 | 
			
		||||
		});
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
@@ -70,11 +69,7 @@ export default function VerifyOTPForm({
 | 
			
		||||
						<p className="text-red-500 text-sm">{errors.pin.message}</p>
 | 
			
		||||
					)}
 | 
			
		||||
				</div>
 | 
			
		||||
				<Button
 | 
			
		||||
					className="w-full"
 | 
			
		||||
					disabled={isPending}
 | 
			
		||||
					type="submit"
 | 
			
		||||
				>
 | 
			
		||||
				<Button className="w-full" disabled={isPending} type="submit">
 | 
			
		||||
					{isPending ? <Loader2 className="animate-spin" /> : "Login"}
 | 
			
		||||
				</Button>
 | 
			
		||||
			</div>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,4 @@
 | 
			
		||||
import { authOptions } from "@/app/auth";
 | 
			
		||||
import {
 | 
			
		||||
	Table,
 | 
			
		||||
	TableBody,
 | 
			
		||||
@@ -8,9 +9,7 @@ import {
 | 
			
		||||
	TableHeader,
 | 
			
		||||
	TableRow,
 | 
			
		||||
} from "@/components/ui/table";
 | 
			
		||||
import { auth } from "@/app/auth";
 | 
			
		||||
import prisma from "@/lib/db";
 | 
			
		||||
import { headers } from "next/headers";
 | 
			
		||||
import { getServerSession } from "next-auth";
 | 
			
		||||
import ClickableRow from "./clickable-row";
 | 
			
		||||
import DeviceCard from "./device-card";
 | 
			
		||||
import Pagination from "./pagination";
 | 
			
		||||
@@ -26,85 +25,84 @@ export async function DevicesTable({
 | 
			
		||||
	}>;
 | 
			
		||||
	parentalControl?: boolean;
 | 
			
		||||
}) {
 | 
			
		||||
	const session = await auth.api.getSession({
 | 
			
		||||
		headers: await headers(),
 | 
			
		||||
	});
 | 
			
		||||
	const isAdmin = session?.user.role === "ADMIN";
 | 
			
		||||
	const session = await getServerSession(authOptions);
 | 
			
		||||
	const isAdmin = session?.user;
 | 
			
		||||
	const query = (await searchParams)?.query || "";
 | 
			
		||||
	const page = (await searchParams)?.page;
 | 
			
		||||
	const sortBy = (await searchParams)?.sortBy || "asc";
 | 
			
		||||
	const totalDevices = await prisma.device.count({
 | 
			
		||||
		where: {
 | 
			
		||||
			userId: isAdmin ? undefined : session?.session.userId,
 | 
			
		||||
			OR: [
 | 
			
		||||
				{
 | 
			
		||||
					name: {
 | 
			
		||||
						contains: query || "",
 | 
			
		||||
						mode: "insensitive",
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					mac: {
 | 
			
		||||
						contains: query || "",
 | 
			
		||||
						mode: "insensitive",
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			],
 | 
			
		||||
			NOT: {
 | 
			
		||||
				payments: {
 | 
			
		||||
					some: {
 | 
			
		||||
						paid: false,
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			isActive: isAdmin ? undefined : parentalControl,
 | 
			
		||||
			blocked: isAdmin
 | 
			
		||||
				? undefined
 | 
			
		||||
				: parentalControl !== undefined
 | 
			
		||||
					? undefined
 | 
			
		||||
					: false,
 | 
			
		||||
		},
 | 
			
		||||
	});
 | 
			
		||||
	// const totalDevices = await prisma.device.count({
 | 
			
		||||
	// 	where: {
 | 
			
		||||
	// 		userId: isAdmin ? undefined : session?.session.userId,
 | 
			
		||||
	// 		OR: [
 | 
			
		||||
	// 			{
 | 
			
		||||
	// 				name: {
 | 
			
		||||
	// 					contains: query || "",
 | 
			
		||||
	// 					mode: "insensitive",
 | 
			
		||||
	// 				},
 | 
			
		||||
	// 			},
 | 
			
		||||
	// 			{
 | 
			
		||||
	// 				mac: {
 | 
			
		||||
	// 					contains: query || "",
 | 
			
		||||
	// 					mode: "insensitive",
 | 
			
		||||
	// 				},
 | 
			
		||||
	// 			},
 | 
			
		||||
	// 		],
 | 
			
		||||
	// 		NOT: {
 | 
			
		||||
	// 			payments: {
 | 
			
		||||
	// 				some: {
 | 
			
		||||
	// 					paid: false,
 | 
			
		||||
	// 				},
 | 
			
		||||
	// 			},
 | 
			
		||||
	// 		},
 | 
			
		||||
	// 		isActive: isAdmin ? undefined : parentalControl,
 | 
			
		||||
	// 		blocked: isAdmin
 | 
			
		||||
	// 			? undefined
 | 
			
		||||
	// 			: parentalControl !== undefined
 | 
			
		||||
	// 				? undefined
 | 
			
		||||
	// 				: false,
 | 
			
		||||
	// 	},
 | 
			
		||||
	// });
 | 
			
		||||
 | 
			
		||||
	const totalPages = Math.ceil(totalDevices / 10);
 | 
			
		||||
	// const totalPages = Math.ceil(totalDevices / 10);
 | 
			
		||||
	const limit = 10;
 | 
			
		||||
	const offset = (Number(page) - 1) * limit || 0;
 | 
			
		||||
 | 
			
		||||
	const devices = await prisma.device.findMany({
 | 
			
		||||
		where: {
 | 
			
		||||
			userId: session?.session.userId,
 | 
			
		||||
			OR: [
 | 
			
		||||
				{
 | 
			
		||||
					name: {
 | 
			
		||||
						contains: query || "",
 | 
			
		||||
						mode: "insensitive",
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					mac: {
 | 
			
		||||
						contains: query || "",
 | 
			
		||||
						mode: "insensitive",
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			],
 | 
			
		||||
			NOT: {
 | 
			
		||||
				payments: {
 | 
			
		||||
					some: {
 | 
			
		||||
						paid: false,
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			isActive: parentalControl,
 | 
			
		||||
			blocked: parentalControl !== undefined ? undefined : false,
 | 
			
		||||
		},
 | 
			
		||||
	// const devices = await prisma.device.findMany({
 | 
			
		||||
	// 	where: {
 | 
			
		||||
	// 		userId: session?.session.userId,
 | 
			
		||||
	// 		OR: [
 | 
			
		||||
	// 			{
 | 
			
		||||
	// 				name: {
 | 
			
		||||
	// 					contains: query || "",
 | 
			
		||||
	// 					mode: "insensitive",
 | 
			
		||||
	// 				},
 | 
			
		||||
	// 			},
 | 
			
		||||
	// 			{
 | 
			
		||||
	// 				mac: {
 | 
			
		||||
	// 					contains: query || "",
 | 
			
		||||
	// 					mode: "insensitive",
 | 
			
		||||
	// 				},
 | 
			
		||||
	// 			},
 | 
			
		||||
	// 		],
 | 
			
		||||
	// 		NOT: {
 | 
			
		||||
	// 			payments: {
 | 
			
		||||
	// 				some: {
 | 
			
		||||
	// 					paid: false,
 | 
			
		||||
	// 				},
 | 
			
		||||
	// 			},
 | 
			
		||||
	// 		},
 | 
			
		||||
	// 		isActive: parentalControl,
 | 
			
		||||
	// 		blocked: parentalControl !== undefined ? undefined : false,
 | 
			
		||||
	// 	},
 | 
			
		||||
 | 
			
		||||
		skip: offset,
 | 
			
		||||
		take: limit,
 | 
			
		||||
		orderBy: {
 | 
			
		||||
			name: `${sortBy}` as "asc" | "desc",
 | 
			
		||||
		},
 | 
			
		||||
	});
 | 
			
		||||
	// 	skip: offset,
 | 
			
		||||
	// 	take: limit,
 | 
			
		||||
	// 	orderBy: {
 | 
			
		||||
	// 		name: `${sortBy}` as "asc" | "desc",
 | 
			
		||||
	// 	},
 | 
			
		||||
	// });
 | 
			
		||||
 | 
			
		||||
	return null;
 | 
			
		||||
	return (
 | 
			
		||||
		<div>
 | 
			
		||||
			{devices.length === 0 ? (
 | 
			
		||||
 
 | 
			
		||||
@@ -2,123 +2,119 @@
 | 
			
		||||
 | 
			
		||||
import { Button } from "@/components/ui/button";
 | 
			
		||||
import {
 | 
			
		||||
  Drawer,
 | 
			
		||||
  DrawerClose,
 | 
			
		||||
  DrawerContent,
 | 
			
		||||
  DrawerDescription,
 | 
			
		||||
  DrawerFooter,
 | 
			
		||||
  DrawerHeader,
 | 
			
		||||
  DrawerTitle,
 | 
			
		||||
  DrawerTrigger,
 | 
			
		||||
	Drawer,
 | 
			
		||||
	DrawerClose,
 | 
			
		||||
	DrawerContent,
 | 
			
		||||
	DrawerDescription,
 | 
			
		||||
	DrawerFooter,
 | 
			
		||||
	DrawerHeader,
 | 
			
		||||
	DrawerTitle,
 | 
			
		||||
	DrawerTrigger,
 | 
			
		||||
} from "@/components/ui/drawer";
 | 
			
		||||
import {
 | 
			
		||||
  WalletDrawerOpenAtom,
 | 
			
		||||
  walletTopUpValue,
 | 
			
		||||
} from "@/lib/atoms";
 | 
			
		||||
import { authClient } from "@/lib/auth-client";
 | 
			
		||||
import { WalletDrawerOpenAtom, walletTopUpValue } from "@/lib/atoms";
 | 
			
		||||
import type { TopupType } from "@/lib/types";
 | 
			
		||||
import { useAtom, } from "jotai";
 | 
			
		||||
import {
 | 
			
		||||
  CircleDollarSign,
 | 
			
		||||
  Loader2,
 | 
			
		||||
  Wallet2,
 | 
			
		||||
} from "lucide-react";
 | 
			
		||||
import { usePathname, } from "next/navigation";
 | 
			
		||||
import { useAtom } from "jotai";
 | 
			
		||||
import { CircleDollarSign, Loader2, Wallet2 } from "lucide-react";
 | 
			
		||||
import { useSession } from "next-auth/react";
 | 
			
		||||
import { usePathname } from "next/navigation";
 | 
			
		||||
import { useState } from "react";
 | 
			
		||||
import NumberInput from "./number-input";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export function Wallet({
 | 
			
		||||
  walletBalance,
 | 
			
		||||
	walletBalance,
 | 
			
		||||
}: {
 | 
			
		||||
  walletBalance: number;
 | 
			
		||||
	walletBalance: number;
 | 
			
		||||
}) {
 | 
			
		||||
  const session = authClient.useSession();
 | 
			
		||||
  const pathname = usePathname();
 | 
			
		||||
  const [amount, setAmount] = useAtom(walletTopUpValue);
 | 
			
		||||
  const [isOpen, setIsOpen] = useAtom(WalletDrawerOpenAtom);
 | 
			
		||||
  const [disabled, setDisabled] = useState(false);
 | 
			
		||||
  // const router = useRouter();
 | 
			
		||||
	const session = useSession();
 | 
			
		||||
	const pathname = usePathname();
 | 
			
		||||
	const [amount, setAmount] = useAtom(walletTopUpValue);
 | 
			
		||||
	const [isOpen, setIsOpen] = useAtom(WalletDrawerOpenAtom);
 | 
			
		||||
	const [disabled, setDisabled] = useState(false);
 | 
			
		||||
	// const router = useRouter();
 | 
			
		||||
 | 
			
		||||
  if (pathname === "/payment") {
 | 
			
		||||
    return null;
 | 
			
		||||
  }
 | 
			
		||||
	if (pathname === "/payment") {
 | 
			
		||||
		return null;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
  const data: TopupType = {
 | 
			
		||||
    userId: session?.data?.user.id ?? "",
 | 
			
		||||
    amount: Number.parseFloat(amount.toFixed(2)),
 | 
			
		||||
    paid: false,
 | 
			
		||||
  };
 | 
			
		||||
	const data: TopupType = {
 | 
			
		||||
		userId: session?.data?.user.id ?? "",
 | 
			
		||||
		amount: Number.parseFloat(amount.toFixed(2)),
 | 
			
		||||
		paid: false,
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <Drawer open={isOpen} onOpenChange={setIsOpen}>
 | 
			
		||||
      <DrawerTrigger asChild>
 | 
			
		||||
        <Button onClick={() => setIsOpen(!isOpen)} variant="outline">
 | 
			
		||||
          {new Intl.NumberFormat('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(walletBalance)} MVR
 | 
			
		||||
          <Wallet2 />
 | 
			
		||||
        </Button>
 | 
			
		||||
      </DrawerTrigger>
 | 
			
		||||
      <DrawerContent>
 | 
			
		||||
        <div className="mx-auto w-full max-w-sm">
 | 
			
		||||
          <DrawerHeader>
 | 
			
		||||
            <DrawerTitle>Wallet</DrawerTitle>
 | 
			
		||||
            <DrawerDescription asChild>
 | 
			
		||||
              <div>
 | 
			
		||||
                Your wallet balance is{" "}
 | 
			
		||||
                <span className="font-semibold">
 | 
			
		||||
                  {new Intl.NumberFormat('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(walletBalance)}
 | 
			
		||||
                </span>{" "}
 | 
			
		||||
              </div>
 | 
			
		||||
            </DrawerDescription>
 | 
			
		||||
          </DrawerHeader>
 | 
			
		||||
	return (
 | 
			
		||||
		<Drawer open={isOpen} onOpenChange={setIsOpen}>
 | 
			
		||||
			<DrawerTrigger asChild>
 | 
			
		||||
				<Button onClick={() => setIsOpen(!isOpen)} variant="outline">
 | 
			
		||||
					{new Intl.NumberFormat("en-US", {
 | 
			
		||||
						minimumFractionDigits: 2,
 | 
			
		||||
						maximumFractionDigits: 2,
 | 
			
		||||
					}).format(walletBalance)}{" "}
 | 
			
		||||
					MVR
 | 
			
		||||
					<Wallet2 />
 | 
			
		||||
				</Button>
 | 
			
		||||
			</DrawerTrigger>
 | 
			
		||||
			<DrawerContent>
 | 
			
		||||
				<div className="mx-auto w-full max-w-sm">
 | 
			
		||||
					<DrawerHeader>
 | 
			
		||||
						<DrawerTitle>Wallet</DrawerTitle>
 | 
			
		||||
						<DrawerDescription asChild>
 | 
			
		||||
							<div>
 | 
			
		||||
								Your wallet balance is{" "}
 | 
			
		||||
								<span className="font-semibold">
 | 
			
		||||
									{new Intl.NumberFormat("en-US", {
 | 
			
		||||
										minimumFractionDigits: 2,
 | 
			
		||||
										maximumFractionDigits: 2,
 | 
			
		||||
									}).format(walletBalance)}
 | 
			
		||||
								</span>{" "}
 | 
			
		||||
							</div>
 | 
			
		||||
						</DrawerDescription>
 | 
			
		||||
					</DrawerHeader>
 | 
			
		||||
 | 
			
		||||
          <div className="px-4 flex flex-col gap-4">
 | 
			
		||||
            <NumberInput
 | 
			
		||||
              label="Set amount to top up"
 | 
			
		||||
              value={amount}
 | 
			
		||||
              onChange={(value) => setAmount(value)}
 | 
			
		||||
              maxAllowed={5000}
 | 
			
		||||
              isDisabled={amount === 0}
 | 
			
		||||
            />
 | 
			
		||||
 | 
			
		||||
          </div>
 | 
			
		||||
          <DrawerFooter>
 | 
			
		||||
            <Button
 | 
			
		||||
              onClick={async () => {
 | 
			
		||||
                console.log(data)
 | 
			
		||||
                setDisabled(true)
 | 
			
		||||
                // const payment = await createPayment(data)
 | 
			
		||||
                setDisabled(false)
 | 
			
		||||
                // setMonths(1)
 | 
			
		||||
                // if (payment) {
 | 
			
		||||
                // router.push(`/payments/${payment.id}`);
 | 
			
		||||
                // setIsOpen(!isOpen);
 | 
			
		||||
                // } else {
 | 
			
		||||
                // toast.error("Something went wrong.")
 | 
			
		||||
                // }
 | 
			
		||||
              }}
 | 
			
		||||
              className="w-full"
 | 
			
		||||
              disabled={amount === 0 || disabled}
 | 
			
		||||
            >
 | 
			
		||||
              {disabled ? (
 | 
			
		||||
                <>
 | 
			
		||||
                  <Loader2 className="ml-2 animate-spin" />
 | 
			
		||||
                </>
 | 
			
		||||
              ) : (
 | 
			
		||||
                <>
 | 
			
		||||
                  Go to payment
 | 
			
		||||
                  <CircleDollarSign />
 | 
			
		||||
                </>
 | 
			
		||||
              )}
 | 
			
		||||
            </Button>
 | 
			
		||||
            <DrawerClose asChild>
 | 
			
		||||
              <Button variant="outline">Cancel</Button>
 | 
			
		||||
            </DrawerClose>
 | 
			
		||||
          </DrawerFooter>
 | 
			
		||||
        </div>
 | 
			
		||||
      </DrawerContent>
 | 
			
		||||
    </Drawer>
 | 
			
		||||
  );
 | 
			
		||||
					<div className="px-4 flex flex-col gap-4">
 | 
			
		||||
						<NumberInput
 | 
			
		||||
							label="Set amount to top up"
 | 
			
		||||
							value={amount}
 | 
			
		||||
							onChange={(value) => setAmount(value)}
 | 
			
		||||
							maxAllowed={5000}
 | 
			
		||||
							isDisabled={amount === 0}
 | 
			
		||||
						/>
 | 
			
		||||
					</div>
 | 
			
		||||
					<DrawerFooter>
 | 
			
		||||
						<Button
 | 
			
		||||
							onClick={async () => {
 | 
			
		||||
								console.log(data);
 | 
			
		||||
								setDisabled(true);
 | 
			
		||||
								// const payment = await createPayment(data)
 | 
			
		||||
								setDisabled(false);
 | 
			
		||||
								// setMonths(1)
 | 
			
		||||
								// if (payment) {
 | 
			
		||||
								// router.push(`/payments/${payment.id}`);
 | 
			
		||||
								// setIsOpen(!isOpen);
 | 
			
		||||
								// } else {
 | 
			
		||||
								// toast.error("Something went wrong.")
 | 
			
		||||
								// }
 | 
			
		||||
							}}
 | 
			
		||||
							className="w-full"
 | 
			
		||||
							disabled={amount === 0 || disabled}
 | 
			
		||||
						>
 | 
			
		||||
							{disabled ? (
 | 
			
		||||
								<>
 | 
			
		||||
									<Loader2 className="ml-2 animate-spin" />
 | 
			
		||||
								</>
 | 
			
		||||
							) : (
 | 
			
		||||
								<>
 | 
			
		||||
									Go to payment
 | 
			
		||||
									<CircleDollarSign />
 | 
			
		||||
								</>
 | 
			
		||||
							)}
 | 
			
		||||
						</Button>
 | 
			
		||||
						<DrawerClose asChild>
 | 
			
		||||
							<Button variant="outline">Cancel</Button>
 | 
			
		||||
						</DrawerClose>
 | 
			
		||||
					</DrawerFooter>
 | 
			
		||||
				</div>
 | 
			
		||||
			</DrawerContent>
 | 
			
		||||
		</Drawer>
 | 
			
		||||
	);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,15 +0,0 @@
 | 
			
		||||
"use server";
 | 
			
		||||
import { headers } from "next/headers";
 | 
			
		||||
import { cache } from "react";
 | 
			
		||||
import { auth } from "../app/auth";
 | 
			
		||||
 | 
			
		||||
const getCurrentUserCache = cache(async () => {
 | 
			
		||||
	const session = await auth.api.getSession({
 | 
			
		||||
		headers: await headers(),
 | 
			
		||||
	});
 | 
			
		||||
	return session?.user;
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export async function getCurrentUser() {
 | 
			
		||||
	return await getCurrentUserCache();
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user