diff --git a/.gitignore b/.gitignore index 1195d5e..95c0623 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ coverage/ !.env.example .DS_Store .pnpm-store/ +test-results/ diff --git a/apps/web/src/pages/auth/VerifyEmailPage.tsx b/apps/web/src/pages/auth/VerifyEmailPage.tsx index 9d42e36..d2d2930 100644 --- a/apps/web/src/pages/auth/VerifyEmailPage.tsx +++ b/apps/web/src/pages/auth/VerifyEmailPage.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react'; +import { useEffect, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Link, useSearchParams } from 'react-router-dom'; @@ -14,12 +14,17 @@ export function VerifyEmailPage(): React.JSX.Element { const [resent, setResent] = useState(false); const token = params.get('token'); + // Tokens are single-use: the effect must fire exactly once per token, + // also under React StrictMode's double-invocation in development. + const firedFor = useRef(null); useEffect(() => { if (!token) { setState('error'); return; } + if (firedFor.current === token) return; + firedFor.current = token; apiPost('/auth/verify-email', { token }) .then(() => setState('success')) .catch(() => setState('error')); diff --git a/apps/web/test-results/.last-run.json b/apps/web/test-results/.last-run.json deleted file mode 100644 index f740f7c..0000000 --- a/apps/web/test-results/.last-run.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "status": "passed", - "failedTests": [] -}