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 { registerSchema, RegisterFormData } from "@/lib/validations";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import TeaIconsBackground from "@/components/TeaIconsBackground";
|
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";
|
import ReCAPTCHA from "react-google-recaptcha";
|
||||||
|
|
||||||
export default function RegisterPage() {
|
export default function RegisterPage() {
|
||||||
|
|
@ -19,6 +19,14 @@ export default function RegisterPage() {
|
||||||
const [captchaError, setCaptchaError] = useState(false);
|
const [captchaError, setCaptchaError] = useState(false);
|
||||||
const recaptchaRef = useRef<ReCAPTCHA>(null);
|
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 {
|
const {
|
||||||
register,
|
register,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
|
|
@ -32,7 +40,42 @@ export default function RegisterPage() {
|
||||||
setCaptchaError(false);
|
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) => {
|
const onSubmit = async (data: RegisterFormData) => {
|
||||||
|
// Vérifier si l'email existe déjà
|
||||||
|
if (emailStatus.exists) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Vérifier le captcha
|
// Vérifier le captcha
|
||||||
if (!captchaToken) {
|
if (!captchaToken) {
|
||||||
setCaptchaError(true);
|
setCaptchaError(true);
|
||||||
|
|
@ -129,16 +172,53 @@ export default function RegisterPage() {
|
||||||
<label htmlFor="email" className="block text-sm font-semibold text-gray-700 mb-2">
|
<label htmlFor="email" className="block text-sm font-semibold text-gray-700 mb-2">
|
||||||
Email <span className="text-red-500">*</span>
|
Email <span className="text-red-500">*</span>
|
||||||
</label>
|
</label>
|
||||||
<input
|
<div className="relative">
|
||||||
id="email"
|
<input
|
||||||
type="email"
|
id="email"
|
||||||
placeholder="email"
|
type="email"
|
||||||
{...register("email")}
|
placeholder="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`}
|
{...register("email")}
|
||||||
/>
|
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 && (
|
{errors.email && (
|
||||||
<p className="mt-1 text-sm text-red-500">{errors.email.message}</p>
|
<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>
|
</div>
|
||||||
|
|
||||||
{/* Téléphone */}
|
{/* Téléphone */}
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ export const API_ENDPOINTS = {
|
||||||
VERIFY_EMAIL: '/auth/verify-email',
|
VERIFY_EMAIL: '/auth/verify-email',
|
||||||
FORGOT_PASSWORD: '/auth/forgot-password',
|
FORGOT_PASSWORD: '/auth/forgot-password',
|
||||||
RESET_PASSWORD: '/auth/reset-password',
|
RESET_PASSWORD: '/auth/reset-password',
|
||||||
|
CHECK_EMAIL: '/auth/check-email',
|
||||||
},
|
},
|
||||||
GAME: {
|
GAME: {
|
||||||
PLAY: '/game/play',
|
PLAY: '/game/play',
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user