feat: add real-time email validation on registration
- Add CHECK_EMAIL endpoint to constants - Check email availability on blur - Show validation status (loading, valid, exists) - Block submission if email already exists - Display DNS MX validation result 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
391fa7b8c2
commit
d60c03cb0e
|
|
@ -6,7 +6,7 @@ import { useAuth } from "@/contexts/AuthContext";
|
|||
import { registerSchema, RegisterFormData } from "@/lib/validations";
|
||||
import Link from "next/link";
|
||||
import TeaIconsBackground from "@/components/TeaIconsBackground";
|
||||
import { ROUTES } from "@/utils/constants";
|
||||
import { ROUTES, API_BASE_URL, API_ENDPOINTS } from "@/utils/constants";
|
||||
import ReCAPTCHA from "react-google-recaptcha";
|
||||
|
||||
export default function RegisterPage() {
|
||||
|
|
@ -19,6 +19,14 @@ export default function RegisterPage() {
|
|||
const [captchaError, setCaptchaError] = useState(false);
|
||||
const recaptchaRef = useRef<ReCAPTCHA>(null);
|
||||
|
||||
// Email validation state
|
||||
const [emailStatus, setEmailStatus] = useState<{
|
||||
checking: boolean;
|
||||
exists: boolean | null;
|
||||
valid: boolean | null;
|
||||
message: string;
|
||||
}>({ checking: false, exists: null, valid: null, message: '' });
|
||||
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
|
|
@ -32,7 +40,42 @@ export default function RegisterPage() {
|
|||
setCaptchaError(false);
|
||||
};
|
||||
|
||||
// Vérifier si l'email existe déjà
|
||||
const checkEmail = async (email: string) => {
|
||||
if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
|
||||
setEmailStatus({ checking: false, exists: null, valid: null, message: '' });
|
||||
return;
|
||||
}
|
||||
|
||||
setEmailStatus({ checking: true, exists: null, valid: null, message: 'Vérification...' });
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}${API_ENDPOINTS.AUTH.CHECK_EMAIL}`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ email }),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
setEmailStatus({
|
||||
checking: false,
|
||||
exists: data.exists,
|
||||
valid: data.isValid,
|
||||
message: data.message,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error checking email:', error);
|
||||
setEmailStatus({ checking: false, exists: null, valid: null, message: '' });
|
||||
}
|
||||
};
|
||||
|
||||
const onSubmit = async (data: RegisterFormData) => {
|
||||
// Vérifier si l'email existe déjà
|
||||
if (emailStatus.exists) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Vérifier le captcha
|
||||
if (!captchaToken) {
|
||||
setCaptchaError(true);
|
||||
|
|
@ -129,16 +172,53 @@ export default function RegisterPage() {
|
|||
<label htmlFor="email" className="block text-sm font-semibold text-gray-700 mb-2">
|
||||
Email <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<div className="relative">
|
||||
<input
|
||||
id="email"
|
||||
type="email"
|
||||
placeholder="email"
|
||||
{...register("email")}
|
||||
className={`w-full px-4 py-3 border ${errors.email ? 'border-red-500' : 'border-gray-300'} rounded-lg focus:outline-none focus:ring-2 focus:ring-[#d4a574] focus:border-transparent`}
|
||||
onBlur={(e) => checkEmail(e.target.value)}
|
||||
className={`w-full px-4 py-3 border ${
|
||||
errors.email || emailStatus.exists
|
||||
? 'border-red-500'
|
||||
: emailStatus.valid
|
||||
? 'border-green-500'
|
||||
: 'border-gray-300'
|
||||
} rounded-lg focus:outline-none focus:ring-2 focus:ring-[#d4a574] focus:border-transparent pr-10`}
|
||||
/>
|
||||
{/* Status icon */}
|
||||
{emailStatus.checking && (
|
||||
<div className="absolute right-3 top-1/2 -translate-y-1/2">
|
||||
<svg className="animate-spin h-5 w-5 text-gray-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
|
||||
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
)}
|
||||
{!emailStatus.checking && emailStatus.valid && (
|
||||
<div className="absolute right-3 top-1/2 -translate-y-1/2">
|
||||
<svg className="h-5 w-5 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
|
||||
</svg>
|
||||
</div>
|
||||
)}
|
||||
{!emailStatus.checking && emailStatus.exists && (
|
||||
<div className="absolute right-3 top-1/2 -translate-y-1/2">
|
||||
<svg className="h-5 w-5 text-red-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{errors.email && (
|
||||
<p className="mt-1 text-sm text-red-500">{errors.email.message}</p>
|
||||
)}
|
||||
{!errors.email && emailStatus.message && (
|
||||
<p className={`mt-1 text-sm ${emailStatus.exists ? 'text-red-500' : emailStatus.valid ? 'text-green-600' : 'text-gray-500'}`}>
|
||||
{emailStatus.message}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Téléphone */}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ export const API_ENDPOINTS = {
|
|||
VERIFY_EMAIL: '/auth/verify-email',
|
||||
FORGOT_PASSWORD: '/auth/forgot-password',
|
||||
RESET_PASSWORD: '/auth/reset-password',
|
||||
CHECK_EMAIL: '/auth/check-email',
|
||||
},
|
||||
GAME: {
|
||||
PLAY: '/game/play',
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user