diff --git a/client/components/shared/scanning-beam.tsx b/client/components/shared/scanning-beam.tsx new file mode 100644 index 0000000..598f336 --- /dev/null +++ b/client/components/shared/scanning-beam.tsx @@ -0,0 +1,38 @@ +"use client"; + +import React from "react"; +import { motion } from "framer-motion"; + +interface ScanningBeamProps { + duration?: number; + className?: string; +} + +export function ScanningBeam({ + duration = 1.2, + className = "", +}: ScanningBeamProps) { + return ( + +
+
+ +
+
+
+
+ +
+
+ + ); +} diff --git a/client/components/shared/verification-progress.tsx b/client/components/shared/verification-progress.tsx new file mode 100644 index 0000000..3ba138f --- /dev/null +++ b/client/components/shared/verification-progress.tsx @@ -0,0 +1,348 @@ +"use client"; + +import React, { useState, useEffect, useMemo, useRef } from "react"; +import { motion, AnimatePresence } from "framer-motion"; +import { + ShieldCheck, + Scan, + UserCheck, + Activity, + Loader2, + AlertCircle, + RefreshCw, +} from "lucide-react"; +import { Card, CardContent } from "@/components/ui/card"; +import { Badge } from "@/components/ui/badge"; +import { Progress } from "@/components/ui/progress"; +import { Button } from "@/components/ui/button"; +import { ScanningBeam } from "@/components/shared/scanning-beam"; + +// type definitions so that ui can't enter an undefined state +export type VerificationStatus = + | "idle" + | "scanning" + | "liveness" + | "analyzing" + | "completed" + | "failed"; + +interface VerificationProgressProps { + status?: VerificationStatus; + onComplete?: () => void; + isSimulated?: boolean; +} + +// stages +const STAGES = [ + { id: "scanning", label: "ID Scan", icon: Scan }, + { id: "liveness", label: "Liveness", icon: UserCheck }, + { id: "analyzing", label: "Analysis", icon: Activity }, +] as const; + +export function VerificationProgress({ + status: externalStatus = "idle", //the control + onComplete, + isSimulated = true, //set to false for control with props otherwise true if automated rendering +}: VerificationProgressProps) { + + const [mounted, setMounted] = useState(false); + const [internalStatus, setInternalStatus] = + useState("idle"); + const [resetKey, setResetKey] = useState(0); + + const onCompleteRef = useRef(onComplete); + + useEffect(() => { + onCompleteRef.current = onComplete; + }, [onComplete]); + + // Determine if we listen to the simulation or an external API prop + const currentStatus = isSimulated ? internalStatus : externalStatus; + + useEffect(() => { + setMounted(true); + }, []); + + // engine flow of simulation logic, this handles the automated transition of 3 stages + useEffect(() => { + // stop if not simulated, not mounted, or if already in completed state + if (!isSimulated || !mounted || internalStatus === "failed" || internalStatus === "completed") return; + + const sequence: VerificationStatus[] = [ + "scanning", + "liveness", + "analyzing", + "completed", + ]; + + // find current position in the sequence + let currentIdx = sequence.indexOf(internalStatus); + if (currentIdx === -1) currentIdx = 0; + + // instant feel + const timer = setInterval(() => { + const nextIdx = currentIdx + 1; + if (nextIdx < sequence.length) { + setInternalStatus(sequence[nextIdx]); + } else { + setInternalStatus("completed"); + // Accessing the stable ref ensures we don't trigger infinite loops in Next.js 16 + onCompleteRef.current?.(); + clearInterval(timer); + } + }, 800); + + return () => clearInterval(timer); + + // logic simulation integrity trigger + }, [isSimulated, mounted, resetKey, internalStatus]); + + const currentStepIndex = useMemo(() => { + // calculates the progress bar without rerunning on every render + const statusMap: Record = { + idle: 0, + scanning: 0, + liveness: 1, + analyzing: 2, + completed: 3, + failed: 1, // failure visually resets the progress to the liveness step + }; + return statusMap[currentStatus]; + }, [currentStatus]); + + if (!mounted) return null; + + return ( +
+ + + + {/* Active processing state - shown during scanning, liveness, and analysis stages */} + {currentStatus !== "completed" && currentStatus !== "failed" ? ( + + {/* Header */} +
+
+

+ Security Protocol +

+

+ Identity Verification +

+
+ + {currentStatus.toUpperCase()} + +
+ + {/* Stages and progress bar */} +
+ +
+ {STAGES.map((stage, idx) => { + const Icon = stage.icon; + const isActive = idx === currentStepIndex; + const isPassed = idx < currentStepIndex; + + return ( +
+
+ +
+ + {stage.label} + +
+ ); + })} +
+
+ + {/* scanner animation container */} +
+ +
+
+ + + {currentStatus === "scanning" && "SCANNING_DOCUMENT..."} + {currentStatus === "liveness" && + "MATCHING_BIOMETRICS..."} + {currentStatus === "analyzing" && + "COMPUTING_HASH_VERIFICATION..."} + +
+
+
+
+ ) : currentStatus === "completed" ? ( + /* Success state - Displays the pulsating green shield and verified status */ + +
+ {/* green glow pulse */} + + +
+ +
+
+ +
+ + Verified + + + Identity successfully verified. + +
+ + {/* Final state status badge with pulse verification */} + + + KYC_VERIFIED + + +
+ ) : ( + /* Failure state */ + +
+ +
+
+

+ Verification Failed +

+

+ Security mismatch detected during biometrics. +

+
+ +
+ )} +
+
+
+ + {/* Temporary manual testing for debugging controls */} +
+ + +
+
+ ); +} \ No newline at end of file