Submit Benchmark
diff --git a/app/profile/[id]/page.tsx b/app/profile/[id]/page.tsx
index 9b96c25..4dd39ab 100644
--- a/app/profile/[id]/page.tsx
+++ b/app/profile/[id]/page.tsx
@@ -60,7 +60,8 @@ export default async function ProfilePage({
fpsHigh: performanceEntries.fpsHigh,
hardwareSlug: performanceEntries.hardwareSlug,
hardwareName: hardware.name,
- fsrVersion: performanceEntries.fsrVersion,
+ upscalerType: performanceEntries.upscalerType,
+ upscalerVersion: performanceEntries.upscalerVersion,
frameGenMethod: performanceEntries.frameGenMethod,
verifiedAt: performanceEntries.verifiedAt,
createdAt: performanceEntries.createdAt,
diff --git a/app/profile/page.tsx b/app/profile/page.tsx
index d22035b..abb3d1f 100644
--- a/app/profile/page.tsx
+++ b/app/profile/page.tsx
@@ -35,7 +35,7 @@ export default function ProfilePage() {
useEffect(() => {
if (!isSessionLoading && !session) {
- router.push("/login")
+ router.push(`/login?redirect=${encodeURIComponent(window.location.pathname)}`)
}
}, [session, isSessionLoading, router])
diff --git a/components/auth/login-form.tsx b/components/auth/login-form.tsx
index eea6dd9..aac5f74 100644
--- a/components/auth/login-form.tsx
+++ b/components/auth/login-form.tsx
@@ -1,7 +1,7 @@
"use client"
-import { useState, useEffect } from "react"
-import { Loader2 } from "lucide-react"
+import { useState, useEffect, useRef, useCallback } from "react"
+import { Loader2, Key } from "lucide-react"
import { authClient } from "@/lib/auth-client"
import {
loginEmailSchema,
@@ -9,22 +9,70 @@ import {
} from "@/lib/auth/validation"
import SocialButtons from "./social-buttons"
import Link from "next/link"
-import { useRouter } from "next/navigation"
+import { useRouter, useSearchParams } from "next/navigation"
+
+function isWebAuthnAbortError(err: unknown): boolean {
+ if (err instanceof DOMException && err.name === "AbortError") return true
+ const msg = err instanceof Error ? err.message : String(err ?? "")
+ return msg.includes("abort signal") || msg.includes("AbortError")
+}
+
+// Suppress the console.error that @better-auth/passkey logs internally
+// when a WebAuthn ceremony is aborted (expected on navigation/remount).
+let suppressPasskeyErrors = false
+const originalConsoleError = console.error
+const passkeyErrorPattern = /\[Better Auth\] Error verifying passkey/
+
+console.error = (...args: unknown[]) => {
+ if (suppressPasskeyErrors) {
+ const msg = typeof args[0] === 'string' ? args[0] : ''
+ if (passkeyErrorPattern.test(msg) || args.some(a => isWebAuthnAbortError(a))) {
+ return
+ }
+ }
+ originalConsoleError(...args)
+}
export default function LoginForm() {
const router = useRouter()
- const [phase, setPhase] = useState<"email" | "password">("email")
+ const searchParams = useSearchParams()
+ const redirectTo = searchParams.get("redirect") || "/"
+ const [showPassword, setShowPassword] = useState(false)
const [email, setEmail] = useState("")
const [password, setPassword] = useState("")
const [error, setError] = useState("")
const [isLoading, setIsLoading] = useState(false)
+ const [emailChecked, setEmailChecked] = useState(false)
+ const mountedRef = useRef(true)
+ const passkeyInitiatedRef = useRef(false)
- // Preload passkeys for conditional UI
+ // Redirect to the intended page after successful login
+ const handleLoginSuccess = useCallback(() => {
+ if (mountedRef.current) router.push(redirectTo)
+ }, [router, redirectTo])
+
+ // Preload passkeys for conditional UI — must be called on mount when
+ // both email + password fields are in the DOM.
useEffect(() => {
- if (phase === "password" && "PublicKeyCredential" in window) {
- authClient.signIn.passkey({ autoFill: true })
+ mountedRef.current = true
+ if ("PublicKeyCredential" in window && !passkeyInitiatedRef.current) {
+ passkeyInitiatedRef.current = true
+ suppressPasskeyErrors = true
+ authClient.signIn.passkey({
+ autoFill: true,
+ fetchOptions: {
+ onSuccess: handleLoginSuccess,
+ },
+ }).catch((err) => {
+ if (!isWebAuthnAbortError(err)) {
+ console.warn("[passkey-conditional-ui]", err)
+ }
+ }).finally(() => {
+ suppressPasskeyErrors = false
+ })
}
- }, [phase])
+ return () => { mountedRef.current = false }
+ }, [handleLoginSuccess])
const handleEmailSubmit = async (e: React.FormEvent) => {
e.preventDefault()
@@ -56,8 +104,9 @@ export default function LoginForm() {
return
}
- // Email exists, move to password phase
- setPhase("password")
+ // Email exists, show password field
+ setEmailChecked(true)
+ setShowPassword(true)
} catch {
setError("Something went wrong. Please try again.")
} finally {
@@ -84,13 +133,54 @@ export default function LoginForm() {
return
}
- router.push("/")
+ router.push(redirectTo)
}
const handleChangeEmail = () => {
- setPhase("email")
+ setShowPassword(false)
setPassword("")
setError("")
+ setEmailChecked(false)
+ }
+
+ const handlePasskeyError = useCallback((ctx: { error?: { message?: string } }) => {
+ // Only show errors that aren't from conditional UI cancellation
+ // (user dismissing the browser prompt is expected and not an error)
+ const msg = ctx.error?.message || ""
+ if (
+ !msg.includes("No available") &&
+ !msg.includes("cancelled") &&
+ !msg.includes("NotAllowed") &&
+ !msg.includes("aborted")
+ ) {
+ setError(msg || "Passkey sign-in failed. Please try again.")
+ }
+ }, [])
+
+ const handlePasskeyLogin = async () => {
+ setError("")
+ setIsLoading(true)
+ suppressPasskeyErrors = true
+ try {
+ const result = await authClient.signIn.passkey({
+ autoFill: false,
+ fetchOptions: {
+ onSuccess: handleLoginSuccess,
+ onError: handlePasskeyError,
+ },
+ }).catch((err) => {
+ if (isWebAuthnAbortError(err)) return { data: null, error: null }
+ return { data: null, error: { message: err?.message || "Passkey sign-in failed" } }
+ })
+ if (result?.error) {
+ handlePasskeyError({ error: result.error })
+ }
+ } catch {
+ setError("Passkey sign-in failed. Please try again.")
+ } finally {
+ suppressPasskeyErrors = false
+ setIsLoading(false)
+ }
}
return (
@@ -100,10 +190,10 @@ export default function LoginForm() {
Welcome back
- {phase === "email"
- ? "Sign in to DeckyVault"
- : `Signing in as `}
- {phase === "password" && (
+ {showPassword
+ ? "Signing in as "
+ : "Sign in to DeckyVault"}
+ {showPassword && (
<>
{email}
{" "}
@@ -145,48 +235,43 @@ export default function LoginForm() {
)}
- {phase === "email" ? (
-
- ) : (
-
+ )}
+
+
+ {/* Explicit passkey login button */}
+ {"PublicKeyCredential" in window && !showPassword && (
+
)}
diff --git a/components/navbar.tsx b/components/navbar.tsx
index d85818e..3020118 100644
--- a/components/navbar.tsx
+++ b/components/navbar.tsx
@@ -5,7 +5,15 @@ import logo from "@/app/icon.png"
import Image from "next/image"
import Link from "next/link"
import { useEffect, useRef, useState } from "react"
-import { Bookmark, CircleXIcon, Gamepad2Icon, LogOut, MenuIcon, User, XIcon } from "lucide-react"
+import {
+ Bookmark,
+ CircleXIcon,
+ Gamepad2Icon,
+ LogOut,
+ MenuIcon,
+ User,
+ XIcon,
+} from "lucide-react"
import { routes, authRoutes } from "@/lib/routes"
import { usePathname, useRouter, useSearchParams } from "next/navigation"
import { useDebounce } from "@/lib/hooks/useDebounce"
@@ -17,7 +25,11 @@ export default function Navbar() {
const searchParams = useSearchParams()
const isLanding = pathname === "/"
- const isAuthRoute = pathname.startsWith("/login") || pathname.startsWith("/signup") || pathname.startsWith("/forgot-password") || pathname.startsWith("/reset-password")
+ const isAuthRoute =
+ pathname.startsWith("/login") ||
+ pathname.startsWith("/signup") ||
+ pathname.startsWith("/forgot-password") ||
+ pathname.startsWith("/reset-password")
const [searchQuery, setSearchQuery] = useState(
() => searchParams.get("q") || "",
@@ -140,6 +152,7 @@ export default function Navbar() {
src={logo}
alt='DeckyVault Logo'
className='h-6 my-1 w-auto'
+ loading='eager'
/>
{!isLanding && (
@@ -243,7 +256,9 @@ export default function Navbar() {
className='relative'
>