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:
soufiane 2025-12-02 21:16:34 +01:00
parent 391fa7b8c2
commit d60c03cb0e
2 changed files with 89 additions and 8 deletions

View File

@ -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 */}

View File

@ -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',