iris_backend / src /components /VerificationModal.jsx
Muhammed Sameer
Initial commit - Iris Full (under development)
ea9ca44
// File: src/components/VerificationModal.js
import React, { useState, useEffect } from 'react';
import { supabase } from '../supabaseClient';
import { motion } from 'framer-motion';
export default function VerificationModal({ onClose, onVerified }) {
const [step, setStep] = useState(0); // 0 = Loading Profile, 1 = Confirm Send, 2 = Enter OTP
const [phone, setPhone] = useState('');
const [otp, setOtp] = useState('');
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
// 1. Fetch User's Phone on Mount
useEffect(() => {
const fetchUserProfile = async () => {
setError(null);
try {
const { data: { user } } = await supabase.auth.getUser();
if (!user) throw new Error("No user found.");
const { data: profile, error: profileError } = await supabase
.from('profiles')
.select('phone')
.eq('id', user.id)
.single();
if (profileError) throw profileError;
if (!profile.phone) {
setError("No phone number found in your profile. Please go to your Profile settings and add a phone number first.");
return;
}
setPhone(profile.phone);
setStep(1); // Ready to send
} catch (err) {
console.error("Profile Fetch Error:", err);
setError("Could not load profile details.");
}
};
fetchUserProfile();
}, []);
// 2. Helper to mask phone number (e.g., +1 ********99)
const maskPhone = (p) => {
if (!p || p.length < 5) return p;
return `${p.slice(0, 3)}****${p.slice(-3)}`;
};
// 3. Send OTP
const handleSendOtp = async () => {
setLoading(true);
setError(null);
try {
// Call the 'otp' Edge Function with action: 'send'
const { data, error } = await supabase.functions.invoke('otp', {
body: { action: 'send' }
});
if (error) throw error;
// Check for logic error from function
if (data?.error) throw new Error(data.error);
// For testing only (Remove in production)
if (data?.dev_otp) console.log("DEV OTP:", data.dev_otp);
setStep(2);
} catch (err) {
setError(err.message || "Failed to send OTP");
} finally {
setLoading(false);
}
};
// 4. Verify OTP
const handleVerifyOtp = async () => {
if (!otp) return setError("Please enter the code.");
setLoading(true);
setError(null);
try {
// Call the 'otp' Edge Function with action: 'verify'
const { data, error } = await supabase.functions.invoke('otp', {
body: {
action: 'verify',
userCode: otp
}
});
if (error) throw error;
if (data?.error) throw new Error(data.error);
// Success!
onVerified();
} catch (err) {
setError(err.message || "Invalid OTP");
} finally {
setLoading(false);
}
};
return (
<div style={{ position: 'fixed', inset: 0, backgroundColor: 'rgba(0,0,0,0.8)', display: 'grid', placeItems: 'center', zIndex: 100 }}>
<motion.div
initial={{ scale: 0.9, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
style={{ backgroundColor: '#111827', padding: '2rem', borderRadius: '1rem', width: '100%', maxWidth: '400px', border: '1px solid #374151' }}
>
{/* --- HEADER --- */}
<div style={{ textAlign: 'center', marginBottom: '1.5rem' }}>
<div style={{ display: 'inline-flex', alignItems: 'center', justifyContent: 'center', width: '3rem', height: '3rem', borderRadius: '50%', backgroundColor: 'rgba(245, 158, 11, 0.2)', color: '#f59e0b', marginBottom: '1rem' }}>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"></path></svg>
</div>
<h2 style={{ fontSize: '1.5rem', fontWeight: 'bold', color: 'white' }}>
{step === 2 ? 'Enter Code' : 'Verify Phone Number'}
</h2>
</div>
{/* --- ERROR MESSAGE --- */}
{error && (
<div style={{ backgroundColor: 'rgba(239, 68, 68, 0.1)', border: '1px solid rgba(239, 68, 68, 0.2)', padding: '0.75rem', borderRadius: '0.5rem', color: '#f87171', marginBottom: '1.5rem', fontSize: '0.875rem', textAlign: 'center' }}>
{error}
</div>
)}
{/* --- STEP 0: LOADING --- */}
{step === 0 && !error && (
<p style={{ textAlign: 'center', color: '#9ca3af' }}>Fetching contact details...</p>
)}
{/* --- STEP 1: CONFIRM PHONE --- */}
{step === 1 && (
<>
<div style={{ textAlign: 'center', marginBottom: '1.5rem', backgroundColor: '#1f2937', padding: '1rem', borderRadius: '0.5rem', border: '1px solid #374151' }}>
<p style={{ color: '#9ca3af', fontSize: '0.875rem', marginBottom: '0.5rem' }}>We will send a code to:</p>
<p style={{ color: 'white', fontSize: '1.25rem', fontWeight: 'bold', letterSpacing: '0.05em' }}>
{maskPhone(phone)}
</p>
</div>
<button
onClick={handleSendOtp}
disabled={loading}
style={{ width: '100%', padding: '0.75rem', backgroundColor: '#f59e0b', color: 'black', borderRadius: '0.5rem', fontWeight: 'bold', border: 'none', cursor: loading ? 'not-allowed' : 'pointer', opacity: loading ? 0.7 : 1 }}
>
{loading ? 'Sending...' : 'Send Verification Code'}
</button>
</>
)}
{/* --- STEP 2: ENTER OTP --- */}
{step === 2 && (
<>
<p style={{ color: '#9ca3af', textAlign: 'center', marginBottom: '1.5rem', fontSize: '0.875rem' }}>
Enter the 6-digit code sent to your phone.
</p>
<div style={{ marginBottom: '1.5rem' }}>
<input
type="text"
placeholder="000000"
maxLength={6}
value={otp}
onChange={(e) => setOtp(e.target.value)}
style={{ width: '100%', padding: '0.75rem', borderRadius: '0.5rem', backgroundColor: '#374151', color: 'white', border: '1px solid #4b5563', outline: 'none', textAlign: 'center', letterSpacing: '0.5em', fontSize: '1.5rem', fontWeight: 'bold' }}
/>
</div>
<button
onClick={handleVerifyOtp}
disabled={loading}
style={{ width: '100%', padding: '0.75rem', backgroundColor: '#10b981', color: 'white', borderRadius: '0.5rem', fontWeight: 'bold', border: 'none', cursor: loading ? 'not-allowed' : 'pointer', opacity: loading ? 0.7 : 1 }}
>
{loading ? 'Verifying...' : 'Verify & Apply'}
</button>
</>
)}
<button onClick={onClose} style={{ marginTop: '1.5rem', width: '100%', padding: '0.5rem', color: '#6b7280', background: 'none', border: 'none', cursor: 'pointer', fontSize: '0.875rem' }}>Cancel</button>
</motion.div>
</div>
);
}