feat: add reCAPTCHA, reset-password, sort tickets, update dates
- Add reCAPTCHA v2 to registration form - Add reset-password page for password recovery - Fix forgot-password to call real API - Sort employee pending tickets (most recent first) - Update contest dates (validation: Dec 1-31, recovery: Dec 1 - Jan 31) - Update draw date to Feb 1, 2026 - Improve GamePeriod and GrandPrize components design 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
e0330d4f28
commit
9013551659
3
.env
3
.env
|
|
@ -14,3 +14,6 @@ NEXTAUTH_SECRET=dev-secret-key-change-in-production
|
|||
# OAuth Providers
|
||||
NEXT_PUBLIC_GOOGLE_CLIENT_ID=546665126481-itnlvt22hjn6t0bbgua0aj55h6dpplsk.apps.googleusercontent.com
|
||||
NEXT_PUBLIC_FACEBOOK_APP_ID=836681122652445
|
||||
|
||||
# reCAPTCHA v2
|
||||
NEXT_PUBLIC_RECAPTCHA_SITE_KEY=YOUR_RECAPTCHA_SITE_KEY
|
||||
|
|
|
|||
3
.env.dev
3
.env.dev
|
|
@ -14,3 +14,6 @@ NEXTAUTH_SECRET=dev-secret-key-change-in-production
|
|||
# OAuth Providers
|
||||
NEXT_PUBLIC_GOOGLE_CLIENT_ID=546665126481-itnlvt22hjn6t0bbgua0aj55h6dpplsk.apps.googleusercontent.com
|
||||
NEXT_PUBLIC_FACEBOOK_APP_ID=836681122652445
|
||||
|
||||
# reCAPTCHA v2
|
||||
NEXT_PUBLIC_RECAPTCHA_SITE_KEY=YOUR_RECAPTCHA_SITE_KEY
|
||||
|
|
|
|||
|
|
@ -14,3 +14,6 @@ NEXTAUTH_SECRET=4+mQhf1k0Ad7hsl35myygwELsw+/vDYsKtj2ovPdFQ0=
|
|||
# OAuth Configuration
|
||||
NEXT_PUBLIC_GOOGLE_CLIENT_ID=546665126481-itnlvt22hjn6t0bbgua0aj55h6dpplsk.apps.googleusercontent.com
|
||||
NEXT_PUBLIC_FACEBOOK_APP_ID=836681122652445
|
||||
|
||||
# reCAPTCHA v2
|
||||
NEXT_PUBLIC_RECAPTCHA_SITE_KEY=YOUR_RECAPTCHA_SITE_KEY
|
||||
|
|
|
|||
|
|
@ -14,3 +14,6 @@ NEXTAUTH_SECRET=MkeC4B5MOng3KE3uYipmpiuRv5zLkjpy6fcirJeKG8c=
|
|||
# OAuth Configuration
|
||||
NEXT_PUBLIC_GOOGLE_CLIENT_ID=546665126481-itnlvt22hjn6t0bbgua0aj55h6dpplsk.apps.googleusercontent.com
|
||||
NEXT_PUBLIC_FACEBOOK_APP_ID=836681122652445
|
||||
|
||||
# reCAPTCHA v2
|
||||
NEXT_PUBLIC_RECAPTCHA_SITE_KEY=YOUR_RECAPTCHA_SITE_KEY
|
||||
|
|
|
|||
|
|
@ -33,7 +33,13 @@ export default function EmployeeVerificationPage() {
|
|||
try {
|
||||
setLoading(true);
|
||||
const data = await employeeService.getPendingTickets();
|
||||
setTickets(data);
|
||||
// Trier par date décroissante (plus récent en premier)
|
||||
const sortedData = data.sort((a: Ticket, b: Ticket) => {
|
||||
const dateA = a.playedAt ? new Date(a.playedAt).getTime() : 0;
|
||||
const dateB = b.playedAt ? new Date(b.playedAt).getTime() : 0;
|
||||
return dateB - dateA;
|
||||
});
|
||||
setTickets(sortedData);
|
||||
} catch (error: any) {
|
||||
console.error("Error loading tickets:", error);
|
||||
toast.error("Erreur lors du chargement des tickets");
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
"use client";
|
||||
import { useState } from "react";
|
||||
import { useState, useRef } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useAuth } from "@/contexts/AuthContext";
|
||||
|
|
@ -7,6 +7,7 @@ import { registerSchema, RegisterFormData } from "@/lib/validations";
|
|||
import Link from "next/link";
|
||||
import TeaIconsBackground from "@/components/TeaIconsBackground";
|
||||
import { ROUTES } from "@/utils/constants";
|
||||
import ReCAPTCHA from "react-google-recaptcha";
|
||||
|
||||
export default function RegisterPage() {
|
||||
const { register: registerUser } = useAuth();
|
||||
|
|
@ -14,6 +15,9 @@ export default function RegisterPage() {
|
|||
const [showPassword, setShowPassword] = useState(false);
|
||||
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
|
||||
const [animationKey] = useState(Date.now());
|
||||
const [captchaToken, setCaptchaToken] = useState<string | null>(null);
|
||||
const [captchaError, setCaptchaError] = useState(false);
|
||||
const recaptchaRef = useRef<ReCAPTCHA>(null);
|
||||
|
||||
const {
|
||||
register,
|
||||
|
|
@ -23,12 +27,26 @@ export default function RegisterPage() {
|
|||
resolver: zodResolver(registerSchema),
|
||||
});
|
||||
|
||||
const onCaptchaChange = (token: string | null) => {
|
||||
setCaptchaToken(token);
|
||||
setCaptchaError(false);
|
||||
};
|
||||
|
||||
const onSubmit = async (data: RegisterFormData) => {
|
||||
// Vérifier le captcha
|
||||
if (!captchaToken) {
|
||||
setCaptchaError(true);
|
||||
return;
|
||||
}
|
||||
|
||||
setIsSubmitting(true);
|
||||
try {
|
||||
await registerUser(data);
|
||||
await registerUser({ ...data, captchaToken });
|
||||
} catch (error) {
|
||||
console.error("Registration error:", error);
|
||||
// Reset captcha en cas d'erreur
|
||||
recaptchaRef.current?.reset();
|
||||
setCaptchaToken(null);
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
|
|
@ -233,6 +251,19 @@ export default function RegisterPage() {
|
|||
</label>
|
||||
</div>
|
||||
|
||||
{/* reCAPTCHA */}
|
||||
<div className="flex flex-col items-center">
|
||||
<ReCAPTCHA
|
||||
ref={recaptchaRef}
|
||||
sitekey={process.env.NEXT_PUBLIC_RECAPTCHA_SITE_KEY || ""}
|
||||
onChange={onCaptchaChange}
|
||||
onExpired={() => setCaptchaToken(null)}
|
||||
/>
|
||||
{captchaError && (
|
||||
<p className="mt-2 text-sm text-red-500">Veuillez confirmer que vous n'êtes pas un robot</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Submit Button */}
|
||||
<button
|
||||
type="submit"
|
||||
|
|
|
|||
56
package-lock.json
generated
56
package-lock.json
generated
|
|
@ -10,6 +10,7 @@
|
|||
"dependencies": {
|
||||
"@hookform/resolvers": "^5.2.2",
|
||||
"@react-oauth/google": "^0.12.2",
|
||||
"@types/react-google-recaptcha": "^2.1.9",
|
||||
"axios": "^1.13.1",
|
||||
"bootstrap": "^5.3.3",
|
||||
"lucide-react": "^0.553.0",
|
||||
|
|
@ -19,6 +20,7 @@
|
|||
"prom-client": "^15.1.3",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-google-recaptcha": "^3.1.0",
|
||||
"react-hook-form": "^7.66.0",
|
||||
"react-hot-toast": "^2.6.0",
|
||||
"recharts": "^3.4.1",
|
||||
|
|
@ -2308,12 +2310,20 @@
|
|||
"version": "19.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.2.tgz",
|
||||
"integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"csstype": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-google-recaptcha": {
|
||||
"version": "2.1.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-google-recaptcha/-/react-google-recaptcha-2.1.9.tgz",
|
||||
"integrity": "sha512-nT31LrBDuoSZJN4QuwtQSF3O89FVHC4jLhM+NtKEmVF5R1e8OY0Jo4//x2Yapn2aNHguwgX5doAq8Zo+Ehd0ug==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/stack-utils": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz",
|
||||
|
|
@ -5678,6 +5688,21 @@
|
|||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/hoist-non-react-statics": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
|
||||
"integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"react-is": "^16.7.0"
|
||||
}
|
||||
},
|
||||
"node_modules/hoist-non-react-statics/node_modules/react-is": {
|
||||
"version": "16.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/html-encoding-sniffer": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz",
|
||||
|
|
@ -8099,7 +8124,6 @@
|
|||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
|
|
@ -8807,7 +8831,6 @@
|
|||
"version": "15.8.1",
|
||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.4.0",
|
||||
|
|
@ -8819,7 +8842,6 @@
|
|||
"version": "16.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/proxy-from-env": {
|
||||
|
|
@ -8888,6 +8910,19 @@
|
|||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-async-script": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/react-async-script/-/react-async-script-1.2.0.tgz",
|
||||
"integrity": "sha512-bCpkbm9JiAuMGhkqoAiC0lLkb40DJ0HOEJIku+9JDjxX3Rcs+ztEOG13wbrOskt3n2DTrjshhaQ/iay+SnGg5Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"hoist-non-react-statics": "^3.3.0",
|
||||
"prop-types": "^15.5.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.4.1"
|
||||
}
|
||||
},
|
||||
"node_modules/react-dom": {
|
||||
"version": "18.2.0",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
|
||||
|
|
@ -8901,6 +8936,19 @@
|
|||
"react": "^18.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-google-recaptcha": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/react-google-recaptcha/-/react-google-recaptcha-3.1.0.tgz",
|
||||
"integrity": "sha512-cYW2/DWas8nEKZGD7SCu9BSuVz8iOcOLHChHyi7upUuVhkpkhYG/6N3KDiTQ3XAiZ2UAZkfvYKMfAHOzBOcGEg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"prop-types": "^15.5.0",
|
||||
"react-async-script": "^1.2.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.4.1"
|
||||
}
|
||||
},
|
||||
"node_modules/react-hook-form": {
|
||||
"version": "7.66.1",
|
||||
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.66.1.tgz",
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
"dependencies": {
|
||||
"@hookform/resolvers": "^5.2.2",
|
||||
"@react-oauth/google": "^0.12.2",
|
||||
"@types/react-google-recaptcha": "^2.1.9",
|
||||
"axios": "^1.13.1",
|
||||
"bootstrap": "^5.3.3",
|
||||
"lucide-react": "^0.553.0",
|
||||
|
|
@ -22,6 +23,7 @@
|
|||
"prom-client": "^15.1.3",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-google-recaptcha": "^3.1.0",
|
||||
"react-hook-form": "^7.66.0",
|
||||
"react-hot-toast": "^2.6.0",
|
||||
"recharts": "^3.4.1",
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ export interface RegisterData {
|
|||
firstName: string;
|
||||
lastName: string;
|
||||
phone?: string;
|
||||
captchaToken?: string;
|
||||
}
|
||||
|
||||
export interface AuthResponse {
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user