UNPKG

gebeya-whatsapp-otp

Version:

React WhatsApp OTP verification component with Supabase integration

181 lines (180 loc) 7.12 kB
import { useState, useCallback } from "react"; export const useWhatsAppOTP = (config, callbacks) => { const [step, setStep] = useState("phone"); const [countryCode, setCountryCode] = useState(config.defaultCountry || "+251"); const [phoneNumber, setPhoneNumber] = useState(""); const [otpCode, setOtpCode] = useState(""); const [isLoading, setIsLoading] = useState(false); const [attemptsLeft, setAttemptsLeft] = useState(3); const [maxAttemptsReached, setMaxAttemptsReached] = useState(false); const [isSuspended, setIsSuspended] = useState(false); const [suspensionMessage, setSuspensionMessage] = useState(""); const [isModalOpen, setIsModalOpen] = useState(false); const sendOTP = useCallback(async () => { if (!phoneNumber) return; setIsLoading(true); const fullNumber = countryCode + phoneNumber; try { const response = await fetch(`${config.supabaseUrl}/functions/v1/otp_whatsapp`, { method: "POST", headers: { Authorization: `Bearer ${config.supabaseKey}`, "Content-Type": "application/json", apikey: config.supabaseKey, }, body: JSON.stringify({ action: "send_otp", phone_number: fullNumber, }), }); const data = await response.json(); if (response.ok && data.success) { setStep("verify"); setAttemptsLeft(3); setMaxAttemptsReached(false); setIsSuspended(false); setSuspensionMessage(""); callbacks?.onStepChange?.("verify"); } else { throw new Error(data.error || "Failed to send OTP"); } } catch (error) { const errorMessage = error instanceof Error ? error.message : "Failed to send OTP"; if (errorMessage.includes("temporarily suspended")) { setIsSuspended(true); setSuspensionMessage(errorMessage); callbacks?.onSuspension?.(errorMessage); } callbacks?.onError?.(errorMessage); } finally { setIsLoading(false); } }, [phoneNumber, countryCode, config, callbacks]); const verifyOTP = useCallback(async () => { if (!otpCode) return; setIsLoading(true); const fullNumber = countryCode + phoneNumber; try { const response = await fetch(`${config.supabaseUrl}/functions/v1/otp_whatsapp`, { method: "POST", headers: { Authorization: `Bearer ${config.supabaseKey}`, "Content-Type": "application/json", apikey: config.supabaseKey, }, body: JSON.stringify({ action: "verify_otp", phone_number: fullNumber, code: otpCode, redirect_url: config.redirectUrl || "/dashboard", }), }); const data = await response.json(); if (response.ok && data?.success) { setStep("success"); callbacks?.onStepChange?.("success"); callbacks?.onSuccess?.(fullNumber); // Handle authentication and redirect if (data.auth_url) { console.log("Using auth URL for authentication:", data.auth_url); // Use the auth URL for automatic authentication - this will establish the Supabase session window.location.href = data.auth_url; } else if (data.redirect_url) { console.log("Redirecting to:", data.redirect_url); // Fallback to redirect URL setTimeout(() => { window.location.href = data.redirect_url; }, 2000); // Give user time to see success message } // Store session data for reference if (data.access_token) { localStorage.setItem("whatsapp_otp_session", JSON.stringify({ access_token: data.access_token, refresh_token: data.refresh_token, user: data.user, verified_at: new Date().toISOString(), })); } } else { const errorMessage = data?.error || "Invalid or expired OTP code"; if (errorMessage.includes("temporarily suspended")) { setIsSuspended(true); setSuspensionMessage(errorMessage); setMaxAttemptsReached(true); setAttemptsLeft(0); callbacks?.onSuspension?.(errorMessage); } else if (errorMessage.includes("Maximum attempts reached")) { setMaxAttemptsReached(true); setAttemptsLeft(0); setIsSuspended(true); setSuspensionMessage(errorMessage); callbacks?.onSuspension?.(errorMessage); } else if (errorMessage.includes("attempts remaining")) { const match = errorMessage.match(/(\d+)\s+attempts?\s+remaining/); if (match) { const remaining = parseInt(match[1]); setAttemptsLeft(remaining); } } callbacks?.onError?.(errorMessage); setOtpCode(""); } } catch (error) { const errorMessage = error instanceof Error ? error.message : "Something went wrong"; callbacks?.onError?.(errorMessage); setOtpCode(""); } finally { setIsLoading(false); } }, [otpCode, phoneNumber, countryCode, config, callbacks]); const resetFlow = useCallback(() => { setStep("phone"); setPhoneNumber(""); setOtpCode(""); setAttemptsLeft(3); setMaxAttemptsReached(false); setIsSuspended(false); setSuspensionMessage(""); callbacks?.onStepChange?.("phone"); }, [callbacks]); const openModal = useCallback(() => { setIsModalOpen(true); }, []); const closeModal = useCallback(() => { setIsModalOpen(false); resetFlow(); }, [resetFlow]); return { // State step, countryCode, phoneNumber, otpCode, isLoading, attemptsLeft, maxAttemptsReached, isSuspended, suspensionMessage, isModalOpen, // Actions setCountryCode, setPhoneNumber, setOtpCode, sendOTP, verifyOTP, resetFlow, openModal, closeModal, }; };