chore: auth flow cleanup and verification
This commit is contained in:
+9
-12
@@ -7,7 +7,6 @@ import {
|
|||||||
ExternalLinkIcon,
|
ExternalLinkIcon,
|
||||||
Gamepad2Icon,
|
Gamepad2Icon,
|
||||||
MessageSquareIcon,
|
MessageSquareIcon,
|
||||||
MonitorIcon,
|
|
||||||
SettingsIcon,
|
SettingsIcon,
|
||||||
TrendingUpIcon,
|
TrendingUpIcon,
|
||||||
DatabaseIcon,
|
DatabaseIcon,
|
||||||
@@ -50,13 +49,11 @@ function SearchContent() {
|
|||||||
const [results, setResults] = useState<UnifiedResult[]>([])
|
const [results, setResults] = useState<UnifiedResult[]>([])
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
const [error, setError] = useState<string | null>(null)
|
const [error, setError] = useState<string | null>(null)
|
||||||
|
const isValidQuery = query && query.length >= 2
|
||||||
|
|
||||||
// Handle direct navigation / browser back-forward
|
// Handle direct navigation / browser back-forward
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!query || query.length < 2) {
|
if (!isValidQuery) return
|
||||||
setResults([])
|
|
||||||
setError(null)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let cancelled = false
|
let cancelled = false
|
||||||
|
|
||||||
@@ -85,7 +82,7 @@ function SearchContent() {
|
|||||||
return () => {
|
return () => {
|
||||||
cancelled = true
|
cancelled = true
|
||||||
}
|
}
|
||||||
}, [query])
|
}, [isValidQuery, query])
|
||||||
|
|
||||||
function handleClick(result: UnifiedResult) {
|
function handleClick(result: UnifiedResult) {
|
||||||
const path = result.appId
|
const path = result.appId
|
||||||
@@ -114,7 +111,7 @@ function SearchContent() {
|
|||||||
: "Enter a game name or AppID to find benchmarks, settings, and reviews."}
|
: "Enter a game name or AppID to find benchmarks, settings, and reviews."}
|
||||||
</motion.p>
|
</motion.p>
|
||||||
|
|
||||||
{!query && (
|
{!isValidQuery && (
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0, y: 20 }}
|
initial={{ opacity: 0, y: 20 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
@@ -127,7 +124,7 @@ function SearchContent() {
|
|||||||
</motion.div>
|
</motion.div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{query && loading && (
|
{isValidQuery && loading && (
|
||||||
<div className="flex flex-col items-center justify-center py-20 gap-4">
|
<div className="flex flex-col items-center justify-center py-20 gap-4">
|
||||||
<motion.div
|
<motion.div
|
||||||
animate={{ rotate: 360 }}
|
animate={{ rotate: 360 }}
|
||||||
@@ -140,7 +137,7 @@ function SearchContent() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{query && !loading && error && (
|
{isValidQuery && !loading && error && (
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0 }}
|
initial={{ opacity: 0 }}
|
||||||
animate={{ opacity: 1 }}
|
animate={{ opacity: 1 }}
|
||||||
@@ -150,7 +147,7 @@ function SearchContent() {
|
|||||||
</motion.div>
|
</motion.div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{query && !loading && !error && results.length === 0 && (
|
{isValidQuery && !loading && !error && results.length === 0 && (
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0, y: 20 }}
|
initial={{ opacity: 0, y: 20 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
@@ -163,7 +160,7 @@ function SearchContent() {
|
|||||||
</motion.div>
|
</motion.div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{query && !loading && !error && results.length > 0 && (
|
{isValidQuery && !loading && !error && results.length > 0 && (
|
||||||
<motion.div
|
<motion.div
|
||||||
className="flex flex-col gap-3"
|
className="flex flex-col gap-3"
|
||||||
initial="hidden"
|
initial="hidden"
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import { Lock, Loader2, ArrowLeft } from "lucide-react"
|
|||||||
import { authClient } from "@/lib/auth-client"
|
import { authClient } from "@/lib/auth-client"
|
||||||
import {
|
import {
|
||||||
forgotPasswordSchema,
|
forgotPasswordSchema,
|
||||||
type ForgotPasswordInput,
|
|
||||||
} from "@/lib/auth/validation"
|
} from "@/lib/auth/validation"
|
||||||
import Link from "next/link"
|
import Link from "next/link"
|
||||||
import { useRouter } from "next/navigation"
|
import { useRouter } from "next/navigation"
|
||||||
@@ -51,7 +50,7 @@ export default function ForgotPasswordForm() {
|
|||||||
Forgot your password?
|
Forgot your password?
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-xs text-[#ebe4f1]/50 mt-1 leading-relaxed">
|
<p className="text-xs text-[#ebe4f1]/50 mt-1 leading-relaxed">
|
||||||
Enter your email and we'll send you a verification code to
|
Enter your email and we'll send you a verification code to
|
||||||
reset your password.
|
reset your password.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { useState, useEffect, useCallback } from "react"
|
import { useState, useEffect } from "react"
|
||||||
import { Loader2 } from "lucide-react"
|
import { Loader2 } from "lucide-react"
|
||||||
import { authClient } from "@/lib/auth-client"
|
import { authClient } from "@/lib/auth-client"
|
||||||
import {
|
import {
|
||||||
loginEmailSchema,
|
loginEmailSchema,
|
||||||
loginSchema,
|
loginSchema,
|
||||||
type LoginInput,
|
|
||||||
} from "@/lib/auth/validation"
|
} from "@/lib/auth/validation"
|
||||||
import SocialButtons from "./social-buttons"
|
import SocialButtons from "./social-buttons"
|
||||||
import Link from "next/link"
|
import Link from "next/link"
|
||||||
@@ -221,7 +220,7 @@ export default function LoginForm() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<p className="text-center text-xs text-[#ebe4f1]/50">
|
<p className="text-center text-xs text-[#ebe4f1]/50">
|
||||||
Don't have an account?{" "}
|
Don't have an account?{" "}
|
||||||
<Link
|
<Link
|
||||||
href="/signup"
|
href="/signup"
|
||||||
className="text-[#eb3779] hover:underline"
|
className="text-[#eb3779] hover:underline"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { useRef, useState, useCallback, useEffect } from "react"
|
import { useRef, useCallback, useMemo } from "react"
|
||||||
|
|
||||||
interface OtpInputProps {
|
interface OtpInputProps {
|
||||||
length?: number
|
length?: number
|
||||||
@@ -17,23 +17,18 @@ export default function OtpInput({
|
|||||||
disabled = false,
|
disabled = false,
|
||||||
error,
|
error,
|
||||||
}: OtpInputProps) {
|
}: OtpInputProps) {
|
||||||
const [digits, setDigits] = useState<string[]>(
|
const digits = useMemo(
|
||||||
value.split("").concat(Array(length).fill("")).slice(0, length),
|
() =>
|
||||||
|
value
|
||||||
|
.split("")
|
||||||
|
.concat(Array(length).fill(""))
|
||||||
|
.slice(0, length),
|
||||||
|
[value, length],
|
||||||
)
|
)
|
||||||
const refs = useRef<(HTMLInputElement | null)[]>([])
|
const refs = useRef<(HTMLInputElement | null)[]>([])
|
||||||
|
|
||||||
// Sync with external value
|
|
||||||
useEffect(() => {
|
|
||||||
const newDigits = value
|
|
||||||
.split("")
|
|
||||||
.concat(Array(length).fill(""))
|
|
||||||
.slice(0, length)
|
|
||||||
setDigits(newDigits)
|
|
||||||
}, [value, length])
|
|
||||||
|
|
||||||
const updateDigits = useCallback(
|
const updateDigits = useCallback(
|
||||||
(newDigits: string[]) => {
|
(newDigits: string[]) => {
|
||||||
setDigits(newDigits)
|
|
||||||
onChange(newDigits.join(""))
|
onChange(newDigits.join(""))
|
||||||
},
|
},
|
||||||
[onChange],
|
[onChange],
|
||||||
|
|||||||
@@ -20,14 +20,11 @@ export default function OtpVerificationStep({
|
|||||||
const [error, setError] = useState("")
|
const [error, setError] = useState("")
|
||||||
const [isLoading, setIsLoading] = useState(false)
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
const [resendTimer, setResendTimer] = useState(300) // 5 minutes
|
const [resendTimer, setResendTimer] = useState(300) // 5 minutes
|
||||||
const [canResend, setCanResend] = useState(false)
|
const canResend = resendTimer <= 0
|
||||||
|
|
||||||
// Countdown timer
|
// Countdown timer
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (resendTimer <= 0) {
|
if (resendTimer <= 0) return
|
||||||
setCanResend(true)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const interval = setInterval(() => {
|
const interval = setInterval(() => {
|
||||||
setResendTimer((prev) => prev - 1)
|
setResendTimer((prev) => prev - 1)
|
||||||
}, 1000)
|
}, 1000)
|
||||||
@@ -40,37 +37,33 @@ export default function OtpVerificationStep({
|
|||||||
return `${mins}:${secs.toString().padStart(2, "0")}`
|
return `${mins}:${secs.toString().padStart(2, "0")}`
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleVerify = useCallback(async () => {
|
const handleVerify = useCallback(
|
||||||
if (otp.length !== 6) return
|
async (otpValue: string) => {
|
||||||
|
if (otpValue.length !== 6) return
|
||||||
|
|
||||||
setIsLoading(true)
|
setIsLoading(true)
|
||||||
setError("")
|
setError("")
|
||||||
|
|
||||||
const { error } = await authClient.emailOtp.verifyEmail({
|
const { error } = await authClient.emailOtp.verifyEmail({
|
||||||
email,
|
email,
|
||||||
otp,
|
otp: otpValue,
|
||||||
})
|
})
|
||||||
|
|
||||||
setIsLoading(false)
|
setIsLoading(false)
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
setError(
|
setError(
|
||||||
error.code === "TOO_MANY_ATTEMPTS"
|
error.code === "TOO_MANY_ATTEMPTS"
|
||||||
? "Too many attempts. Please request a new code."
|
? "Too many attempts. Please request a new code."
|
||||||
: "Invalid code. Please try again.",
|
: "Invalid code. Please try again.",
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
onSuccess()
|
onSuccess()
|
||||||
}, [otp, email, onSuccess])
|
},
|
||||||
|
[email, onSuccess],
|
||||||
// Auto-submit when all digits entered
|
)
|
||||||
useEffect(() => {
|
|
||||||
if (otp.length === 6) {
|
|
||||||
handleVerify()
|
|
||||||
}
|
|
||||||
}, [otp, handleVerify])
|
|
||||||
|
|
||||||
const handleResend = async () => {
|
const handleResend = async () => {
|
||||||
setError("")
|
setError("")
|
||||||
@@ -79,7 +72,6 @@ export default function OtpVerificationStep({
|
|||||||
type: "email-verification",
|
type: "email-verification",
|
||||||
})
|
})
|
||||||
setResendTimer(300)
|
setResendTimer(300)
|
||||||
setCanResend(false)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -101,7 +93,12 @@ export default function OtpVerificationStep({
|
|||||||
</label>
|
</label>
|
||||||
<OtpInput
|
<OtpInput
|
||||||
value={otp}
|
value={otp}
|
||||||
onChange={setOtp}
|
onChange={(value) => {
|
||||||
|
setOtp(value)
|
||||||
|
if (value.length === 6) {
|
||||||
|
handleVerify(value)
|
||||||
|
}
|
||||||
|
}}
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
error={error}
|
error={error}
|
||||||
/>
|
/>
|
||||||
@@ -126,7 +123,7 @@ export default function OtpVerificationStep({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={handleVerify}
|
onClick={() => handleVerify(otp)}
|
||||||
disabled={isLoading || otp.length !== 6}
|
disabled={isLoading || otp.length !== 6}
|
||||||
className="w-full py-2.5 rounded-lg bg-[#eb3779] text-white text-sm font-semibold hover:bg-[#eb3779]/90 transition-colors disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
|
className="w-full py-2.5 rounded-lg bg-[#eb3779] text-white text-sm font-semibold hover:bg-[#eb3779]/90 transition-colors disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ export default function PasskeySetupStep({
|
|||||||
Set up a passkey
|
Set up a passkey
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-xs text-[#ebe4f1]/50 mt-1 leading-relaxed">
|
<p className="text-xs text-[#ebe4f1]/50 mt-1 leading-relaxed">
|
||||||
Sign in faster with biometrics or your device's security
|
Sign in faster with biometrics or your device's security
|
||||||
key. No password needed.
|
key. No password needed.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { useState, useEffect, useCallback } from "react"
|
import { useState, useEffect } from "react"
|
||||||
import {
|
import {
|
||||||
Mail,
|
Mail,
|
||||||
CheckCircle2,
|
CheckCircle2,
|
||||||
@@ -10,7 +10,6 @@ import {
|
|||||||
import { authClient } from "@/lib/auth-client"
|
import { authClient } from "@/lib/auth-client"
|
||||||
import {
|
import {
|
||||||
resetPasswordSchema,
|
resetPasswordSchema,
|
||||||
type ResetPasswordInput,
|
|
||||||
} from "@/lib/auth/validation"
|
} from "@/lib/auth/validation"
|
||||||
import OtpInput from "./otp-input"
|
import OtpInput from "./otp-input"
|
||||||
import PasswordStrengthMeter from "./password-strength"
|
import PasswordStrengthMeter from "./password-strength"
|
||||||
@@ -30,14 +29,11 @@ export default function ResetPasswordForm({
|
|||||||
const [isLoading, setIsLoading] = useState(false)
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
const [isSuccess, setIsSuccess] = useState(false)
|
const [isSuccess, setIsSuccess] = useState(false)
|
||||||
const [resendTimer, setResendTimer] = useState(300)
|
const [resendTimer, setResendTimer] = useState(300)
|
||||||
const [canResend, setCanResend] = useState(false)
|
const canResend = resendTimer <= 0
|
||||||
|
|
||||||
// Countdown timer
|
// Countdown timer
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (resendTimer <= 0) {
|
if (resendTimer <= 0) return
|
||||||
setCanResend(true)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const interval = setInterval(() => {
|
const interval = setInterval(() => {
|
||||||
setResendTimer((prev) => prev - 1)
|
setResendTimer((prev) => prev - 1)
|
||||||
}, 1000)
|
}, 1000)
|
||||||
@@ -65,9 +61,10 @@ export default function ResetPasswordForm({
|
|||||||
}
|
}
|
||||||
|
|
||||||
setIsLoading(true)
|
setIsLoading(true)
|
||||||
const { error } = await authClient.resetPassword({
|
const { error } = await authClient.emailOtp.resetPassword({
|
||||||
newPassword,
|
email,
|
||||||
otp,
|
otp,
|
||||||
|
password: newPassword,
|
||||||
})
|
})
|
||||||
setIsLoading(false)
|
setIsLoading(false)
|
||||||
|
|
||||||
@@ -87,7 +84,6 @@ export default function ResetPasswordForm({
|
|||||||
setError("")
|
setError("")
|
||||||
await authClient.emailOtp.requestPasswordReset({ email })
|
await authClient.emailOtp.requestPasswordReset({ email })
|
||||||
setResendTimer(300)
|
setResendTimer(300)
|
||||||
setCanResend(false)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isSuccess) {
|
if (isSuccess) {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import { useState } from "react"
|
import { useState } from "react"
|
||||||
import { Loader2 } from "lucide-react"
|
import { Loader2 } from "lucide-react"
|
||||||
import { authClient } from "@/lib/auth-client"
|
import { authClient } from "@/lib/auth-client"
|
||||||
import { signupSchema, type SignupInput } from "@/lib/auth/validation"
|
import { signupSchema } from "@/lib/auth/validation"
|
||||||
import SocialButtons from "./social-buttons"
|
import SocialButtons from "./social-buttons"
|
||||||
import PasswordStrengthMeter from "./password-strength"
|
import PasswordStrengthMeter from "./password-strength"
|
||||||
import Link from "next/link"
|
import Link from "next/link"
|
||||||
@@ -61,7 +61,7 @@ export default function SignupFormStep({ onSuccess }: SignupFormStepProps) {
|
|||||||
Create your account
|
Create your account
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-xs text-[#ebe4f1]/50 mt-1">
|
<p className="text-xs text-[#ebe4f1]/50 mt-1">
|
||||||
Choose how you'd like to sign up
|
Choose how you'd like to sign up
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
+17
-7
@@ -9,7 +9,7 @@ import { CircleXIcon, Gamepad2Icon, MenuIcon, XIcon } from "lucide-react"
|
|||||||
import { routes } from "@/lib/routes"
|
import { routes } from "@/lib/routes"
|
||||||
import { usePathname, useRouter, useSearchParams } from "next/navigation"
|
import { usePathname, useRouter, useSearchParams } from "next/navigation"
|
||||||
import { useDebounce } from "@/lib/hooks/useDebounce"
|
import { useDebounce } from "@/lib/hooks/useDebounce"
|
||||||
import { authClient } from "@/lib/auth-client"
|
import { authClient, useSession } from "@/lib/auth-client"
|
||||||
import { LogOut, User } from "lucide-react"
|
import { LogOut, User } from "lucide-react"
|
||||||
|
|
||||||
export default function Navbar() {
|
export default function Navbar() {
|
||||||
@@ -27,13 +27,14 @@ export default function Navbar() {
|
|||||||
const inputRef = useRef<HTMLInputElement>(null)
|
const inputRef = useRef<HTMLInputElement>(null)
|
||||||
|
|
||||||
const { data: session, isPending: isSessionLoading } =
|
const { data: session, isPending: isSessionLoading } =
|
||||||
authClient.useSession()
|
useSession()
|
||||||
const [userMenuOpen, setUserMenuOpen] = useState(false)
|
const [userMenuOpen, setUserMenuOpen] = useState(false)
|
||||||
|
|
||||||
// Sync search query with URL ?q= param
|
// Sync search query with URL ?q= param
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const q = searchParams.get("q") || ""
|
const q = searchParams.get("q") || ""
|
||||||
setSearchQuery(q)
|
const timeout = setTimeout(() => setSearchQuery(q), 0)
|
||||||
|
return () => clearTimeout(timeout)
|
||||||
}, [searchParams])
|
}, [searchParams])
|
||||||
|
|
||||||
// Update URL when debounced query changes (skip if already matches)
|
// Update URL when debounced query changes (skip if already matches)
|
||||||
@@ -61,13 +62,16 @@ export default function Navbar() {
|
|||||||
sessionStorage.getItem("focusSearch") === "true"
|
sessionStorage.getItem("focusSearch") === "true"
|
||||||
) {
|
) {
|
||||||
sessionStorage.removeItem("focusSearch")
|
sessionStorage.removeItem("focusSearch")
|
||||||
setForceFocusStyles(true)
|
const focusTimeout = setTimeout(() => setForceFocusStyles(true), 0)
|
||||||
// Focus the input after the layout animation element mounts
|
// Focus the input after the layout animation element mounts
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
inputRef.current?.focus()
|
inputRef.current?.focus()
|
||||||
})
|
})
|
||||||
const timer = setTimeout(() => setForceFocusStyles(false), 450)
|
const timer = setTimeout(() => setForceFocusStyles(false), 450)
|
||||||
return () => clearTimeout(timer)
|
return () => {
|
||||||
|
clearTimeout(focusTimeout)
|
||||||
|
clearTimeout(timer)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, [isLanding, searchQuery])
|
}, [isLanding, searchQuery])
|
||||||
|
|
||||||
@@ -200,9 +204,12 @@ export default function Navbar() {
|
|||||||
className='flex items-center gap-2 px-2 py-1.5 rounded-lg hover:bg-white/[0.05] transition-colors'
|
className='flex items-center gap-2 px-2 py-1.5 rounded-lg hover:bg-white/[0.05] transition-colors'
|
||||||
>
|
>
|
||||||
{session.user.image ? (
|
{session.user.image ? (
|
||||||
<img
|
<Image
|
||||||
src={session.user.image}
|
src={session.user.image}
|
||||||
alt=''
|
alt=''
|
||||||
|
width={28}
|
||||||
|
height={28}
|
||||||
|
unoptimized
|
||||||
className='h-7 w-7 rounded-full'
|
className='h-7 w-7 rounded-full'
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
@@ -320,9 +327,12 @@ export default function Navbar() {
|
|||||||
<div className='space-y-3'>
|
<div className='space-y-3'>
|
||||||
<div className='flex items-center gap-2'>
|
<div className='flex items-center gap-2'>
|
||||||
{session.user.image ? (
|
{session.user.image ? (
|
||||||
<img
|
<Image
|
||||||
src={session.user.image}
|
src={session.user.image}
|
||||||
alt=''
|
alt=''
|
||||||
|
width={32}
|
||||||
|
height={32}
|
||||||
|
unoptimized
|
||||||
className='h-8 w-8 rounded-full'
|
className='h-8 w-8 rounded-full'
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ export const searchUnifiedRoutes = new Elysia({ prefix: "/search" }).get(
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Fetch platform support + anti-cheat for local games
|
// Fetch platform support + anti-cheat for local games
|
||||||
let platformSupportMap = new Map<
|
const platformSupportMap = new Map<
|
||||||
string,
|
string,
|
||||||
{
|
{
|
||||||
isSupported: boolean
|
isSupported: boolean
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
import { createAuthClient } from 'better-auth/client'
|
import { createAuthClient } from 'better-auth/react'
|
||||||
import { adminClient, emailOTPClient, lastLoginMethodClient } from 'better-auth/client/plugins'
|
import { adminClient, emailOTPClient, lastLoginMethodClient } from 'better-auth/client/plugins'
|
||||||
import { passkeyClient } from '@better-auth/passkey/client'
|
import { passkeyClient } from '@better-auth/passkey/client'
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user