diff --git a/e2e/journeys/j1-registration-login.spec.ts b/e2e/journeys/j1-registration-login.spec.ts
index ec116ab..b1b28a4 100644
--- a/e2e/journeys/j1-registration-login.spec.ts
+++ b/e2e/journeys/j1-registration-login.spec.ts
@@ -10,7 +10,6 @@ test.describe('J1: Registration and Login', () => {
await page.fill('[placeholder="Password (min. 8 characters)"]', 'TestPass123!');
await page.click('button[type="submit"]');
- // With VITE_MOCK_AUTH=true the app navigates to "/" on success
await expect(page).toHaveURL('http://localhost:5173/');
await expect(page.getByRole('heading', { name: /cart/i })).toBeVisible();
});
diff --git a/playwright.config.ts b/playwright.config.ts
index b22d74a..a2d7b0b 100644
--- a/playwright.config.ts
+++ b/playwright.config.ts
@@ -9,7 +9,7 @@ export default defineConfig({
},
],
webServer: {
- command: 'VITE_MOCK_AUTH=true npm run dev',
+ command: 'npm run dev',
url: 'http://localhost:5173',
reuseExistingServer: !process.env.CI,
},
diff --git a/src/App.tsx b/src/App.tsx
index ee4c2dc..953553a 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -15,6 +15,7 @@ import { AccountLinking } from './pages/AccountLinking.tsx'
import { Login } from './pages/Login.tsx'
import { Register } from './pages/Register.tsx'
import { ForgotPassword } from './pages/ForgotPassword.tsx'
+import { VerifyEmail } from './pages/VerifyEmail.tsx'
const queryClient = new QueryClient({
defaultOptions: {
@@ -47,6 +48,7 @@ export default function App() {
} />
} />
} />
+ } />
diff --git a/src/components/ProtectedRoute.tsx b/src/components/ProtectedRoute.tsx
index cf92831..294ec4f 100644
--- a/src/components/ProtectedRoute.tsx
+++ b/src/components/ProtectedRoute.tsx
@@ -1,25 +1,8 @@
-import { useEffect } from 'react'
import { Navigate, Outlet } from 'react-router-dom'
import { authClient } from '../lib/auth-client.ts'
-import { useAuthStore } from '../stores/auth.ts'
export function ProtectedRoute() {
- const isMockAuth = import.meta.env.VITE_MOCK_AUTH === 'true'
const { data: session, isPending } = authClient.useSession()
- const isAuthenticated = useAuthStore((s) => s.isAuthenticated)
- const setAuthenticated = useAuthStore((s) => s.setAuthenticated)
-
- useEffect(() => {
- if (!isMockAuth) {
- setAuthenticated(!!session)
- }
- }, [session, setAuthenticated, isMockAuth])
-
- // In mock auth mode, rely on Zustand store (set by Login/Register pages)
- if (isMockAuth) {
- if (!isAuthenticated) return
- return
- }
if (isPending) {
return (
diff --git a/src/pages/Login.tsx b/src/pages/Login.tsx
index ae7fc0c..5044613 100644
--- a/src/pages/Login.tsx
+++ b/src/pages/Login.tsx
@@ -1,7 +1,6 @@
import { useState } from 'react'
import { Link, useNavigate } from 'react-router-dom'
import { authClient } from '../lib/auth-client.ts'
-import { useAuthStore } from '../stores/auth.ts'
export function Login() {
const [email, setEmail] = useState('')
@@ -9,7 +8,6 @@ export function Login() {
const [error, setError] = useState('')
const [loading, setLoading] = useState(false)
const navigate = useNavigate()
- const setAuthenticated = useAuthStore((s) => s.setAuthenticated)
async function handleSubmit(e: React.FormEvent) {
e.preventDefault()
@@ -40,12 +38,7 @@ export function Login() {
setError('Sign in failed. Please try again.')
}
} catch {
- if (import.meta.env.VITE_MOCK_AUTH === 'true') {
- setAuthenticated(true)
- navigate('/')
- } else {
- setError('Invalid email or password. Please try again.')
- }
+ setError('Invalid email or password. Please try again.')
} finally {
setLoading(false)
}
diff --git a/src/pages/Register.tsx b/src/pages/Register.tsx
index c75e2d6..ba039ba 100644
--- a/src/pages/Register.tsx
+++ b/src/pages/Register.tsx
@@ -1,7 +1,6 @@
import { useState } from 'react'
import { Link, useNavigate } from 'react-router-dom'
import { authClient } from '../lib/auth-client.ts'
-import { useAuthStore } from '../stores/auth.ts'
export function Register() {
const [name, setName] = useState('')
@@ -9,8 +8,10 @@ export function Register() {
const [password, setPassword] = useState('')
const [error, setError] = useState('')
const [loading, setLoading] = useState(false)
+ const [registrationComplete, setRegistrationComplete] = useState(false)
+ const [resendLoading, setResendLoading] = useState(false)
+ const [resendMessage, setResendMessage] = useState('')
const navigate = useNavigate()
- const setAuthenticated = useAuthStore((s) => s.setAuthenticated)
async function handleSubmit(e: React.FormEvent) {
e.preventDefault()
@@ -38,27 +39,57 @@ export function Register() {
throw new Error(authError.message ?? 'Registration failed')
}
- // After successful signUp, force a session fetch to confirm the cookie is set
- // before navigating to the protected route
- const sessionResult = await authClient.getSession()
- if (sessionResult.data) {
- navigate('/')
- } else {
- // Session not established — show success message and link to login
- setError('Account created! Please sign in.')
- }
+ setRegistrationComplete(true)
} catch {
- if (import.meta.env.VITE_MOCK_AUTH === 'true') {
- setAuthenticated(true)
- navigate('/')
- } else {
- setError('Registration failed. Please try again.')
- }
+ setError('Registration failed. Please try again.')
} finally {
setLoading(false)
}
}
+ async function handleResendVerification() {
+ setResendLoading(true)
+ setResendMessage('')
+ try {
+ const { error } = await authClient.sendVerificationEmail({ email })
+ if (error) {
+ setResendMessage('Failed to resend. Please try again.')
+ } else {
+ setResendMessage('Verification email sent!')
+ }
+ } finally {
+ setResendLoading(false)
+ }
+ }
+
+ if (registrationComplete) {
+ return (
+
+
Check your email
+
+ We sent a verification link to {email}. Click it to activate your account.
+
+
+ {resendMessage && (
+
{resendMessage}
+ )}
+
+ Already have an account?{' '}
+
+ Sign in
+
+
+
+ )
+ }
+
return (
Create Account
diff --git a/src/pages/VerifyEmail.tsx b/src/pages/VerifyEmail.tsx
new file mode 100644
index 0000000..d1c5fb3
--- /dev/null
+++ b/src/pages/VerifyEmail.tsx
@@ -0,0 +1,113 @@
+import { useEffect, useState } from "react";
+import { useNavigate, useSearchParams } from "react-router-dom";
+import { authClient } from "../lib/auth-client.ts";
+
+export function VerifyEmail() {
+ const [searchParams] = useSearchParams();
+ const navigate = useNavigate();
+ const [status, setStatus] = useState<"verifying" | "success" | "error">("verifying");
+ const [resendEmail, setResendEmail] = useState("");
+ const [showResend, setShowResend] = useState(false);
+ const [resending, setResending] = useState(false);
+ const [resendMessage, setResendMessage] = useState("");
+
+ useEffect(() => {
+ const token = searchParams.get("token");
+ const callbackURL = searchParams.get("callbackURL") || "/";
+
+ if (!token) {
+ setStatus("error");
+ return;
+ }
+
+ authClient.verifyEmail({ query: { token } })
+ .then(() => {
+ setStatus("success");
+ setTimeout(() => {
+ navigate(callbackURL);
+ }, 2000);
+ })
+ .catch(() => {
+ setStatus("error");
+ });
+ }, [searchParams, navigate]);
+
+ async function handleResend() {
+ if (!resendEmail) {
+ setResendMessage("Please enter your email address.");
+ return;
+ }
+
+ setResending(true);
+ setResendMessage("");
+
+ try {
+ const { error } = await authClient.sendVerificationEmail({ email: resendEmail });
+ if (error) {
+ setResendMessage("Failed to resend. Please try again.");
+ } else {
+ setResendMessage("Verification email sent!");
+ setShowResend(false);
+ }
+ } finally {
+ setResending(false);
+ }
+ }
+
+ return (
+
+ {status === "verifying" && (
+ <>
+
+
Verifying your email...
+
Please wait while we verify your email address.
+ >
+ )}
+
+ {status === "success" && (
+ <>
+
Email verified!
+
Redirecting you shortly...
+ >
+ )}
+
+ {status === "error" && (
+ <>
+
Verification failed
+
The verification link may have expired or is invalid.
+
+ {!showResend ? (
+
+ ) : (
+
+
setResendEmail(e.target.value)}
+ className="min-h-12 w-full rounded-xl border border-gray-200 px-4 text-base focus:border-brand-blue focus:outline-none focus:ring-1 focus:ring-brand-blue"
+ />
+
+ {resendMessage && (
+
{resendMessage}
+ )}
+
+ )}
+ >
+ )}
+
+ );
+}