UNPKG

gebeya-whatsapp-otp

Version:

React WhatsApp OTP verification component with Supabase integration

91 lines (90 loc) โ€ข 10.6 kB
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime"; import { Dialog, DialogContent, DialogHeader, DialogTitle } from "./ui/dialog"; import { Button } from "./ui/button"; import { Input } from "./ui/input"; import { Label } from "./ui/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "./ui/select"; import { Phone, CheckCircle, X } from "lucide-react"; import { useWhatsAppOTPContext } from "./WhatsAppOTPProvider"; // Comprehensive African Countries Dataset - All 54 African Union member countries // Organized alphabetically by country name with accurate ITU-T E.164 dialing codes const defaultCountries = [ { code: "+213", country: "Algeria", flag: "๐Ÿ‡ฉ๐Ÿ‡ฟ" }, { code: "+244", country: "Angola", flag: "๐Ÿ‡ฆ๐Ÿ‡ด" }, { code: "+229", country: "Benin", flag: "๐Ÿ‡ง๐Ÿ‡ฏ" }, { code: "+267", country: "Botswana", flag: "๐Ÿ‡ง๐Ÿ‡ผ" }, { code: "+226", country: "Burkina Faso", flag: "๐Ÿ‡ง๐Ÿ‡ซ" }, { code: "+257", country: "Burundi", flag: "๐Ÿ‡ง๐Ÿ‡ฎ" }, { code: "+237", country: "Cameroon", flag: "๐Ÿ‡จ๐Ÿ‡ฒ" }, { code: "+238", country: "Cape Verde", flag: "๐Ÿ‡จ๐Ÿ‡ป" }, { code: "+236", country: "Central African Republic", flag: "๐Ÿ‡จ๐Ÿ‡ซ" }, { code: "+235", country: "Chad", flag: "๐Ÿ‡น๐Ÿ‡ฉ" }, { code: "+269", country: "Comoros", flag: "๐Ÿ‡ฐ๐Ÿ‡ฒ" }, { code: "+225", country: "Cรดte d'Ivoire", flag: "๐Ÿ‡จ๐Ÿ‡ฎ" }, { code: "+243", country: "Democratic Republic of the Congo", flag: "๐Ÿ‡จ๐Ÿ‡ฉ" }, { code: "+253", country: "Djibouti", flag: "๐Ÿ‡ฉ๐Ÿ‡ฏ" }, { code: "+20", country: "Egypt", flag: "๐Ÿ‡ช๐Ÿ‡ฌ" }, { code: "+240", country: "Equatorial Guinea", flag: "๐Ÿ‡ฌ๐Ÿ‡ถ" }, { code: "+291", country: "Eritrea", flag: "๐Ÿ‡ช๐Ÿ‡ท" }, { code: "+268", country: "Eswatini", flag: "๐Ÿ‡ธ๐Ÿ‡ฟ" }, { code: "+251", country: "Ethiopia", flag: "๐Ÿ‡ช๐Ÿ‡น" }, { code: "+241", country: "Gabon", flag: "๐Ÿ‡ฌ๐Ÿ‡ฆ" }, { code: "+220", country: "Gambia", flag: "๐Ÿ‡ฌ๐Ÿ‡ฒ" }, { code: "+233", country: "Ghana", flag: "๐Ÿ‡ฌ๐Ÿ‡ญ" }, { code: "+224", country: "Guinea", flag: "๐Ÿ‡ฌ๐Ÿ‡ณ" }, { code: "+245", country: "Guinea-Bissau", flag: "๐Ÿ‡ฌ๐Ÿ‡ผ" }, { code: "+254", country: "Kenya", flag: "๐Ÿ‡ฐ๐Ÿ‡ช" }, { code: "+266", country: "Lesotho", flag: "๐Ÿ‡ฑ๐Ÿ‡ธ" }, { code: "+231", country: "Liberia", flag: "๐Ÿ‡ฑ๐Ÿ‡ท" }, { code: "+218", country: "Libya", flag: "๐Ÿ‡ฑ๐Ÿ‡พ" }, { code: "+261", country: "Madagascar", flag: "๐Ÿ‡ฒ๐Ÿ‡ฌ" }, { code: "+265", country: "Malawi", flag: "๐Ÿ‡ฒ๐Ÿ‡ผ" }, { code: "+223", country: "Mali", flag: "๐Ÿ‡ฒ๐Ÿ‡ฑ" }, { code: "+222", country: "Mauritania", flag: "๐Ÿ‡ฒ๐Ÿ‡ท" }, { code: "+230", country: "Mauritius", flag: "๐Ÿ‡ฒ๐Ÿ‡บ" }, { code: "+212", country: "Morocco", flag: "๐Ÿ‡ฒ๐Ÿ‡ฆ" }, { code: "+258", country: "Mozambique", flag: "๐Ÿ‡ฒ๐Ÿ‡ฟ" }, { code: "+264", country: "Namibia", flag: "๐Ÿ‡ณ๐Ÿ‡ฆ" }, { code: "+227", country: "Niger", flag: "๐Ÿ‡ณ๐Ÿ‡ช" }, { code: "+234", country: "Nigeria", flag: "๐Ÿ‡ณ๐Ÿ‡ฌ" }, { code: "+242", country: "Republic of the Congo", flag: "๐Ÿ‡จ๐Ÿ‡ฌ" }, { code: "+250", country: "Rwanda", flag: "๐Ÿ‡ท๐Ÿ‡ผ" }, { code: "+239", country: "Sรฃo Tomรฉ and Prรญncipe", flag: "๐Ÿ‡ธ๐Ÿ‡น" }, { code: "+221", country: "Senegal", flag: "๐Ÿ‡ธ๐Ÿ‡ณ" }, { code: "+248", country: "Seychelles", flag: "๐Ÿ‡ธ๐Ÿ‡จ" }, { code: "+232", country: "Sierra Leone", flag: "๐Ÿ‡ธ๐Ÿ‡ฑ" }, { code: "+252", country: "Somalia", flag: "๐Ÿ‡ธ๐Ÿ‡ด" }, { code: "+27", country: "South Africa", flag: "๐Ÿ‡ฟ๐Ÿ‡ฆ" }, { code: "+211", country: "South Sudan", flag: "๐Ÿ‡ธ๐Ÿ‡ธ" }, { code: "+249", country: "Sudan", flag: "๐Ÿ‡ธ๐Ÿ‡ฉ" }, { code: "+255", country: "Tanzania", flag: "๐Ÿ‡น๐Ÿ‡ฟ" }, { code: "+228", country: "Togo", flag: "๐Ÿ‡น๐Ÿ‡ฌ" }, { code: "+216", country: "Tunisia", flag: "๐Ÿ‡น๐Ÿ‡ณ" }, { code: "+256", country: "Uganda", flag: "๐Ÿ‡บ๐Ÿ‡ฌ" }, { code: "+260", country: "Zambia", flag: "๐Ÿ‡ฟ๐Ÿ‡ฒ" }, { code: "+263", country: "Zimbabwe", flag: "๐Ÿ‡ฟ๐Ÿ‡ผ" }, ]; export const WhatsAppOTPModal = ({ countries = defaultCountries, }) => { const { isModalOpen, closeModal, step, countryCode, phoneNumber, otpCode, isLoading, attemptsLeft, maxAttemptsReached, isSuspended, suspensionMessage, setCountryCode, setPhoneNumber, setOtpCode, sendOTP, verifyOTP, resetFlow, } = useWhatsAppOTPContext(); const handleBackToPhone = () => { resetFlow(); }; return (_jsx(Dialog, { open: isModalOpen, onOpenChange: closeModal, children: _jsxs(DialogContent, { className: "sm:max-w-md", children: [_jsx(DialogHeader, { children: _jsxs(DialogTitle, { className: "flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Phone, { className: "h-5 w-5 text-green-600" }), step === "phone" && "WhatsApp OTP", step === "verify" && "Enter Verification Code", step === "success" && "Phone Verified!"] }), _jsx(Button, { variant: "ghost", size: "icon", onClick: closeModal, children: _jsx(X, { className: "h-4 w-4" }) })] }) }), _jsxs("div", { className: "space-y-4", children: [step === "phone" && (_jsxs(_Fragment, { children: [_jsx("p", { className: "text-sm text-muted-foreground", children: "Enter your phone number to receive a verification code via WhatsApp" }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { htmlFor: "country", children: "Country" }), _jsxs(Select, { value: countryCode, onValueChange: setCountryCode, searchable: true, placeholder: "", children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, { children: countryCode ? (_jsxs("span", { className: "flex items-center gap-2", children: [_jsx("span", { children: countries.find((c) => c.code === countryCode) ?.flag }), _jsxs("span", { children: [countries.find((c) => c.code === countryCode) ?.country, " ", "(", countryCode, ")"] })] })) : ("") }) }), _jsx(SelectContent, { children: countries.map((country) => (_jsx(SelectItem, { value: country.code, searchText: `${country.country} ${country.code}`, children: _jsxs("span", { className: "flex items-center gap-2", children: [_jsx("span", { children: country.flag }), _jsxs("span", { children: [country.country, " (", country.code, ")"] })] }) }, country.code))) })] })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { htmlFor: "phone", children: "Phone Number" }), _jsxs("div", { className: "flex gap-2", children: [_jsx("div", { className: "flex items-center px-3 py-2 border rounded-md bg-muted text-muted-foreground", children: countryCode }), _jsx(Input, { id: "phone", type: "tel", placeholder: "", value: phoneNumber, onChange: (e) => setPhoneNumber(e.target.value.replace(/\D/g, "")), className: "flex-1" })] })] }), _jsx(Button, { onClick: sendOTP, className: "w-full", disabled: !phoneNumber || isLoading || isSuspended, children: isLoading ? "Sending..." : isSuspended ? "Phone Suspended" : "Send OTP via WhatsApp" })] })), step === "verify" && (_jsxs(_Fragment, { children: [_jsx("p", { className: "text-sm text-muted-foreground", children: "Enter the 6-digit code sent to your WhatsApp" }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { htmlFor: "otp", children: "Verification Code" }), _jsx(Input, { id: "otp", type: "text", placeholder: "", value: otpCode, onChange: (e) => setOtpCode(e.target.value.replace(/\D/g, "").slice(0, 6)), maxLength: 6, className: "text-center text-lg tracking-widest", disabled: maxAttemptsReached || isSuspended })] }), _jsx("div", { className: "text-center", children: isSuspended ? (_jsxs("div", { className: "p-3 bg-destructive/10 border border-destructive/20 rounded-md", children: [_jsx("p", { className: "text-destructive font-medium mb-1", children: "Phone Number Suspended" }), _jsx("p", { className: "text-sm text-muted-foreground", children: suspensionMessage })] })) : maxAttemptsReached ? (_jsxs("div", { className: "p-3 bg-destructive/10 border border-destructive/20 rounded-md", children: [_jsx("p", { className: "text-destructive font-medium", children: "Maximum attempts reached" }), _jsx("p", { className: "text-sm text-muted-foreground", children: "Please request a new OTP code" })] })) : (_jsx("div", { className: `p-2 rounded-md ${attemptsLeft === 1 ? "bg-destructive/10 border border-destructive/20" : attemptsLeft === 2 ? "bg-yellow-50 border border-yellow-200 dark:bg-yellow-900/20 dark:border-yellow-800" : "bg-muted/50"}`, children: _jsxs("p", { className: `text-sm font-medium ${attemptsLeft === 1 ? "text-destructive" : attemptsLeft === 2 ? "text-yellow-700 dark:text-yellow-300" : "text-muted-foreground"}`, children: [attemptsLeft, " ", attemptsLeft === 1 ? "attempt" : "attempts", " remaining"] }) })) }), _jsxs("div", { className: "flex gap-2", children: [_jsx(Button, { variant: "outline", onClick: handleBackToPhone, className: "flex-1", children: isSuspended || maxAttemptsReached ? "Get New Code" : "Back" }), _jsx(Button, { onClick: verifyOTP, className: "flex-1", disabled: otpCode.length !== 6 || isLoading || maxAttemptsReached || isSuspended, children: isLoading ? "Verifying..." : "Verify Code" })] })] })), step === "success" && (_jsxs("div", { className: "text-center space-y-4", children: [_jsx(CheckCircle, { className: "h-16 w-16 text-green-600 mx-auto" }), _jsx("p", { className: "text-lg font-semibold", children: "Phone number verified!" }), _jsxs("p", { className: "text-muted-foreground", children: [countryCode, phoneNumber, " has been successfully verified"] }), _jsxs("div", { className: "flex gap-2", children: [_jsx(Button, { variant: "outline", onClick: closeModal, className: "flex-1", children: "Close" }), _jsx(Button, { onClick: handleBackToPhone, className: "flex-1", children: "Verify Another" })] })] }))] })] }) })); };