Enhance user verification and data validation features

- Updated `next.config.ts` to include remote image patterns for user verification.
- Introduced `VerifyUserDetails` function in `lib/person.ts` to validate user data against national records.
- Added `usePerson` hook for fetching national data based on ID card.
- Enhanced `signup` and `signin` functions in `auth-actions.ts` to handle user verification status and send notifications for pending verifications.
- Refactored `VerifyUser` function in `user-actions.ts` to incorporate national data validation.
- Improved UI components in the user verification page to display both database and national information.
- Updated `InputReadOnly` component to support customizable label classes for better styling.

These changes improve the user verification process, ensuring data integrity and enhancing the overall user experience.
This commit is contained in:
2025-01-10 15:59:44 +05:00
parent 1a195d2307
commit ff0eae6ec4
12 changed files with 253 additions and 123 deletions

View File

@ -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<AtollWithIslands>();
const [islands, setIslands] = React.useState<Island[]>();
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 (
<form
action={action}
@ -59,17 +59,17 @@ export default function SignUpForm({ atolls }: { atolls: AtollWithIslands[] }) {
<Input
className={cn(
"text-base",
actionState.errors?.fieldErrors.name && "border-2 border-red-500",
actionState?.errors?.fieldErrors.name && "border-2 border-red-500",
)}
name="name"
type="text"
disabled={isPending}
defaultValue={(actionState.payload?.get("name") || "") as string}
defaultValue={(actionState?.payload?.get("name") || "") as string}
placeholder="Full Name"
/>
{actionState.errors?.fieldErrors.name && (
{actionState?.errors?.fieldErrors.name && (
<span className="text-sm inline-block text-red-500">
{actionState.errors?.fieldErrors.name}
{actionState?.errors?.fieldErrors.name}
</span>
)}
</div>
@ -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] && (
<span className="text-sm inline-block text-red-500">
{actionState.errors.fieldErrors.id_card[0]}
{actionState?.errors.fieldErrors.id_card[0]}
</span>
)}
{actionState.db_error === "id_card" && (
{actionState?.db_error === "id_card" && (
<span className="text-sm inline-block text-red-500">
{actionState.message}
{actionState?.message}
</span>
)}
</div>
@ -128,9 +128,9 @@ export default function SignUpForm({ atolls }: { atolls: AtollWithIslands[] }) {
))}
</SelectGroup>
</SelectContent>
{actionState.errors?.fieldErrors?.atoll_id && (
{actionState?.errors?.fieldErrors?.atoll_id && (
<span className="text-sm inline-block text-red-500">
{actionState.errors?.fieldErrors?.atoll_id}
{actionState?.errors?.fieldErrors?.atoll_id}
</span>
)}
</Select>
@ -153,9 +153,9 @@ export default function SignUpForm({ atolls }: { atolls: AtollWithIslands[] }) {
))}
</SelectGroup>
</SelectContent>
{actionState.errors?.fieldErrors?.island_id && (
{actionState?.errors?.fieldErrors?.island_id && (
<span className="text-sm inline-block text-red-500">
{actionState.errors?.fieldErrors?.island_id}
{actionState?.errors?.fieldErrors?.island_id}
</span>
)}
</Select>
@ -169,20 +169,20 @@ export default function SignUpForm({ atolls }: { atolls: AtollWithIslands[] }) {
<Input
className={cn(
"text-base",
actionState.errors?.fieldErrors?.address &&
actionState?.errors?.fieldErrors?.address &&
"border-2 border-red-500",
)}
disabled={isPending}
name="address"
defaultValue={
(actionState.payload?.get("address") || "") as string
(actionState?.payload?.get("address") || "") as string
}
type="text"
placeholder="Address"
/>
{actionState.errors?.fieldErrors?.address && (
{actionState?.errors?.fieldErrors?.address && (
<span className="text-sm inline-block text-red-500">
{actionState.errors?.fieldErrors?.address}
{actionState?.errors?.fieldErrors?.address}
</span>
)}
</div>
@ -194,17 +194,17 @@ export default function SignUpForm({ atolls }: { atolls: AtollWithIslands[] }) {
<Input
className={cn(
"text-base",
actionState.errors?.fieldErrors?.dob && "border-2 border-red-500",
actionState?.errors?.fieldErrors?.dob && "border-2 border-red-500",
)}
name="dob"
disabled={isPending}
defaultValue={(actionState.payload?.get("dob") || "") as string}
defaultValue={(actionState?.payload?.get("dob") || "") as string}
type="date"
placeholder="Date of birth"
/>
{actionState.errors?.fieldErrors?.dob && (
{actionState?.errors?.fieldErrors?.dob && (
<span className="text-sm inline-block text-red-500">
{actionState.errors?.fieldErrors?.dob}
{actionState?.errors?.fieldErrors?.dob}
</span>
)}
</div>
@ -216,17 +216,17 @@ export default function SignUpForm({ atolls }: { atolls: AtollWithIslands[] }) {
<Input
className={cn(
"text-base",
actionState.errors?.fieldErrors.accNo && "border-2 border-red-500",
actionState?.errors?.fieldErrors.accNo && "border-2 border-red-500",
)}
name="accNo"
type="number"
disabled={isPending}
defaultValue={(actionState.payload?.get("accNo") || "") as string}
defaultValue={(actionState?.payload?.get("accNo") || "") as string}
placeholder="Account no"
/>
{actionState.errors?.fieldErrors.accNo && (
{actionState?.errors?.fieldErrors.accNo && (
<span className="text-sm inline-block text-red-500">
{actionState.errors?.fieldErrors.accNo}
{actionState?.errors?.fieldErrors.accNo}
</span>
)}
</div>
@ -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[] }) {
</div>
{actionState?.errors?.fieldErrors?.phone_number?.[0] && (
<span className="text-sm inline-block text-red-500">
{actionState.errors.fieldErrors.phone_number[0]}
{actionState?.errors.fieldErrors.phone_number[0]}
</span>
)}
{actionState.db_error === "phone_number" && (
{actionState?.db_error === "phone_number" && (
<span className="text-sm inline-block text-red-500">
{actionState.message}
{actionState?.message}
</span>
)}
<div className="flex flex-col gap-2 items-start justify-start py-2">
<div className="flex gap-2 items-center">
<input
type="checkbox"
defaultChecked={(actionState.payload?.get("terms") || "") as string === 'on'}
defaultChecked={(actionState?.payload?.get("terms") || "") as string === 'on'}
name="terms" id="terms" />
<label
htmlFor="terms"
@ -277,16 +277,16 @@ export default function SignUpForm({ atolls }: { atolls: AtollWithIslands[] }) {
</Link>
</label>
</div>
{actionState.errors?.fieldErrors?.terms && (
{actionState?.errors?.fieldErrors?.terms && (
<span className="text-sm inline-block text-red-500">
{actionState.errors?.fieldErrors?.terms}
{actionState?.errors?.fieldErrors?.terms}
</span>
)}
<div className="flex gap-2 items-center">
<input
type="checkbox"
defaultChecked={(actionState.payload?.get("policy") || "") as string === 'on'}
defaultChecked={(actionState?.payload?.get("policy") || "") as string === 'on'}
name="policy" id="terms" />
<label
htmlFor="terms"
@ -300,9 +300,9 @@ export default function SignUpForm({ atolls }: { atolls: AtollWithIslands[] }) {
</Link>
</label>
</div>
{actionState.errors?.fieldErrors?.policy && (
{actionState?.errors?.fieldErrors?.policy && (
<span className="text-sm inline-block text-red-500">
{actionState.errors?.fieldErrors?.policy}
{actionState?.errors?.fieldErrors?.policy}
</span>
)}