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
|
# OAuth Providers
|
||||||
NEXT_PUBLIC_GOOGLE_CLIENT_ID=546665126481-itnlvt22hjn6t0bbgua0aj55h6dpplsk.apps.googleusercontent.com
|
NEXT_PUBLIC_GOOGLE_CLIENT_ID=546665126481-itnlvt22hjn6t0bbgua0aj55h6dpplsk.apps.googleusercontent.com
|
||||||
NEXT_PUBLIC_FACEBOOK_APP_ID=836681122652445
|
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
|
# OAuth Providers
|
||||||
NEXT_PUBLIC_GOOGLE_CLIENT_ID=546665126481-itnlvt22hjn6t0bbgua0aj55h6dpplsk.apps.googleusercontent.com
|
NEXT_PUBLIC_GOOGLE_CLIENT_ID=546665126481-itnlvt22hjn6t0bbgua0aj55h6dpplsk.apps.googleusercontent.com
|
||||||
NEXT_PUBLIC_FACEBOOK_APP_ID=836681122652445
|
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
|
# OAuth Configuration
|
||||||
NEXT_PUBLIC_GOOGLE_CLIENT_ID=546665126481-itnlvt22hjn6t0bbgua0aj55h6dpplsk.apps.googleusercontent.com
|
NEXT_PUBLIC_GOOGLE_CLIENT_ID=546665126481-itnlvt22hjn6t0bbgua0aj55h6dpplsk.apps.googleusercontent.com
|
||||||
NEXT_PUBLIC_FACEBOOK_APP_ID=836681122652445
|
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
|
# OAuth Configuration
|
||||||
NEXT_PUBLIC_GOOGLE_CLIENT_ID=546665126481-itnlvt22hjn6t0bbgua0aj55h6dpplsk.apps.googleusercontent.com
|
NEXT_PUBLIC_GOOGLE_CLIENT_ID=546665126481-itnlvt22hjn6t0bbgua0aj55h6dpplsk.apps.googleusercontent.com
|
||||||
NEXT_PUBLIC_FACEBOOK_APP_ID=836681122652445
|
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 {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const data = await employeeService.getPendingTickets();
|
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) {
|
} catch (error: any) {
|
||||||
console.error("Error loading tickets:", error);
|
console.error("Error loading tickets:", error);
|
||||||
toast.error("Erreur lors du chargement des tickets");
|
toast.error("Erreur lors du chargement des tickets");
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
"use client";
|
"use client";
|
||||||
import { useState } from "react";
|
import { useState, useRef } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { useAuth } from "@/contexts/AuthContext";
|
import { useAuth } from "@/contexts/AuthContext";
|
||||||
|
|
@ -7,6 +7,7 @@ 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 } from "@/utils/constants";
|
||||||
|
import ReCAPTCHA from "react-google-recaptcha";
|
||||||
|
|
||||||
export default function RegisterPage() {
|
export default function RegisterPage() {
|
||||||
const { register: registerUser } = useAuth();
|
const { register: registerUser } = useAuth();
|
||||||
|
|
@ -14,6 +15,9 @@ export default function RegisterPage() {
|
||||||
const [showPassword, setShowPassword] = useState(false);
|
const [showPassword, setShowPassword] = useState(false);
|
||||||
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
|
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
|
||||||
const [animationKey] = useState(Date.now());
|
const [animationKey] = useState(Date.now());
|
||||||
|
const [captchaToken, setCaptchaToken] = useState<string | null>(null);
|
||||||
|
const [captchaError, setCaptchaError] = useState(false);
|
||||||
|
const recaptchaRef = useRef<ReCAPTCHA>(null);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
register,
|
register,
|
||||||
|
|
@ -23,12 +27,26 @@ export default function RegisterPage() {
|
||||||
resolver: zodResolver(registerSchema),
|
resolver: zodResolver(registerSchema),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const onCaptchaChange = (token: string | null) => {
|
||||||
|
setCaptchaToken(token);
|
||||||
|
setCaptchaError(false);
|
||||||
|
};
|
||||||
|
|
||||||
const onSubmit = async (data: RegisterFormData) => {
|
const onSubmit = async (data: RegisterFormData) => {
|
||||||
|
// Vérifier le captcha
|
||||||
|
if (!captchaToken) {
|
||||||
|
setCaptchaError(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
setIsSubmitting(true);
|
setIsSubmitting(true);
|
||||||
try {
|
try {
|
||||||
await registerUser(data);
|
await registerUser({ ...data, captchaToken });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Registration error:", error);
|
console.error("Registration error:", error);
|
||||||
|
// Reset captcha en cas d'erreur
|
||||||
|
recaptchaRef.current?.reset();
|
||||||
|
setCaptchaToken(null);
|
||||||
} finally {
|
} finally {
|
||||||
setIsSubmitting(false);
|
setIsSubmitting(false);
|
||||||
}
|
}
|
||||||
|
|
@ -233,6 +251,19 @@ export default function RegisterPage() {
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</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 */}
|
{/* Submit Button */}
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
|
|
|
||||||
56
package-lock.json
generated
56
package-lock.json
generated
|
|
@ -10,6 +10,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hookform/resolvers": "^5.2.2",
|
"@hookform/resolvers": "^5.2.2",
|
||||||
"@react-oauth/google": "^0.12.2",
|
"@react-oauth/google": "^0.12.2",
|
||||||
|
"@types/react-google-recaptcha": "^2.1.9",
|
||||||
"axios": "^1.13.1",
|
"axios": "^1.13.1",
|
||||||
"bootstrap": "^5.3.3",
|
"bootstrap": "^5.3.3",
|
||||||
"lucide-react": "^0.553.0",
|
"lucide-react": "^0.553.0",
|
||||||
|
|
@ -19,6 +20,7 @@
|
||||||
"prom-client": "^15.1.3",
|
"prom-client": "^15.1.3",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
|
"react-google-recaptcha": "^3.1.0",
|
||||||
"react-hook-form": "^7.66.0",
|
"react-hook-form": "^7.66.0",
|
||||||
"react-hot-toast": "^2.6.0",
|
"react-hot-toast": "^2.6.0",
|
||||||
"recharts": "^3.4.1",
|
"recharts": "^3.4.1",
|
||||||
|
|
@ -2308,12 +2310,20 @@
|
||||||
"version": "19.2.2",
|
"version": "19.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.2.tgz",
|
||||||
"integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==",
|
"integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==",
|
||||||
"devOptional": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"csstype": "^3.0.2"
|
"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": {
|
"node_modules/@types/stack-utils": {
|
||||||
"version": "2.0.3",
|
"version": "2.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz",
|
||||||
|
|
@ -5678,6 +5688,21 @@
|
||||||
"node": ">= 0.4"
|
"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": {
|
"node_modules/html-encoding-sniffer": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz",
|
||||||
|
|
@ -8099,7 +8124,6 @@
|
||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||||
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
|
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
|
|
@ -8807,7 +8831,6 @@
|
||||||
"version": "15.8.1",
|
"version": "15.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||||
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"loose-envify": "^1.4.0",
|
"loose-envify": "^1.4.0",
|
||||||
|
|
@ -8819,7 +8842,6 @@
|
||||||
"version": "16.13.1",
|
"version": "16.13.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/proxy-from-env": {
|
"node_modules/proxy-from-env": {
|
||||||
|
|
@ -8888,6 +8910,19 @@
|
||||||
"node": ">=0.10.0"
|
"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": {
|
"node_modules/react-dom": {
|
||||||
"version": "18.2.0",
|
"version": "18.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
|
||||||
|
|
@ -8901,6 +8936,19 @@
|
||||||
"react": "^18.2.0"
|
"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": {
|
"node_modules/react-hook-form": {
|
||||||
"version": "7.66.1",
|
"version": "7.66.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.66.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.66.1.tgz",
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hookform/resolvers": "^5.2.2",
|
"@hookform/resolvers": "^5.2.2",
|
||||||
"@react-oauth/google": "^0.12.2",
|
"@react-oauth/google": "^0.12.2",
|
||||||
|
"@types/react-google-recaptcha": "^2.1.9",
|
||||||
"axios": "^1.13.1",
|
"axios": "^1.13.1",
|
||||||
"bootstrap": "^5.3.3",
|
"bootstrap": "^5.3.3",
|
||||||
"lucide-react": "^0.553.0",
|
"lucide-react": "^0.553.0",
|
||||||
|
|
@ -22,6 +23,7 @@
|
||||||
"prom-client": "^15.1.3",
|
"prom-client": "^15.1.3",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
|
"react-google-recaptcha": "^3.1.0",
|
||||||
"react-hook-form": "^7.66.0",
|
"react-hook-form": "^7.66.0",
|
||||||
"react-hot-toast": "^2.6.0",
|
"react-hot-toast": "^2.6.0",
|
||||||
"recharts": "^3.4.1",
|
"recharts": "^3.4.1",
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ export interface RegisterData {
|
||||||
firstName: string;
|
firstName: string;
|
||||||
lastName: string;
|
lastName: string;
|
||||||
phone?: string;
|
phone?: string;
|
||||||
|
captchaToken?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AuthResponse {
|
export interface AuthResponse {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user