fix: Wrap useSearchParams in Suspense boundary
Next.js 15 requires useSearchParams() to be wrapped in a Suspense boundary for static page generation. - Split LoginForm into separate component - Add Suspense wrapper with loading skeleton - Fixes build error on /login page 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
56b919fe4a
commit
d21dc88812
|
|
@ -1,6 +1,6 @@
|
|||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
import { Suspense, useState } from 'react'
|
||||
import { useRouter, useSearchParams } from 'next/navigation'
|
||||
import { signIn } from 'next-auth/react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
|
|
@ -15,7 +15,7 @@ import {
|
|||
CardTitle,
|
||||
} from '@/components/ui/card'
|
||||
|
||||
export default function LoginPage() {
|
||||
function LoginForm() {
|
||||
const router = useRouter()
|
||||
const searchParams = useSearchParams()
|
||||
const callbackUrl = searchParams.get('callbackUrl') || '/'
|
||||
|
|
@ -55,79 +55,107 @@ export default function LoginPage() {
|
|||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-gray-50 py-12 px-4 sm:px-6 lg:px-8">
|
||||
<Card className="w-full max-w-md">
|
||||
<CardHeader className="space-y-1">
|
||||
<CardTitle className="text-2xl font-bold text-center">
|
||||
LetsBe Hub
|
||||
</CardTitle>
|
||||
<CardDescription className="text-center">
|
||||
Sign in to your account
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<CardContent className="space-y-4">
|
||||
{(error || loginError) && (
|
||||
<div className="p-3 text-sm text-red-500 bg-red-50 rounded-md">
|
||||
{error === 'CredentialsSignin'
|
||||
? 'Invalid email or password'
|
||||
: loginError || error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
type="button"
|
||||
variant={userType === 'staff' ? 'default' : 'outline'}
|
||||
className="flex-1"
|
||||
onClick={() => setUserType('staff')}
|
||||
>
|
||||
Staff Login
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
variant={userType === 'customer' ? 'default' : 'outline'}
|
||||
className="flex-1"
|
||||
onClick={() => setUserType('customer')}
|
||||
>
|
||||
Customer Login
|
||||
</Button>
|
||||
<Card className="w-full max-w-md">
|
||||
<CardHeader className="space-y-1">
|
||||
<CardTitle className="text-2xl font-bold text-center">
|
||||
LetsBe Hub
|
||||
</CardTitle>
|
||||
<CardDescription className="text-center">
|
||||
Sign in to your account
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<CardContent className="space-y-4">
|
||||
{(error || loginError) && (
|
||||
<div className="p-3 text-sm text-red-500 bg-red-50 rounded-md">
|
||||
{error === 'CredentialsSignin'
|
||||
? 'Invalid email or password'
|
||||
: loginError || error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="email">Email</Label>
|
||||
<Input
|
||||
id="email"
|
||||
type="email"
|
||||
placeholder="Enter your email"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
required
|
||||
autoComplete="email"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="password">Password</Label>
|
||||
<Input
|
||||
id="password"
|
||||
type="password"
|
||||
placeholder="Enter your password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
required
|
||||
autoComplete="current-password"
|
||||
/>
|
||||
</div>
|
||||
</CardContent>
|
||||
|
||||
<CardFooter>
|
||||
<Button type="submit" className="w-full" disabled={isLoading}>
|
||||
{isLoading ? 'Signing in...' : 'Sign In'}
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
type="button"
|
||||
variant={userType === 'staff' ? 'default' : 'outline'}
|
||||
className="flex-1"
|
||||
onClick={() => setUserType('staff')}
|
||||
>
|
||||
Staff Login
|
||||
</Button>
|
||||
</CardFooter>
|
||||
</form>
|
||||
</Card>
|
||||
<Button
|
||||
type="button"
|
||||
variant={userType === 'customer' ? 'default' : 'outline'}
|
||||
className="flex-1"
|
||||
onClick={() => setUserType('customer')}
|
||||
>
|
||||
Customer Login
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="email">Email</Label>
|
||||
<Input
|
||||
id="email"
|
||||
type="email"
|
||||
placeholder="Enter your email"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
required
|
||||
autoComplete="email"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="password">Password</Label>
|
||||
<Input
|
||||
id="password"
|
||||
type="password"
|
||||
placeholder="Enter your password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
required
|
||||
autoComplete="current-password"
|
||||
/>
|
||||
</div>
|
||||
</CardContent>
|
||||
|
||||
<CardFooter>
|
||||
<Button type="submit" className="w-full" disabled={isLoading}>
|
||||
{isLoading ? 'Signing in...' : 'Sign In'}
|
||||
</Button>
|
||||
</CardFooter>
|
||||
</form>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
function LoginFormSkeleton() {
|
||||
return (
|
||||
<Card className="w-full max-w-md">
|
||||
<CardHeader className="space-y-1">
|
||||
<CardTitle className="text-2xl font-bold text-center">
|
||||
LetsBe Hub
|
||||
</CardTitle>
|
||||
<CardDescription className="text-center">
|
||||
Loading...
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="h-10 bg-gray-200 rounded animate-pulse" />
|
||||
<div className="h-10 bg-gray-200 rounded animate-pulse" />
|
||||
<div className="h-10 bg-gray-200 rounded animate-pulse" />
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
export default function LoginPage() {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-gray-50 py-12 px-4 sm:px-6 lg:px-8">
|
||||
<Suspense fallback={<LoginFormSkeleton />}>
|
||||
<LoginForm />
|
||||
</Suspense>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue