Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 2 additions & 25 deletions frontend/src/pages/Register.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,7 @@ import { supabase } from '../supabaseClient';
import { Link } from 'react-router-dom';
import { motion as Motion } from 'motion/react';
import { Mail, Lock, User, ArrowRight, AlertCircle, CheckCircle2, Eye, EyeOff } from 'lucide-react';

/* eslint-disable react-refresh/only-export-components -- test-only helper exports */
// Generous 5 min: a false "already exists" for a genuinely new user is worse
// than missing a duplicate.
export const EXISTING_ACCOUNT_AGE_MS = 300_000;

export const DUPLICATE_ACCOUNT_MESSAGE =
"An account with this email already exists — log in or reset your password.";

// Supabase returns identities: [] when the email is already registered (confirmed
// account). For an unconfirmed duplicate, identities come back non-empty but the
// account's created_at is old (Supabase re-signup doesn't reset it). A missing or
// unparseable created_at, or missing identities, is treated as a fresh account.
export function isExistingAccount(user, now = Date.now()) {
if (!user) return false;
if (user.identities?.length === 0) return true;
if (user.created_at) {
const createdAt = Date.parse(user.created_at);
if (!Number.isNaN(createdAt) && now - createdAt > EXISTING_ACCOUNT_AGE_MS) {
return true;
}
}
return false;
}
import { isExistingAccount, DUPLICATE_ACCOUNT_MESSAGE } from '../utils/registerHelpers';

export default function Register() {
const [email, setEmail] = useState('');
Expand All @@ -44,7 +21,7 @@ export default function Register() {
const oauthError = localStorage.getItem('oauth_login_error');
if (oauthError) {
setError(oauthError);
setErrorKind(null);
setErrorKind(oauthError.includes('already exists') ? 'duplicate' : null);
localStorage.removeItem('oauth_login_error');
}
}, []);
Expand Down
11 changes: 10 additions & 1 deletion frontend/src/pages/__tests__/Register.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { render, screen, fireEvent, waitFor, cleanup, act } from '@testing-library/react';
import { BrowserRouter } from 'react-router-dom';
import Register, { isExistingAccount, EXISTING_ACCOUNT_AGE_MS } from '../Register';
import Register from '../Register';
import { isExistingAccount, EXISTING_ACCOUNT_AGE_MS } from '../../utils/registerHelpers';

// Mock Supabase
vi.mock('../../supabaseClient', () => ({
Expand Down Expand Up @@ -496,6 +497,14 @@ describe('Register', () => {
expect(localStorage.getItem('oauth_login_error')).toBeNull();
});

it('shows log-in link for OAuth existing-account error from localStorage', () => {
localStorage.setItem('oauth_login_error', 'An account with this Google account already exists. Please login instead.');

renderRegister();

expect(screen.getByRole('link', { name: /^log in$/i })).toBeInTheDocument();
});

it('handles Google OAuth error', async () => {
const { supabase } = await import('../../supabaseClient');
supabase.auth.signInWithOAuth.mockResolvedValue({
Expand Down
24 changes: 24 additions & 0 deletions frontend/src/utils/registerHelpers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Pure helpers for Register: duplicate-account detection after supabase.auth.signUp.

// Generous 5 min: a false "already exists" for a genuinely new user is worse
// than missing a duplicate.
export const EXISTING_ACCOUNT_AGE_MS = 300_000;

export const DUPLICATE_ACCOUNT_MESSAGE =
"An account with this email already exists — log in or reset your password.";

// Supabase returns identities: [] when the email is already registered (confirmed
// account). For an unconfirmed duplicate, identities come back non-empty but the
// account's created_at is old (Supabase re-signup doesn't reset it). A missing or
// unparseable created_at, or missing identities, is treated as a fresh account.
export function isExistingAccount(user, now = Date.now()) {
if (!user) return false;
if (user.identities?.length === 0) return true;
if (user.created_at) {
const createdAt = Date.parse(user.created_at);
if (!Number.isNaN(createdAt) && now - createdAt > EXISTING_ACCOUNT_AGE_MS) {
return true;
}
}
return false;
}
Loading