gebeya-whatsapp-otp
Version:
React WhatsApp OTP verification component with Supabase integration
91 lines (90 loc) โข 10.6 kB
JavaScript
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" })] })] }))] })] }) }));
};