mirror of
https://github.com/i701/sarlink-portal.git
synced 2025-02-21 18:22:00 +00:00
Refactor authentication middleware to use native fetch, update dependencies, and enhance error handling. Add new error boundary component for dashboard and improve user verification UI. Update payment handling and device management components for better user experience. Adjust CSS for error backgrounds and refine input read-only components with validation indicators.
Some checks failed
Build and Push Docker Images / Build and Push Docker Images (push) Failing after 3m9s
Some checks failed
Build and Push Docker Images / Build and Push Docker Images (push) Failing after 3m9s
This commit is contained in:
parent
d14b0b35a3
commit
8ffabb1fcb
@ -138,15 +138,15 @@ export async function signup(_actionState: ActionState, formData: FormData) {
|
|||||||
details: `
|
details: `
|
||||||
A new user has requested for verification. \n
|
A new user has requested for verification. \n
|
||||||
USER DETAILS:
|
USER DETAILS:
|
||||||
Name: ${parsedData.data.name}
|
Name: ${parsedData.data.name}
|
||||||
Address: ${parsedData.data.address}
|
Address: ${parsedData.data.address}
|
||||||
ID Card: ${parsedData.data.id_card}
|
ID Card: ${parsedData.data.id_card}
|
||||||
DOB: ${parsedData.data.dob.toLocaleDateString("en-US", {
|
DOB: ${parsedData.data.dob.toLocaleDateString("en-US", {
|
||||||
month: "short",
|
month: "short",
|
||||||
day: "2-digit",
|
day: "2-digit",
|
||||||
year: "numeric",
|
year: "numeric",
|
||||||
})}
|
})}
|
||||||
ACC No: ${parsedData.data.accNo}\n\nVerify the user with the folloiwing link: ${process.env.BETTER_AUTH_URL}/users/${newUser.id}/verify
|
ACC No: ${parsedData.data.accNo}\n\nVerify the user with the following link: ${process.env.BETTER_AUTH_URL}/users/${newUser.id}/verify
|
||||||
`,
|
`,
|
||||||
phoneNumber: process.env.ADMIN_PHONENUMBER ?? "",
|
phoneNumber: process.env.ADMIN_PHONENUMBER ?? "",
|
||||||
});
|
});
|
||||||
|
@ -9,28 +9,23 @@ import { redirect } from "next/navigation";
|
|||||||
import { addDevicesToGroup } from "./omada-actions";
|
import { addDevicesToGroup } from "./omada-actions";
|
||||||
|
|
||||||
export async function createPayment(data: PaymentType) {
|
export async function createPayment(data: PaymentType) {
|
||||||
try {
|
console.log("data", data);
|
||||||
console.log("data", data);
|
const payment = await prisma.payment.create({
|
||||||
const payment = await prisma.payment.create({
|
data: {
|
||||||
data: {
|
amount: data.amount,
|
||||||
amount: data.amount,
|
numberOfMonths: data.numberOfMonths,
|
||||||
numberOfMonths: data.numberOfMonths,
|
paid: data.paid,
|
||||||
paid: data.paid,
|
userId: data.userId,
|
||||||
userId: data.userId,
|
devices: {
|
||||||
devices: {
|
connect: data.deviceIds.map((id) => {
|
||||||
connect: data.deviceIds.map((id) => {
|
return {
|
||||||
return {
|
id,
|
||||||
id,
|
};
|
||||||
};
|
}),
|
||||||
}),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
});
|
},
|
||||||
return { success: true, paymentId: payment.id };
|
});
|
||||||
} catch (error) {
|
redirect(`/payments/${payment.id}`);
|
||||||
console.error("Error creating payment:", error);
|
|
||||||
return { success: false, error: "Failed to create payment" };
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type VerifyPaymentType = {
|
type VerifyPaymentType = {
|
||||||
|
@ -70,15 +70,16 @@ export async function Rejectuser({
|
|||||||
if (!user) {
|
if (!user) {
|
||||||
throw new Error("User not found");
|
throw new Error("User not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await SendUserRejectionDetailSMS({
|
||||||
|
details: reason,
|
||||||
|
phoneNumber: user.phoneNumber,
|
||||||
|
});
|
||||||
await prisma.user.delete({
|
await prisma.user.delete({
|
||||||
where: {
|
where: {
|
||||||
id: userId,
|
id: userId,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
await SendUserRejectionDetailSMS({
|
|
||||||
details: reason,
|
|
||||||
phoneNumber: user.phoneNumber,
|
|
||||||
});
|
|
||||||
revalidatePath("/users");
|
revalidatePath("/users");
|
||||||
redirect("/users");
|
redirect("/users");
|
||||||
}
|
}
|
||||||
@ -90,21 +91,25 @@ export const SendUserRejectionDetailSMS = async ({
|
|||||||
details: string;
|
details: string;
|
||||||
phoneNumber: string;
|
phoneNumber: string;
|
||||||
}) => {
|
}) => {
|
||||||
const respose = await fetch(`${process.env.SMS_API_BASE_URL}/api/sms`, {
|
try {
|
||||||
method: "POST",
|
const respose = await fetch(`${process.env.SMS_API_BASE_URL}/api/sms`, {
|
||||||
headers: {
|
method: "POST",
|
||||||
"Content-Type": "application/json",
|
headers: {
|
||||||
Authorization: `Bearer ${process.env.SMS_API_KEY}`,
|
"Content-Type": "application/json",
|
||||||
},
|
Authorization: `Bearer ${process.env.SMS_API_KEY}`,
|
||||||
body: JSON.stringify({
|
},
|
||||||
check_delivery: false,
|
body: JSON.stringify({
|
||||||
number: phoneNumber,
|
check_delivery: false,
|
||||||
message: details,
|
number: phoneNumber,
|
||||||
}),
|
message: details,
|
||||||
});
|
}),
|
||||||
const data = await respose.json();
|
});
|
||||||
console.log(data);
|
const data = await respose.json();
|
||||||
return data;
|
console.log(data);
|
||||||
|
return data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function AddDevice({
|
export async function AddDevice({
|
||||||
|
12
app/(dashboard)/devices-to-pay/page.tsx
Normal file
12
app/(dashboard)/devices-to-pay/page.tsx
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import DevicesForPayment from '@/components/devices-for-payment'
|
||||||
|
import prisma from '@/lib/db';
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
export default async function DevicesToPay() {
|
||||||
|
const billFormula = await prisma.billFormula.findFirst();
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<DevicesForPayment billFormula={billFormula ?? undefined} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
36
app/(dashboard)/error.tsx
Normal file
36
app/(dashboard)/error.tsx
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
'use client' // Error boundaries must be Client Components
|
||||||
|
|
||||||
|
import { Button } from '@/components/ui/button'
|
||||||
|
import { TriangleAlert } from "lucide-react";
|
||||||
|
import { useEffect } from 'react'
|
||||||
|
export default function DashboardError({
|
||||||
|
error,
|
||||||
|
reset,
|
||||||
|
}: {
|
||||||
|
error: Error & { digest?: string }
|
||||||
|
reset: () => void
|
||||||
|
}) {
|
||||||
|
useEffect(() => {
|
||||||
|
// Log the error to an error reporting service
|
||||||
|
console.error(error)
|
||||||
|
}, [error])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='error-bg dark:error-bg-dark rounded-lg p-4 h-full flex flex-col gap-4 items-center justify-center'>
|
||||||
|
<div className='bg-white dark:bg-transparent p-6 rounded flex flex-col items-center justify-center gap-4'>
|
||||||
|
<TriangleAlert color='red' />
|
||||||
|
<h2 className='text-red-500 text-xl font-semibold'>Something went wrong!</h2>
|
||||||
|
<Button
|
||||||
|
variant={"destructive"}
|
||||||
|
size={"lg"}
|
||||||
|
onClick={
|
||||||
|
// Attempt to recover by trying to re-render the segment
|
||||||
|
() => reset()
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Try again
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
@ -29,12 +29,15 @@ export default async function VerifyUserPage({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const nationalData = await getNationalPerson({ idCard: dbUser?.id_card ?? "" })
|
const nationalData = await getNationalPerson({ idCard: dbUser?.id_card ?? "" })
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className='flex items-center justify-between text-gray-500 text-2xl font-bold title-bg py-4 px-2 mb-4'>
|
<div className='flex items-center justify-between text-gray-500 text-2xl font-bold title-bg py-4 px-2 mb-4'>
|
||||||
<h3 className="">
|
<h3 className="text-sarLinkOrange text-2xl">
|
||||||
Verify user
|
Verify user
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<div className='flex gap-2'>
|
<div className='flex gap-2'>
|
||||||
{dbUser && !dbUser?.verified && <UserVerifyDialog user={dbUser} />}
|
{dbUser && !dbUser?.verified && <UserVerifyDialog user={dbUser} />}
|
||||||
{dbUser && !dbUser?.verified && <UserRejectDialog user={dbUser} />}
|
{dbUser && !dbUser?.verified && <UserRejectDialog user={dbUser} />}
|
||||||
@ -46,44 +49,43 @@ export default async function VerifyUserPage({
|
|||||||
<div id="database-information">
|
<div id="database-information">
|
||||||
<h4 className='p-2 rounded font-semibold'>Database Information</h4>
|
<h4 className='p-2 rounded font-semibold'>Database Information</h4>
|
||||||
<div className='shadow p-2 rounded-lg title-bg space-y-1 my-2 grid grid-cols-1 md:grid-cols-2 gap-2'>
|
<div className='shadow p-2 rounded-lg title-bg space-y-1 my-2 grid grid-cols-1 md:grid-cols-2 gap-2'>
|
||||||
<InputReadOnly labelClassName='text-sarLinkOrange' label="ID Card" value={dbUser?.id_card ?? ""} />
|
<InputReadOnly showCheck checkTrue={dbUser?.id_card === nationalData.nic} labelClassName='text-sarLinkOrange' label="ID Card" value={dbUser?.id_card ?? ""} />
|
||||||
<InputReadOnly labelClassName='text-sarLinkOrange' label="Name" value={dbUser?.name ?? ""} />
|
<InputReadOnly showCheck checkTrue={dbUser?.name === nationalData.name_en} labelClassName='text-sarLinkOrange' label="Name" value={dbUser?.name ?? ""} />
|
||||||
<InputReadOnly labelClassName='text-sarLinkOrange' label="House Name" value={dbUser?.address ?? ""} />
|
<InputReadOnly showCheck checkTrue={dbUser?.address === nationalData.house_name_en} labelClassName='text-sarLinkOrange' label="House Name" value={dbUser?.address ?? ""} />
|
||||||
<InputReadOnly labelClassName='text-sarLinkOrange' label="Island" value={dbUser?.island?.name ?? ""} />
|
<InputReadOnly showCheck checkTrue={dbUser?.island?.name === nationalData.island_name_en} labelClassName='text-sarLinkOrange' label="Island" value={dbUser?.island?.name ?? ""} />
|
||||||
<InputReadOnly labelClassName='text-sarLinkOrange' label="Atoll" value={dbUser?.island?.atoll.name ?? ""} />
|
<InputReadOnly showCheck checkTrue={dbUser?.island?.atoll.name === nationalData.atoll_en} labelClassName='text-sarLinkOrange' label="Atoll" value={dbUser?.island?.atoll.name ?? ""} />
|
||||||
|
|
||||||
<InputReadOnly labelClassName='text-sarLinkOrange' label="DOB" value={new Date(dbUser?.dob ?? "").toLocaleDateString("en-US", {
|
<InputReadOnly showCheck checkTrue={new Date(dbUser?.dob ?? "") === new Date(nationalData.dob)} labelClassName='text-sarLinkOrange' label="DOB" value={new Date(dbUser?.dob ?? "").toLocaleDateString("en-US", {
|
||||||
month: "short",
|
month: "short",
|
||||||
day: "2-digit",
|
day: "2-digit",
|
||||||
year: "numeric",
|
year: "numeric",
|
||||||
})} />
|
})} />
|
||||||
<InputReadOnly labelClassName='text-sarLinkOrange' label="Phone Number" value={dbUser?.phoneNumber ?? ""} />
|
<InputReadOnly showCheck checkTrue={dbUser?.phoneNumber === nationalData.primary_contact} labelClassName='text-sarLinkOrange' label="Phone Number" value={dbUser?.phoneNumber ?? ""} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="national-information">
|
<div id="national-information">
|
||||||
<h4 className='p-2 rounded font-semibold'>National Information</h4>
|
<h4 className='p-2 rounded font-semibold'>National Information</h4>
|
||||||
<div className='shadow p-2 rounded-md title-bg space-y-1 my-2 grid grid-cols-1 md:grid-cols-2 gap-2'>
|
<div className='shadow p-2 rounded-md title-bg space-y-1 my-2 grid grid-cols-1 md:grid-cols-2 gap-2'>
|
||||||
<InputReadOnly labelClassName='text-green-500' label="ID Card" value={nationalData?.nic ?? ""} />
|
<InputReadOnly showCheck={false} labelClassName='text-green-500' label="ID Card" value={nationalData?.nic ?? ""} />
|
||||||
<InputReadOnly labelClassName='text-green-500' label="Name" value={nationalData?.name_en ?? ""} />
|
<InputReadOnly showCheck={false} labelClassName='text-green-500' label="Name" value={nationalData?.name_en ?? ""} />
|
||||||
<InputReadOnly labelClassName='text-green-500' label="House Name" value={nationalData?.house_name_en ?? ""} />
|
<InputReadOnly showCheck={false} labelClassName='text-green-500' label="House Name" value={nationalData?.house_name_en ?? ""} />
|
||||||
<InputReadOnly labelClassName='text-green-500' label="Island" value={nationalData?.island_name_en ?? ""} />
|
<InputReadOnly showCheck={false} labelClassName='text-green-500' label="Island" value={nationalData?.island_name_en ?? ""} />
|
||||||
<InputReadOnly labelClassName='text-green-500' label="Atoll" value={nationalData?.atoll_en ?? ""} />
|
<InputReadOnly showCheck={false} labelClassName='text-green-500' label="Atoll" value={nationalData?.atoll_en ?? ""} />
|
||||||
<InputReadOnly labelClassName='text-green-500' label="DOB" value={new Date(nationalData?.dob ?? "").toLocaleDateString("en-US", {
|
<InputReadOnly showCheck={false} labelClassName='text-green-500' label="DOB" value={new Date(nationalData?.dob ?? "").toLocaleDateString("en-US", {
|
||||||
month: "short",
|
month: "short",
|
||||||
day: "2-digit",
|
day: "2-digit",
|
||||||
year: "numeric",
|
year: "numeric",
|
||||||
})} />
|
})} />
|
||||||
<InputReadOnly labelClassName='text-green-500' label="Phone Number" value={nationalData?.primary_contact ?? ""} />
|
<InputReadOnly showCheck={false} labelClassName='text-green-500' label="Phone Number" value={nationalData?.primary_contact ?? ""} />
|
||||||
<div className='flex flex-col col-span-2 items-center justify-center'>
|
<div className='flex flex-col col-span-2 items-center justify-center'>
|
||||||
<Image
|
<Image
|
||||||
src={nationalData.image_url}
|
src={nationalData.image_url || "https://i.pravatar.cc/300"}
|
||||||
height={100}
|
height={100}
|
||||||
width={100}
|
width={100}
|
||||||
className='object-fit aspect-square rounded-full'
|
className='object-fit aspect-square rounded-full'
|
||||||
alt='id photo'
|
alt='id photo'
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { Rejectuser } from "@/actions/user-actions";
|
||||||
import Filter from "@/components/filter";
|
import Filter from "@/components/filter";
|
||||||
import Search from "@/components/search";
|
import Search from "@/components/search";
|
||||||
import { UsersTable } from "@/components/user-table";
|
import { UsersTable } from "@/components/user-table";
|
||||||
@ -9,6 +10,7 @@ import {
|
|||||||
Hourglass,
|
Hourglass,
|
||||||
Minus,
|
Minus,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
|
import { redirect } from "next/navigation";
|
||||||
import React, { Suspense } from "react";
|
import React, { Suspense } from "react";
|
||||||
|
|
||||||
const sortfilterOptions = [
|
const sortfilterOptions = [
|
||||||
@ -36,6 +38,7 @@ export default async function AdminUsers({
|
|||||||
}) {
|
}) {
|
||||||
await AdminAuthGuard();
|
await AdminAuthGuard();
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<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-2 mb-4">
|
||||||
|
File diff suppressed because one or more lines are too long
@ -151,7 +151,7 @@ export async function AdminDevicesTable({
|
|||||||
{device.blocked ? "Blocked" : "Not Blocked"}
|
{device.blocked ? "Blocked" : "Not Blocked"}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
{device.blockedBy ? device.blockedBy : "Not Blocked"}
|
{device.blocked ? device.blockedBy : ""}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
{new Date().toLocaleDateString("en-US", {
|
{new Date().toLocaleDateString("en-US", {
|
||||||
|
@ -49,7 +49,7 @@ export async function ApplicationLayout({
|
|||||||
<AccountPopover />
|
<AccountPopover />
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<div className="p-4 ">{children}</div>
|
<div className="p-4 flex flex-col flex-1">{children}</div>
|
||||||
</SidebarInset>
|
</SidebarInset>
|
||||||
</SidebarProvider>
|
</SidebarProvider>
|
||||||
);
|
);
|
||||||
|
@ -48,7 +48,7 @@ export default function SignUpForm({ atolls }: { atolls: AtollWithIslands[] }) {
|
|||||||
if (actionState.db_error === "invalidPersonValidation") {
|
if (actionState.db_error === "invalidPersonValidation") {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="h-24 w-72 text-center text-green-500 p-4 flex my-4 flex-col items-center justify-center border dark:title-bg bg-white rounded-lg">{actionState.message}</div>
|
<div className="h-24 w-72 text-center text-green-500 p-4 flex my-4 flex-col items-center justify-center border dark:title-bg bg-white dark:bg-black rounded-lg">{actionState.message}</div>
|
||||||
<div className="mb-4 text-center text-sm">
|
<div className="mb-4 text-center text-sm">
|
||||||
Go to {" "}
|
Go to {" "}
|
||||||
<Link href="login" className="underline">
|
<Link href="login" className="underline">
|
||||||
|
@ -1,200 +1,124 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { createPayment } from "@/actions/payment";
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Drawer,
|
deviceCartAtom
|
||||||
DrawerClose,
|
|
||||||
DrawerContent,
|
|
||||||
DrawerDescription,
|
|
||||||
DrawerFooter,
|
|
||||||
DrawerHeader,
|
|
||||||
DrawerTitle,
|
|
||||||
DrawerTrigger,
|
|
||||||
} from "@/components/ui/drawer";
|
|
||||||
import {
|
|
||||||
cartDrawerOpenAtom,
|
|
||||||
deviceCartAtom,
|
|
||||||
numberOfMonths,
|
|
||||||
} from "@/lib/atoms";
|
} from "@/lib/atoms";
|
||||||
import { authClient } from "@/lib/auth-client";
|
import { authClient } from "@/lib/auth-client";
|
||||||
import type { PaymentType } from "@/lib/types";
|
import { useAtomValue } from "jotai";
|
||||||
import type { BillFormula, Device } from "@prisma/client";
|
|
||||||
import { useAtom, useAtomValue, useSetAtom } from "jotai";
|
|
||||||
import {
|
import {
|
||||||
CircleDollarSign,
|
MonitorSmartphone
|
||||||
Loader2,
|
|
||||||
MonitorSmartphone,
|
|
||||||
Trash2,
|
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { usePathname, useRouter } from "next/navigation";
|
import { usePathname, useRouter } from "next/navigation";
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import NumberInput from "./number-input";
|
|
||||||
|
|
||||||
|
|
||||||
export function DeviceCartDrawer({
|
export function DeviceCartDrawer() {
|
||||||
billFormula,
|
|
||||||
}: {
|
|
||||||
billFormula: BillFormula | null;
|
|
||||||
}) {
|
|
||||||
const baseAmount = billFormula?.baseAmount || 100;
|
|
||||||
const discountPercentage = billFormula?.discountPercentage || 75;
|
|
||||||
const session = authClient.useSession();
|
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
const devices = useAtomValue(deviceCartAtom);
|
const devices = useAtomValue(deviceCartAtom);
|
||||||
const setDeviceCart = useSetAtom(deviceCartAtom);
|
|
||||||
const [months, setMonths] = useAtom(numberOfMonths);
|
|
||||||
const [isOpen, setIsOpen] = useAtom(cartDrawerOpenAtom);
|
|
||||||
const [message, setMessage] = useState("");
|
|
||||||
const [disabled, setDisabled] = useState(false);
|
|
||||||
const [total, setTotal] = useState(0);
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
useEffect(() => {
|
|
||||||
if (months === 7) {
|
|
||||||
setMessage("You will get 1 month free.");
|
|
||||||
} else if (months === 12) {
|
|
||||||
setMessage("You will get 2 months free.");
|
|
||||||
} else {
|
|
||||||
setMessage("");
|
|
||||||
}
|
|
||||||
setTotal(baseAmount + ((devices.length + 1) - 1) * discountPercentage);
|
|
||||||
}, [months, devices.length, baseAmount, discountPercentage]);
|
|
||||||
|
|
||||||
if (pathname === "/payment") {
|
|
||||||
|
if (pathname === "/payment" || pathname === "/devices-to-pay") {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const data: PaymentType = {
|
|
||||||
numberOfMonths: months,
|
|
||||||
userId: session?.data?.user.id ?? "",
|
|
||||||
deviceIds: devices.map((device) => device.id),
|
|
||||||
amount: Number.parseFloat(total.toFixed(2)),
|
|
||||||
paid: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (devices.length === 0) return null
|
if (devices.length === 0) return null
|
||||||
return (
|
return <Button size={"lg"} className="bg-sarLinkOrange absolute bottom-10 w-fit z-20 left-1/2 transform -translate-x-1/2" onClick={() => router.push("/devices-to-pay")} variant="outline">
|
||||||
<>
|
<MonitorSmartphone />
|
||||||
<Drawer open={isOpen} onOpenChange={setIsOpen}>
|
Pay {devices.length > 0 && `(${devices.length})`} Device
|
||||||
<DrawerTrigger asChild>
|
</Button>
|
||||||
<Button size={"lg"} className="bg-sarLinkOrange absolute bottom-10 w-fit z-20 left-1/2 transform -translate-x-1/2" onClick={() => setIsOpen(!isOpen)} variant="outline">
|
|
||||||
<MonitorSmartphone />
|
|
||||||
Pay {devices.length > 0 && `(${devices.length})`} Device
|
|
||||||
</Button>
|
|
||||||
</DrawerTrigger>
|
|
||||||
<DrawerContent>
|
|
||||||
<div className="mx-auto w-full max-w-sm">
|
|
||||||
<DrawerHeader>
|
|
||||||
<DrawerTitle>Selected Devices</DrawerTitle>
|
|
||||||
<DrawerDescription>Selected devices pay.</DrawerDescription>
|
|
||||||
</DrawerHeader>
|
|
||||||
<div className="flex max-h-[calc(100svh-400px)] flex-col overflow-auto px-4 pb-4 gap-4">
|
|
||||||
<pre>{JSON.stringify(isOpen, null, 2)}</pre>
|
|
||||||
{devices.map((device) => (
|
|
||||||
<DeviceCard key={device.id} device={device} />
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<div className="px-4 flex flex-col gap-4">
|
|
||||||
<NumberInput
|
|
||||||
label="Set No of Months"
|
|
||||||
value={months}
|
|
||||||
onChange={(value) => setMonths(value)}
|
|
||||||
maxAllowed={12}
|
|
||||||
isDisabled={devices.length === 0}
|
|
||||||
/>
|
|
||||||
{message && (
|
|
||||||
<span className="title-bg text-lime-800 bg-lime-100/50 dark:text-lime-100 rounded text-center p-2 w-full">
|
|
||||||
{message}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<DrawerFooter>
|
|
||||||
<Button
|
|
||||||
onClick={async () => {
|
|
||||||
setDisabled(true);
|
|
||||||
toast.promise(
|
|
||||||
createPayment(data).then((result) => {
|
|
||||||
if (result.success) {
|
|
||||||
setDeviceCart([]);
|
|
||||||
setMonths(1);
|
|
||||||
setDisabled(false);
|
|
||||||
if (isOpen) router.push(`/payments/${result.paymentId}`);
|
|
||||||
setIsOpen(!isOpen);
|
|
||||||
return "Payment created!";
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
{
|
|
||||||
loading: "Processing payment...",
|
|
||||||
success: "Payment created!",
|
|
||||||
error: (err) => err.message || "Something went wrong.",
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
className="w-full"
|
|
||||||
disabled={devices.length === 0 || disabled}
|
|
||||||
>
|
|
||||||
{disabled ? (
|
|
||||||
<>
|
|
||||||
<Loader2 className="ml-2 animate-spin" />
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
Go to payment
|
|
||||||
<CircleDollarSign />
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
<DrawerClose asChild>
|
|
||||||
<Button variant="outline">Cancel</Button>
|
|
||||||
</DrawerClose>
|
|
||||||
<Button
|
|
||||||
onClick={() => {
|
|
||||||
setDeviceCart([]);
|
|
||||||
setIsOpen(!isOpen);
|
|
||||||
}}
|
|
||||||
variant="outline"
|
|
||||||
>
|
|
||||||
Clear Selection
|
|
||||||
</Button>
|
|
||||||
</DrawerFooter>
|
|
||||||
</div>
|
|
||||||
</DrawerContent>
|
|
||||||
</Drawer>
|
|
||||||
</>
|
|
||||||
|
|
||||||
);
|
// <>
|
||||||
|
// <Drawer open={isOpen} onOpenChange={setIsOpen}>
|
||||||
|
// <DrawerTrigger asChild>
|
||||||
|
// <Button size={"lg"} className="bg-sarLinkOrange absolute bottom-10 w-fit z-20 left-1/2 transform -translate-x-1/2" onClick={() => setIsOpen(!isOpen)} variant="outline">
|
||||||
|
// <MonitorSmartphone />
|
||||||
|
// Pay {devices.length > 0 && `(${devices.length})`} Device
|
||||||
|
// </Button>
|
||||||
|
// </DrawerTrigger>
|
||||||
|
// <DrawerContent>
|
||||||
|
// <div className="mx-auto w-full max-w-sm">
|
||||||
|
// <DrawerHeader>
|
||||||
|
// <DrawerTitle>Selected Devices</DrawerTitle>
|
||||||
|
// <DrawerDescription>Selected devices pay.</DrawerDescription>
|
||||||
|
// </DrawerHeader>
|
||||||
|
// <div className="flex max-h-[calc(100svh-400px)] flex-col overflow-auto px-4 pb-4 gap-4">
|
||||||
|
// <pre>{JSON.stringify(isOpen, null, 2)}</pre>
|
||||||
|
// {devices.map((device) => (
|
||||||
|
// <DeviceCard key={device.id} device={device} />
|
||||||
|
// ))}
|
||||||
|
// </div>
|
||||||
|
// <div className="px-4 flex flex-col gap-4">
|
||||||
|
// <NumberInput
|
||||||
|
// label="Set No of Months"
|
||||||
|
// value={months}
|
||||||
|
// onChange={(value) => setMonths(value)}
|
||||||
|
// maxAllowed={12}
|
||||||
|
// isDisabled={devices.length === 0}
|
||||||
|
// />
|
||||||
|
// {message && (
|
||||||
|
// <span className="title-bg text-lime-800 bg-lime-100/50 dark:text-lime-100 rounded text-center p-2 w-full">
|
||||||
|
// {message}
|
||||||
|
// </span>
|
||||||
|
// )}
|
||||||
|
// </div>
|
||||||
|
// <DrawerFooter>
|
||||||
|
// <Button
|
||||||
|
// onClick={async () => {
|
||||||
|
// setDisabled(true);
|
||||||
|
// toast.promise(
|
||||||
|
// createPayment(data).then((result) => {
|
||||||
|
// if (result.success) {
|
||||||
|
// setDeviceCart([]);
|
||||||
|
// setMonths(1);
|
||||||
|
// setDisabled(false);
|
||||||
|
// if (isOpen) router.push(`/payments/${result.paymentId}`);
|
||||||
|
// setIsOpen(!isOpen);
|
||||||
|
// return "Payment created!";
|
||||||
|
// }
|
||||||
|
// }),
|
||||||
|
// {
|
||||||
|
// loading: "Processing payment...",
|
||||||
|
// success: "Payment created!",
|
||||||
|
// error: (err) => err.message || "Something went wrong.",
|
||||||
|
// }
|
||||||
|
// );
|
||||||
|
// }}
|
||||||
|
// className="w-full"
|
||||||
|
// disabled={devices.length === 0 || disabled}
|
||||||
|
// >
|
||||||
|
// {disabled ? (
|
||||||
|
// <>
|
||||||
|
// <Loader2 className="ml-2 animate-spin" />
|
||||||
|
// </>
|
||||||
|
// ) : (
|
||||||
|
// <>
|
||||||
|
// Go to payment
|
||||||
|
// <CircleDollarSign />
|
||||||
|
// </>
|
||||||
|
// )}
|
||||||
|
// </Button>
|
||||||
|
// <DrawerClose asChild>
|
||||||
|
// <Button variant="outline">Cancel</Button>
|
||||||
|
// </DrawerClose>
|
||||||
|
// <Button
|
||||||
|
// onClick={() => {
|
||||||
|
// setDeviceCart([]);
|
||||||
|
// setIsOpen(!isOpen);
|
||||||
|
// }}
|
||||||
|
// variant="outline"
|
||||||
|
// >
|
||||||
|
// Clear Selection
|
||||||
|
// </Button>
|
||||||
|
// </DrawerFooter>
|
||||||
|
// </div>
|
||||||
|
// </DrawerContent>
|
||||||
|
// </Drawer>
|
||||||
|
// </>
|
||||||
|
|
||||||
|
// );
|
||||||
}
|
}
|
||||||
|
|
||||||
function DeviceCard({ device }: { device: Device }) {
|
|
||||||
const setDeviceCart = useSetAtom(deviceCartAtom);
|
|
||||||
return (
|
|
||||||
<div className="relative flex h-full w-full items-center pr-4 justify-between rounded-lg border border-input bg-background shadow-sm shadow-black/5 transition-shadow focus-within:border-ring focus-within:outline-none focus-within:ring-[3px] focus-within:ring-ring/20">
|
|
||||||
<div>
|
|
||||||
<label
|
|
||||||
htmlFor="input-33"
|
|
||||||
className="block px-3 pt-2 text-xs font-medium text-foreground"
|
|
||||||
>
|
|
||||||
{device.name}
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
className="flex h-10 opacity-50 w-full bg-transparent px-3 pb-2 text-sm text-foreground placeholder:text-muted-foreground/70 focus-visible:outline-none"
|
|
||||||
value={device.mac}
|
|
||||||
readOnly
|
|
||||||
disabled
|
|
||||||
placeholder={"MAC Address"}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
onClick={() => {
|
|
||||||
setDeviceCart((prev) => prev.filter((d) => d.id !== device.id));
|
|
||||||
}}
|
|
||||||
variant={"destructive"}
|
|
||||||
>
|
|
||||||
Remove
|
|
||||||
<Trash2 />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
107
components/devices-for-payment.tsx
Normal file
107
components/devices-for-payment.tsx
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import { createPayment } from "@/actions/payment";
|
||||||
|
import DeviceCard from "@/components/device-card";
|
||||||
|
import NumberInput from "@/components/number-input";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import {
|
||||||
|
deviceCartAtom,
|
||||||
|
numberOfMonths
|
||||||
|
} from "@/lib/atoms";
|
||||||
|
import { authClient } from "@/lib/auth-client";
|
||||||
|
import type { PaymentType } from "@/lib/types";
|
||||||
|
import type { BillFormula } from "@prisma/client";
|
||||||
|
import { useAtom, useAtomValue, useSetAtom } from "jotai";
|
||||||
|
import {
|
||||||
|
CircleDollarSign,
|
||||||
|
Loader2
|
||||||
|
} from "lucide-react";
|
||||||
|
import { usePathname } from "next/navigation";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
export default function DevicesForPayment({
|
||||||
|
billFormula,
|
||||||
|
}: {
|
||||||
|
billFormula?: BillFormula;
|
||||||
|
}) {
|
||||||
|
const baseAmount = billFormula?.baseAmount || 100;
|
||||||
|
const discountPercentage = billFormula?.discountPercentage || 75;
|
||||||
|
const session = authClient.useSession();
|
||||||
|
const pathname = usePathname();
|
||||||
|
const devices = useAtomValue(deviceCartAtom);
|
||||||
|
const setDeviceCart = useSetAtom(deviceCartAtom);
|
||||||
|
const [months, setMonths] = useAtom(numberOfMonths);
|
||||||
|
const [message, setMessage] = useState("");
|
||||||
|
const [disabled, setDisabled] = useState(false);
|
||||||
|
const [total, setTotal] = useState(0);
|
||||||
|
useEffect(() => {
|
||||||
|
if (months === 7) {
|
||||||
|
setMessage("You will get 1 month free.");
|
||||||
|
} else if (months === 12) {
|
||||||
|
setMessage("You will get 2 months free.");
|
||||||
|
} else {
|
||||||
|
setMessage("");
|
||||||
|
}
|
||||||
|
setTotal(baseAmount + ((devices.length + 1) - 1) * discountPercentage);
|
||||||
|
}, [months, devices.length, baseAmount, discountPercentage]);
|
||||||
|
|
||||||
|
if (pathname === "/payment") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data: PaymentType = {
|
||||||
|
numberOfMonths: months,
|
||||||
|
userId: session?.data?.user.id ?? "",
|
||||||
|
deviceIds: devices.map((device) => device.id),
|
||||||
|
amount: Number.parseFloat(total.toFixed(2)),
|
||||||
|
paid: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
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">
|
||||||
|
{devices.map((device) => (
|
||||||
|
<DeviceCard key={device.id} device={device} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-4">
|
||||||
|
<NumberInput
|
||||||
|
label="Set No of Months"
|
||||||
|
value={months}
|
||||||
|
onChange={(value: number) => setMonths(value)}
|
||||||
|
maxAllowed={12}
|
||||||
|
isDisabled={devices.length === 0}
|
||||||
|
/>
|
||||||
|
{message && (
|
||||||
|
<span className="title-bg text-lime-800 bg-lime-100/50 dark:text-lime-100 rounded text-center p-2 w-full">
|
||||||
|
{message}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
onClick={async () => {
|
||||||
|
setDisabled(true);
|
||||||
|
await createPayment(data);
|
||||||
|
setDeviceCart([]);
|
||||||
|
setMonths(1);
|
||||||
|
setDisabled(false);
|
||||||
|
|
||||||
|
}}
|
||||||
|
className="w-full"
|
||||||
|
disabled={devices.length === 0 || disabled}
|
||||||
|
>
|
||||||
|
{disabled ? (
|
||||||
|
<>
|
||||||
|
<Loader2 className="ml-2 animate-spin" />
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
Go to payment
|
||||||
|
<CircleDollarSign />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
@ -1,20 +1,40 @@
|
|||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils';
|
||||||
import React from 'react'
|
import { CheckCheck, X } from 'lucide-react';
|
||||||
|
|
||||||
export default function InputReadOnly({ label, value, labelClassName, className }: { label: string, value: string, labelClassName?: string, className?: string }) {
|
export default function InputReadOnly({ label, value, labelClassName, className, showCheck = true, checkTrue = false }: {
|
||||||
|
label: string;
|
||||||
|
value: string;
|
||||||
|
labelClassName?: string;
|
||||||
|
className?: string;
|
||||||
|
showCheck?: boolean;
|
||||||
|
checkTrue?: boolean
|
||||||
|
}) {
|
||||||
return (
|
return (
|
||||||
<div className={cn("relative rounded-lg border border-input bg-background shadow-sm shadow-black/5 transition-shadow focus-within:border-ring focus-within:outline-none focus-within:ring-[3px] focus-within:ring-ring/20 has-[:disabled]:cursor-not-allowed has-[:disabled]:opacity-80 [&:has(input:is(:disabled))_*]:pointer-events-none", className)}>
|
<div className={cn("relative flex items-center justify-between rounded-lg border border-input bg-background shadow-sm shadow-black/5 transition-shadow focus-within:border-ring focus-within:outline-none focus-within:ring-[3px] focus-within:ring-ring/20 has-[:disabled]:cursor-not-allowed has-[:disabled]:opacity-80 [&:has(input:is(:disabled))_*]:pointer-events-none", className)}>
|
||||||
<label htmlFor="input-33" className={cn("block px-3 pt-2 text-xs font-medium", labelClassName)}>
|
<div>
|
||||||
{label}
|
|
||||||
</label>
|
<label htmlFor="input-33" className={cn("block px-3 pt-2 text-xs font-medium", labelClassName)}>
|
||||||
<input
|
{label}
|
||||||
id="input-33"
|
</label>
|
||||||
className="flex h-10 w-full bg-transparent px-3 pb-2 text-sm text-foreground placeholder:text-muted-foreground/70 focus-visible:outline-none"
|
<input
|
||||||
placeholder={value}
|
id="input-33"
|
||||||
disabled
|
className="flex h-10 w-full bg-transparent px-3 pb-2 text-sm text-foreground placeholder:text-muted-foreground/70 focus-visible:outline-none"
|
||||||
value={value}
|
placeholder={value}
|
||||||
type="text"
|
disabled
|
||||||
/>
|
value={value}
|
||||||
|
type="text"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{showCheck && (
|
||||||
|
<div>
|
||||||
|
{checkTrue ? (
|
||||||
|
<CheckCheck className='mx-4 text-green-500' />
|
||||||
|
) : (
|
||||||
|
<X className='mx-4 text-red-500' />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
)
|
)
|
||||||
|
@ -54,7 +54,7 @@ export default function UserRejectDialog({ user }: { user: User }) {
|
|||||||
},
|
},
|
||||||
error: (error) => {
|
error: (error) => {
|
||||||
setDisabled(false)
|
setDisabled(false)
|
||||||
return error || "Something went wrong"
|
return error.message || "Something went wrong"
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
setDisabled(false)
|
setDisabled(false)
|
||||||
|
@ -1,10 +1,15 @@
|
|||||||
"use server";
|
"use server";
|
||||||
import { headers } from "next/headers";
|
import { headers } from "next/headers";
|
||||||
|
import { cache } from "react";
|
||||||
import { auth } from "./auth";
|
import { auth } from "./auth";
|
||||||
|
|
||||||
export async function getCurrentUser() {
|
const getCurrentUserCache = cache(async () => {
|
||||||
const session = await auth.api.getSession({
|
const session = await auth.api.getSession({
|
||||||
headers: await headers(),
|
headers: await headers(),
|
||||||
});
|
});
|
||||||
return session?.user;
|
return session?.user;
|
||||||
|
});
|
||||||
|
|
||||||
|
export async function getCurrentUser() {
|
||||||
|
return await getCurrentUserCache();
|
||||||
}
|
}
|
||||||
|
11
lib/auth.ts
11
lib/auth.ts
@ -1,11 +1,16 @@
|
|||||||
import { sendOtp } from "@/actions/auth-actions";
|
import { sendOtp } from "@/actions/auth-actions";
|
||||||
import { PrismaClient } from "@prisma/client";
|
|
||||||
import { betterAuth } from "better-auth";
|
import { betterAuth } from "better-auth";
|
||||||
import { prismaAdapter } from "better-auth/adapters/prisma";
|
import { prismaAdapter } from "better-auth/adapters/prisma";
|
||||||
import { phoneNumber } from "better-auth/plugins";
|
import { phoneNumber } from "better-auth/plugins";
|
||||||
|
import prisma from "./db";
|
||||||
|
|
||||||
const prisma = new PrismaClient();
|
|
||||||
export const auth = betterAuth({
|
export const auth = betterAuth({
|
||||||
|
session: {
|
||||||
|
cookieCache: {
|
||||||
|
enabled: true,
|
||||||
|
maxAge: 10 * 60, // Cache duration in seconds
|
||||||
|
},
|
||||||
|
},
|
||||||
trustedOrigins: process.env.BETTER_AUTH_TRUSTED_ORIGINS?.split(",") || [
|
trustedOrigins: process.env.BETTER_AUTH_TRUSTED_ORIGINS?.split(",") || [
|
||||||
"localhost:3000",
|
"localhost:3000",
|
||||||
],
|
],
|
||||||
@ -25,7 +30,7 @@ export const auth = betterAuth({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
database: prismaAdapter(prisma, {
|
database: prismaAdapter(prisma, {
|
||||||
provider: "sqlite", // or "mysql", "postgresql", ...etc
|
provider: "postgresql", // or "mysql", "postgresql", ...etc
|
||||||
}),
|
}),
|
||||||
plugins: [
|
plugins: [
|
||||||
phoneNumber({
|
phoneNumber({
|
||||||
|
@ -35,7 +35,6 @@ export async function VerifyUserDetails({ user }: { user: User }) {
|
|||||||
user.id_card === nationalData.nic &&
|
user.id_card === nationalData.nic &&
|
||||||
user.name === nationalData.name_en &&
|
user.name === nationalData.name_en &&
|
||||||
user.address === nationalData.house_name_en &&
|
user.address === nationalData.house_name_en &&
|
||||||
phoneNumber === nationalData.primary_contact &&
|
|
||||||
age >= 18
|
age >= 18
|
||||||
) {
|
) {
|
||||||
return true;
|
return true;
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import { betterFetch } from "@better-fetch/fetch";
|
|
||||||
import type { Session } from "better-auth/types";
|
import type { Session } from "better-auth/types";
|
||||||
import { type NextRequest, NextResponse } from "next/server";
|
import { type NextRequest, NextResponse } from "next/server";
|
||||||
|
|
||||||
@ -6,29 +5,28 @@ export default async function authMiddleware(request: NextRequest) {
|
|||||||
const protocol = request.headers.get("x-forwarded-proto") || "http";
|
const protocol = request.headers.get("x-forwarded-proto") || "http";
|
||||||
const host = request.headers.get("host") || "localhost:3000";
|
const host = request.headers.get("host") || "localhost:3000";
|
||||||
|
|
||||||
console.log(protocol)
|
|
||||||
console.log(host)
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { data: session } = await betterFetch<Session>(
|
const response = await fetch(`${protocol}://${host}/api/auth/get-session`, {
|
||||||
"http://localhost:3000/api/auth/get-session",
|
method: "GET",
|
||||||
{
|
headers: {
|
||||||
baseURL: `${protocol}://${host}`,
|
cookie: request.headers.get("cookie") || "",
|
||||||
headers: {
|
host: host,
|
||||||
//get the cookie from the request
|
|
||||||
cookie: request.headers.get("cookie") || "",
|
|
||||||
host: host
|
|
||||||
},
|
|
||||||
|
|
||||||
},
|
},
|
||||||
);
|
next: { revalidate: 600 }, // Cache for 10 minutes (600 seconds)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error("Failed to fetch session");
|
||||||
|
}
|
||||||
|
|
||||||
|
const session: Session = await response.json();
|
||||||
|
|
||||||
if (!session) {
|
if (!session) {
|
||||||
return NextResponse.redirect(new URL("/login", request.url));
|
return NextResponse.redirect(new URL("/login", request.url));
|
||||||
}
|
}
|
||||||
return NextResponse.next();
|
return NextResponse.next();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("Middlewaree", error);
|
console.log("Middleware error", error);
|
||||||
return NextResponse.redirect(new URL("/login", request.url));
|
return NextResponse.redirect(new URL("/login", request.url));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,13 @@ const nextConfig: NextConfig = {
|
|||||||
search: "",
|
search: "",
|
||||||
port: "",
|
port: "",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
protocol: "https",
|
||||||
|
hostname: "i.pravatar.cc",
|
||||||
|
pathname: "/300/**",
|
||||||
|
search: "",
|
||||||
|
port: "",
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
output: "standalone",
|
output: "standalone",
|
||||||
|
143
package-lock.json
generated
143
package-lock.json
generated
@ -24,7 +24,7 @@
|
|||||||
"@radix-ui/react-slot": "^1.1.0",
|
"@radix-ui/react-slot": "^1.1.0",
|
||||||
"@radix-ui/react-tooltip": "^1.1.4",
|
"@radix-ui/react-tooltip": "^1.1.4",
|
||||||
"@tanstack/react-query": "^5.61.4",
|
"@tanstack/react-query": "^5.61.4",
|
||||||
"better-auth": "^1.0.0",
|
"better-auth": "^1.1.13",
|
||||||
"class-variance-authority": "^0.7.0",
|
"class-variance-authority": "^0.7.0",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"cmdk": "^1.0.0",
|
"cmdk": "^1.0.0",
|
||||||
@ -73,14 +73,17 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@better-auth/utils": {
|
"node_modules/@better-auth/utils": {
|
||||||
"version": "0.2.2",
|
"version": "0.2.3",
|
||||||
"license": "MIT",
|
"resolved": "https://registry.npmjs.org/@better-auth/utils/-/utils-0.2.3.tgz",
|
||||||
|
"integrity": "sha512-Ap1GaSmo6JYhJhxJOpUB0HobkKPTNzfta+bLV89HfpyCAHN7p8ntCrmNFHNAVD0F6v0mywFVEUg1FUhNCc81Rw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"uncrypto": "^0.1.3"
|
"uncrypto": "^0.1.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@better-fetch/fetch": {
|
"node_modules/@better-fetch/fetch": {
|
||||||
"version": "1.1.12"
|
"version": "1.1.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/@better-fetch/fetch/-/fetch-1.1.12.tgz",
|
||||||
|
"integrity": "sha512-B3bfloI/2UBQWIATRN6qmlORrvx3Mp0kkNjmXLv0b+DtbtR+pP4/I5kQA/rDUv+OReLywCCldf6co4LdDmh8JA=="
|
||||||
},
|
},
|
||||||
"node_modules/@cspotcode/source-map-support": {
|
"node_modules/@cspotcode/source-map-support": {
|
||||||
"version": "0.8.1",
|
"version": "0.8.1",
|
||||||
@ -561,6 +564,66 @@
|
|||||||
"node": ">= 6"
|
"node": ">= 6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@next/swc-darwin-arm64": {
|
||||||
|
"version": "15.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.1.2.tgz",
|
||||||
|
"integrity": "sha512-b9TN7q+j5/7+rGLhFAVZiKJGIASuo8tWvInGfAd8wsULjB1uNGRCj1z1WZwwPWzVQbIKWFYqc+9L7W09qwt52w==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@next/swc-darwin-x64": {
|
||||||
|
"version": "15.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.1.2.tgz",
|
||||||
|
"integrity": "sha512-caR62jNDUCU+qobStO6YJ05p9E+LR0EoXh1EEmyU69cYydsAy7drMcOlUlRtQihM6K6QfvNwJuLhsHcCzNpqtA==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@next/swc-linux-arm64-gnu": {
|
||||||
|
"version": "15.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.1.2.tgz",
|
||||||
|
"integrity": "sha512-fHHXBusURjBmN6VBUtu6/5s7cCeEkuGAb/ZZiGHBLVBXMBy4D5QpM8P33Or8JD1nlOjm/ZT9sEE5HouQ0F+hUA==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@next/swc-linux-arm64-musl": {
|
||||||
|
"version": "15.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.1.2.tgz",
|
||||||
|
"integrity": "sha512-9CF1Pnivij7+M3G74lxr+e9h6o2YNIe7QtExWq1KUK4hsOLTBv6FJikEwCaC3NeYTflzrm69E5UfwEAbV2U9/g==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@next/swc-linux-x64-gnu": {
|
"node_modules/@next/swc-linux-x64-gnu": {
|
||||||
"version": "15.1.2",
|
"version": "15.1.2",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
@ -589,6 +652,36 @@
|
|||||||
"node": ">= 10"
|
"node": ">= 10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@next/swc-win32-arm64-msvc": {
|
||||||
|
"version": "15.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.1.2.tgz",
|
||||||
|
"integrity": "sha512-wvg7MlfnaociP7k8lxLX4s2iBJm4BrNiNFhVUY+Yur5yhAJHfkS8qPPeDEUH8rQiY0PX3u/P7Q/wcg6Mv6GSAA==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@next/swc-win32-x64-msvc": {
|
||||||
|
"version": "15.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.1.2.tgz",
|
||||||
|
"integrity": "sha512-D3cNA8NoT3aWISWmo7HF5Eyko/0OdOO+VagkoJuiTk7pyX3P/b+n8XA/MYvyR+xSVcbKn68B1rY9fgqjNISqzQ==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@noble/ciphers": {
|
"node_modules/@noble/ciphers": {
|
||||||
"version": "0.6.0",
|
"version": "0.6.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@ -3673,30 +3766,34 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/better-auth": {
|
"node_modules/better-auth": {
|
||||||
"version": "1.1.3",
|
"version": "1.1.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/better-auth/-/better-auth-1.1.13.tgz",
|
||||||
|
"integrity": "sha512-Tt4QYGFKuAFTjJx6RyXzC50KNyh96/QeVT2QxJRnXiEmp6wGEGmua9iRb5XL3bSnBu8oepAE7xlLF/AvfkEZig==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@better-auth/utils": "0.2.2",
|
"@better-auth/utils": "0.2.3",
|
||||||
"@better-fetch/fetch": "1.1.12",
|
"@better-fetch/fetch": "1.1.12",
|
||||||
"@noble/ciphers": "^0.6.0",
|
"@noble/ciphers": "^0.6.0",
|
||||||
"@noble/hashes": "^1.6.1",
|
"@noble/hashes": "^1.6.1",
|
||||||
"@simplewebauthn/browser": "^13.0.0",
|
"@simplewebauthn/browser": "^13.0.0",
|
||||||
"@simplewebauthn/server": "^13.0.0",
|
"@simplewebauthn/server": "^13.0.0",
|
||||||
"better-call": "0.3.3-beta.4",
|
"better-call": "0.3.3",
|
||||||
"defu": "^6.1.4",
|
"defu": "^6.1.4",
|
||||||
"jose": "^5.9.6",
|
"jose": "^5.9.6",
|
||||||
"kysely": "^0.27.4",
|
"kysely": "^0.27.4",
|
||||||
|
"nanostores": "^0.11.3",
|
||||||
"uncrypto": "^0.1.3",
|
"uncrypto": "^0.1.3",
|
||||||
"zod": "^3.23.8"
|
"zod": "^3.24.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/better-call": {
|
"node_modules/better-call": {
|
||||||
"version": "0.3.3-beta.4",
|
"version": "0.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/better-call/-/better-call-0.3.3.tgz",
|
||||||
|
"integrity": "sha512-N4lDVm0NGmFfDJ0XMQ4O83Zm/3dPlvIQdxvwvgSLSkjFX5PM4GUYSVAuxNzXN27QZMHDkrJTWUqxBrm4tPC3eA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@better-fetch/fetch": "^1.1.4",
|
"@better-fetch/fetch": "^1.1.4",
|
||||||
"rou3": "^0.5.1",
|
"rou3": "^0.5.1",
|
||||||
"set-cookie-parser": "^2.7.1",
|
|
||||||
"uncrypto": "^0.1.3",
|
"uncrypto": "^0.1.3",
|
||||||
"zod": "^3.23.8"
|
"zod": "^3.24.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/binary-extensions": {
|
"node_modules/binary-extensions": {
|
||||||
@ -5994,6 +6091,20 @@
|
|||||||
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/nanostores": {
|
||||||
|
"version": "0.11.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/nanostores/-/nanostores-0.11.3.tgz",
|
||||||
|
"integrity": "sha512-TUes3xKIX33re4QzdxwZ6tdbodjmn3tWXCEc1uokiEmo14sI1EaGYNs2k3bU2pyyGNmBqFGAVl6jAGWd06AVIg==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/ai"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.0.0 || >=20.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/natural-compare": {
|
"node_modules/natural-compare": {
|
||||||
"version": "1.4.0",
|
"version": "1.4.0",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
@ -6943,7 +7054,8 @@
|
|||||||
},
|
},
|
||||||
"node_modules/rou3": {
|
"node_modules/rou3": {
|
||||||
"version": "0.5.1",
|
"version": "0.5.1",
|
||||||
"license": "MIT"
|
"resolved": "https://registry.npmjs.org/rou3/-/rou3-0.5.1.tgz",
|
||||||
|
"integrity": "sha512-OXMmJ3zRk2xeXFGfA3K+EOPHC5u7RDFG7lIOx0X1pdnhUkI8MdVrbV+sNsD80ElpUZ+MRHdyxPnFthq9VHs8uQ=="
|
||||||
},
|
},
|
||||||
"node_modules/run-parallel": {
|
"node_modules/run-parallel": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
@ -7013,10 +7125,6 @@
|
|||||||
"semver": "bin/semver.js"
|
"semver": "bin/semver.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/set-cookie-parser": {
|
|
||||||
"version": "2.7.1",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/set-function-length": {
|
"node_modules/set-function-length": {
|
||||||
"version": "1.2.2",
|
"version": "1.2.2",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
@ -7808,7 +7916,8 @@
|
|||||||
},
|
},
|
||||||
"node_modules/uncrypto": {
|
"node_modules/uncrypto": {
|
||||||
"version": "0.1.3",
|
"version": "0.1.3",
|
||||||
"license": "MIT"
|
"resolved": "https://registry.npmjs.org/uncrypto/-/uncrypto-0.1.3.tgz",
|
||||||
|
"integrity": "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q=="
|
||||||
},
|
},
|
||||||
"node_modules/undici-types": {
|
"node_modules/undici-types": {
|
||||||
"version": "6.20.0",
|
"version": "6.20.0",
|
||||||
|
@ -31,7 +31,7 @@
|
|||||||
"@radix-ui/react-slot": "^1.1.0",
|
"@radix-ui/react-slot": "^1.1.0",
|
||||||
"@radix-ui/react-tooltip": "^1.1.4",
|
"@radix-ui/react-tooltip": "^1.1.4",
|
||||||
"@tanstack/react-query": "^5.61.4",
|
"@tanstack/react-query": "^5.61.4",
|
||||||
"better-auth": "^1.0.0",
|
"better-auth": "^1.1.13",
|
||||||
"class-variance-authority": "^0.7.0",
|
"class-variance-authority": "^0.7.0",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"cmdk": "^1.0.0",
|
"cmdk": "^1.0.0",
|
||||||
|
@ -11,13 +11,13 @@ async function main() {
|
|||||||
},
|
},
|
||||||
update: {},
|
update: {},
|
||||||
create: {
|
create: {
|
||||||
name: "Admin Admin",
|
name: process.env.ADMIN_FULLNAME,
|
||||||
email: process.env.ADMIN_EMAIL,
|
email: process.env.ADMIN_EMAIL,
|
||||||
emailVerified: true,
|
emailVerified: true,
|
||||||
verified: true,
|
verified: true,
|
||||||
address: "Sky villa",
|
address: process.env.ADMIN_ADDRESS,
|
||||||
id_card: process.env.ADMIN_IDCARD,
|
id_card: process.env.ADMIN_IDCARD,
|
||||||
dob: new Date("1990-01-01"),
|
dob: new Date("1999-05-06"),
|
||||||
phoneNumber: process.env.ADMIN_PHONENUMBER ?? "",
|
phoneNumber: process.env.ADMIN_PHONENUMBER ?? "",
|
||||||
phoneNumberVerified: true,
|
phoneNumberVerified: true,
|
||||||
role: "ADMIN",
|
role: "ADMIN",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user