diff --git a/actions/auth-actions.ts b/actions/auth-actions.ts index 91b4eb9..d84b7ae 100644 --- a/actions/auth-actions.ts +++ b/actions/auth-actions.ts @@ -2,11 +2,13 @@ import { authClient } from "@/lib/auth-client"; import prisma from "@/lib/db"; +import VerifyUserDetails from "@/lib/person"; import { signUpFormSchema } from "@/lib/schemas"; import { headers } from "next/headers"; // import type { User } from "@prisma/client"; import { redirect } from "next/navigation"; import { z } from "zod"; +import { SendUserRejectionDetailSMS } from "./user-actions"; const formSchema = z.object({ phoneNumber: z .string() @@ -41,6 +43,14 @@ export async function signin(previousState: ActionState, formData: FormData) { if (!userExists) { return redirect(`/signup?phone_number=${phoneNumber}`); } + + 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, }); @@ -79,6 +89,7 @@ export async function signup(_actionState: ActionState, formData: FormData) { NUMBER_WITH_COUNTRY_CODE = `+960${parsedData.data.phone_number.split("-").join("")}`; } console.log({ NUMBER_WITH_COUNTRY_CODE }); + const idCardExists = await prisma.user.findFirst({ where: { id_card: parsedData.data.id_card, @@ -120,14 +131,39 @@ export async function signup(_actionState: ActionState, formData: FormData) { phoneNumber: NUMBER_WITH_COUNTRY_CODE, }, }); - await authClient.phoneNumber.sendOtp({ - phoneNumber: newUser.phoneNumber, - }); + const isValidPerson = await VerifyUserDetails({ user: newUser }); + + if (!isValidPerson) { + await SendUserRejectionDetailSMS({ + details: ` + A new user has requested for verification. + USER DETAILS: + Name: ${parsedData.data.name} + Address: ${parsedData.data.address} + ID Card: ${parsedData.data.id_card} + DOB: ${parsedData.data.dob} + ACC No: ${parsedData.data.accNo} + Verify the user with the folloiwing link: ${process.env.BETTER_AUTH_URL}/users/${newUser.id}/verify + `, + phoneNumber: process.env.ADMIN_PHONENUMBER ?? "", + }); + return { + message: + "Your account has been requested for verification. Please wait for a response from admin.", + payload: formData, + db_error: "invalidPersonValidation", + }; + } + + if (isValidPerson) { + await authClient.phoneNumber.sendOtp({ + phoneNumber: newUser.phoneNumber, + }); + } redirect( `/verify-otp?phone_number=${encodeURIComponent(newUser.phoneNumber)}`, ); - - return { message: "Post created" }; + return { message: "User created successfully" }; } export const sendOtp = async (phoneNumber: string, code: string) => { diff --git a/actions/ninja/client.ts b/actions/ninja/client.ts index 540e55b..b909eed 100644 --- a/actions/ninja/client.ts +++ b/actions/ninja/client.ts @@ -1,25 +1,4 @@ "use server"; -// const raw = { -// group_settings_id: "", -// address1: "", -// city: "F", -// state: "Dharanboodhoo", -// postal_code: "12040", -// country_id: "462", -// address2: "Skyvilla", -// contacts: [ -// { -// first_name: "Abdulla", -// last_name: "Aidhaan", -// email: "", -// phone: "778-0588", -// send_email: false, -// custom_value1: "1971-02-24", -// custom_value2: "A265117", -// custom_value3: "", -// }, -// ], -// }; type CreateClientProps = { group_settings_id: string; diff --git a/actions/user-actions.ts b/actions/user-actions.ts index 2afbc25..2c6c34e 100644 --- a/actions/user-actions.ts +++ b/actions/user-actions.ts @@ -1,6 +1,8 @@ "use server"; +import usePerson from "@/hooks/use-person"; import prisma from "@/lib/db"; +import VerifyUserDetails from "@/lib/person"; import { revalidatePath } from "next/cache"; import { redirect } from "next/navigation"; import { CreateClient } from "./ninja/client"; @@ -18,33 +20,42 @@ export async function VerifyUser(userId: string) { if (!user) { throw new Error("User not found"); } - await prisma.user.update({ - where: { - id: userId, - }, - data: { - verified: true, - }, - }); - const ninjaClient = await CreateClient({ - group_settings_id: "", - address1: "", - city: user.atoll?.name || "", - state: user.island?.name || "", - postal_code: "", - country_id: "462", - address2: user.address || "", - contacts: { - first_name: user.name?.split(" ")[0] || "", - last_name: user.name?.split(" ")[1] || "", - email: user.email || "", - phone: user.phoneNumber || "", - send_email: false, - custom_value1: user.dob?.toISOString().split("T")[0] || "", - custom_value2: user.id_card || "", - custom_value3: "", - }, - }); + const isValidPerson = await VerifyUserDetails({ user }); + + if (!isValidPerson) + throw new Error("The user details does not match national data."); + + if (isValidPerson) { + await prisma.user.update({ + where: { + id: userId, + }, + data: { + verified: true, + }, + }); + + const ninjaClient = await CreateClient({ + group_settings_id: "", + address1: "", + city: user.atoll?.name || "", + state: user.island?.name || "", + postal_code: "", + country_id: "462", + address2: user.address || "", + contacts: { + first_name: user.name?.split(" ")[0] || "", + last_name: user.name?.split(" ")[1] || "", + email: user.email || "", + phone: user.phoneNumber || "", + send_email: false, + custom_value1: user.dob?.toISOString().split("T")[0] || "", + custom_value2: user.id_card || "", + custom_value3: "", + }, + }); + } + revalidatePath("/users"); } diff --git a/app/(dashboard)/users/[userId]/verify/page.tsx b/app/(dashboard)/users/[userId]/verify/page.tsx index c3deb72..536c55b 100644 --- a/app/(dashboard)/users/[userId]/verify/page.tsx +++ b/app/(dashboard)/users/[userId]/verify/page.tsx @@ -2,8 +2,11 @@ import InputReadOnly from '@/components/input-read-only'; import { Badge } from '@/components/ui/badge'; import UserRejectDialog from '@/components/user/user-reject-dialog'; import { UserVerifyDialog } from '@/components/user/user-verify-dialog'; +import usePerson from '@/hooks/use-person'; import prisma from '@/lib/db'; +import type { TNationalPerson } from '@/lib/types'; +import Image from 'next/image'; import React from 'react' export default async function VerifyUserPage({ @@ -18,7 +21,16 @@ export default async function VerifyUserPage({ where: { id: userId, }, + include: { + island: { + include: { + atoll: true + } + } + } }) + + const nationalData = await usePerson({ idCard: dbUser?.id_card ?? "" }) return (
@@ -35,30 +47,45 @@ export default async function VerifyUserPage({

Database Information

-
- - - - + + + + + + + - +

National Information

-
- - - - + + + + + + - + +
+ id photo +
+
diff --git a/components/auth/signup-form.tsx b/components/auth/signup-form.tsx index 0a349c0..357cecb 100644 --- a/components/auth/signup-form.tsx +++ b/components/auth/signup-form.tsx @@ -9,7 +9,6 @@ import type { Island, Prisma } from "@prisma/client"; import { Loader2 } from "lucide-react"; import { useSearchParams } from "next/navigation"; import * as React from "react"; -import { useActionState } from "react"; import { Select, @@ -31,7 +30,7 @@ export default function SignUpForm({ atolls }: { atolls: AtollWithIslands[] }) { const [atoll, setAtoll] = React.useState(); const [islands, setIslands] = React.useState(); - const [actionState, action, isPending] = useActionState(signup, { + const [actionState, action, isPending] = React.useActionState(signup, { message: "", }); @@ -45,6 +44,7 @@ export default function SignUpForm({ atolls }: { atolls: AtollWithIslands[] }) { const phoneNumberFromUrl = params.get("phone_number"); const NUMBER_WITHOUT_DASH = phoneNumberFromUrl?.split("-").join(""); + return (
- {actionState.errors?.fieldErrors.name && ( + {actionState?.errors?.fieldErrors.name && ( - {actionState.errors?.fieldErrors.name} + {actionState?.errors?.fieldErrors.name} )}
@@ -82,22 +82,22 @@ export default function SignUpForm({ atolls }: { atolls: AtollWithIslands[] }) { type="text" maxLength={7} disabled={isPending} - defaultValue={(actionState.payload?.get("id_card") || "") as string} + defaultValue={(actionState?.payload?.get("id_card") || "") as string} className={cn( "text-base", - actionState.errors?.fieldErrors?.id_card && + actionState?.errors?.fieldErrors?.id_card && "border-2 border-red-500", )} placeholder="ID Card" /> {actionState?.errors?.fieldErrors?.id_card?.[0] && ( - {actionState.errors.fieldErrors.id_card[0]} + {actionState?.errors.fieldErrors.id_card[0]} )} - {actionState.db_error === "id_card" && ( + {actionState?.db_error === "id_card" && ( - {actionState.message} + {actionState?.message} )}
@@ -128,9 +128,9 @@ export default function SignUpForm({ atolls }: { atolls: AtollWithIslands[] }) { ))} - {actionState.errors?.fieldErrors?.atoll_id && ( + {actionState?.errors?.fieldErrors?.atoll_id && ( - {actionState.errors?.fieldErrors?.atoll_id} + {actionState?.errors?.fieldErrors?.atoll_id} )} @@ -153,9 +153,9 @@ export default function SignUpForm({ atolls }: { atolls: AtollWithIslands[] }) { ))} - {actionState.errors?.fieldErrors?.island_id && ( + {actionState?.errors?.fieldErrors?.island_id && ( - {actionState.errors?.fieldErrors?.island_id} + {actionState?.errors?.fieldErrors?.island_id} )} @@ -169,20 +169,20 @@ export default function SignUpForm({ atolls }: { atolls: AtollWithIslands[] }) { - {actionState.errors?.fieldErrors?.address && ( + {actionState?.errors?.fieldErrors?.address && ( - {actionState.errors?.fieldErrors?.address} + {actionState?.errors?.fieldErrors?.address} )} @@ -194,17 +194,17 @@ export default function SignUpForm({ atolls }: { atolls: AtollWithIslands[] }) { - {actionState.errors?.fieldErrors?.dob && ( + {actionState?.errors?.fieldErrors?.dob && ( - {actionState.errors?.fieldErrors?.dob} + {actionState?.errors?.fieldErrors?.dob} )} @@ -216,17 +216,17 @@ export default function SignUpForm({ atolls }: { atolls: AtollWithIslands[] }) { - {actionState.errors?.fieldErrors.accNo && ( + {actionState?.errors?.fieldErrors.accNo && ( - {actionState.errors?.fieldErrors.accNo} + {actionState?.errors?.fieldErrors.accNo} )} @@ -241,7 +241,7 @@ export default function SignUpForm({ atolls }: { atolls: AtollWithIslands[] }) { disabled={isPending} className={cn( !phoneNumberFromUrl && - actionState.errors?.fieldErrors?.phone_number && + actionState?.errors?.fieldErrors?.phone_number && "border-2 border-red-500 rounded-md", )} defaultValue={NUMBER_WITHOUT_DASH ?? ""} @@ -251,19 +251,19 @@ export default function SignUpForm({ atolls }: { atolls: AtollWithIslands[] }) { {actionState?.errors?.fieldErrors?.phone_number?.[0] && ( - {actionState.errors.fieldErrors.phone_number[0]} + {actionState?.errors.fieldErrors.phone_number[0]} )} - {actionState.db_error === "phone_number" && ( + {actionState?.db_error === "phone_number" && ( - {actionState.message} + {actionState?.message} )}
- {actionState.errors?.fieldErrors?.terms && ( + {actionState?.errors?.fieldErrors?.terms && ( - {actionState.errors?.fieldErrors?.terms} + {actionState?.errors?.fieldErrors?.terms} )}
- {actionState.errors?.fieldErrors?.policy && ( + {actionState?.errors?.fieldErrors?.policy && ( - {actionState.errors?.fieldErrors?.policy} + {actionState?.errors?.fieldErrors?.policy} )} diff --git a/components/input-read-only.tsx b/components/input-read-only.tsx index 06af355..a9335de 100644 --- a/components/input-read-only.tsx +++ b/components/input-read-only.tsx @@ -1,9 +1,10 @@ +import { cn } from '@/lib/utils' import React from 'react' -export default function InputReadOnly({ label, value }: { label: string, value?: string }) { +export default function InputReadOnly({ label, value, labelClassName, className }: { label: string, value: string, labelClassName?: string, className?: string }) { return ( -
-