Switch from magic link to password-based authentication

- Update signup page with password and confirm password fields
- Update login page with password field
- Remove magic link/OTP flow entirely
- Auto-redirect to dashboard after successful auth

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-13 12:40:05 +01:00
parent a7f7423d7f
commit 01739a50cd
2 changed files with 67 additions and 71 deletions

View File

@@ -2,17 +2,19 @@
import { useState } from 'react'
import Link from 'next/link'
import { useRouter } from 'next/navigation'
import { createClient } from '@/lib/supabase/client'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'
import { Bot, Loader2, Mail } from 'lucide-react'
import { Bot, Loader2 } from 'lucide-react'
export default function LoginPage() {
const router = useRouter()
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const [isLoading, setIsLoading] = useState(false)
const [isSent, setIsSent] = useState(false)
const [error, setError] = useState<string | null>(null)
const handleLogin = async (e: React.FormEvent) => {
@@ -22,11 +24,9 @@ export default function LoginPage() {
const supabase = createClient()
const { error } = await supabase.auth.signInWithOtp({
const { error } = await supabase.auth.signInWithPassword({
email,
options: {
emailRedirectTo: `${window.location.origin}/auth/callback`,
},
password,
})
if (error) {
@@ -35,34 +35,7 @@ export default function LoginPage() {
return
}
setIsSent(true)
setIsLoading(false)
}
if (isSent) {
return (
<div className="min-h-screen flex items-center justify-center bg-zinc-50 dark:bg-zinc-950 px-4">
<Card className="w-full max-w-md">
<CardHeader className="text-center">
<div className="mx-auto w-12 h-12 rounded-full bg-primary/10 flex items-center justify-center mb-4">
<Mail className="h-6 w-6 text-primary" />
</div>
<CardTitle>Check your email</CardTitle>
<CardDescription>
We sent a magic link to <strong>{email}</strong>
</CardDescription>
</CardHeader>
<CardContent className="text-center text-sm text-zinc-500">
Click the link in your email to sign in. If you don&apos;t see it, check your spam folder.
</CardContent>
<CardFooter className="flex justify-center">
<Button variant="ghost" onClick={() => setIsSent(false)}>
Try a different email
</Button>
</CardFooter>
</Card>
</div>
)
router.push('/dashboard')
}
return (
@@ -74,7 +47,7 @@ export default function LoginPage() {
<span className="text-xl font-bold">Mylder</span>
</Link>
<CardTitle>Welcome back</CardTitle>
<CardDescription>Sign in to your account with a magic link</CardDescription>
<CardDescription>Sign in to your account</CardDescription>
</CardHeader>
<form onSubmit={handleLogin}>
<CardContent className="space-y-4">
@@ -90,6 +63,18 @@ export default function LoginPage() {
disabled={isLoading}
/>
</div>
<div className="space-y-2">
<Label htmlFor="password">Password</Label>
<Input
id="password"
type="password"
placeholder="••••••••"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
disabled={isLoading}
/>
</div>
{error && (
<p className="text-sm text-red-500">{error}</p>
)}
@@ -99,10 +84,10 @@ export default function LoginPage() {
{isLoading ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
Sending link...
Signing in...
</>
) : (
'Send Magic Link'
'Sign In'
)}
</Button>
<p className="text-sm text-center text-zinc-500">

View File

@@ -2,31 +2,45 @@
import { useState } from 'react'
import Link from 'next/link'
import { useRouter } from 'next/navigation'
import { createClient } from '@/lib/supabase/client'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'
import { Bot, Loader2, Mail } from 'lucide-react'
import { Bot, Loader2 } from 'lucide-react'
export default function SignupPage() {
const router = useRouter()
const [email, setEmail] = useState('')
const [fullName, setFullName] = useState('')
const [password, setPassword] = useState('')
const [confirmPassword, setConfirmPassword] = useState('')
const [isLoading, setIsLoading] = useState(false)
const [isSent, setIsSent] = useState(false)
const [error, setError] = useState<string | null>(null)
const handleSignup = async (e: React.FormEvent) => {
e.preventDefault()
setIsLoading(true)
setError(null)
if (password !== confirmPassword) {
setError('Passwords do not match')
return
}
if (password.length < 6) {
setError('Password must be at least 6 characters')
return
}
setIsLoading(true)
const supabase = createClient()
const { error } = await supabase.auth.signInWithOtp({
const { error } = await supabase.auth.signUp({
email,
password,
options: {
emailRedirectTo: `${window.location.origin}/auth/callback`,
data: {
full_name: fullName,
},
@@ -39,34 +53,7 @@ export default function SignupPage() {
return
}
setIsSent(true)
setIsLoading(false)
}
if (isSent) {
return (
<div className="min-h-screen flex items-center justify-center bg-zinc-50 dark:bg-zinc-950 px-4">
<Card className="w-full max-w-md">
<CardHeader className="text-center">
<div className="mx-auto w-12 h-12 rounded-full bg-primary/10 flex items-center justify-center mb-4">
<Mail className="h-6 w-6 text-primary" />
</div>
<CardTitle>Check your email</CardTitle>
<CardDescription>
We sent a magic link to <strong>{email}</strong>
</CardDescription>
</CardHeader>
<CardContent className="text-center text-sm text-zinc-500">
Click the link in your email to complete your signup. If you don&apos;t see it, check your spam folder.
</CardContent>
<CardFooter className="flex justify-center">
<Button variant="ghost" onClick={() => setIsSent(false)}>
Try a different email
</Button>
</CardFooter>
</Card>
</div>
)
router.push('/dashboard')
}
return (
@@ -106,6 +93,30 @@ export default function SignupPage() {
disabled={isLoading}
/>
</div>
<div className="space-y-2">
<Label htmlFor="password">Password</Label>
<Input
id="password"
type="password"
placeholder="••••••••"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
disabled={isLoading}
/>
</div>
<div className="space-y-2">
<Label htmlFor="confirmPassword">Confirm Password</Label>
<Input
id="confirmPassword"
type="password"
placeholder="••••••••"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
required
disabled={isLoading}
/>
</div>
{error && (
<p className="text-sm text-red-500">{error}</p>
)}
@@ -118,7 +129,7 @@ export default function SignupPage() {
Creating account...
</>
) : (
'Get Started Free'
'Create Account'
)}
</Button>
<p className="text-sm text-center text-zinc-500">