Add debug logging for OTP auth flow
This commit is contained in:
@@ -107,20 +107,35 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
||||
const sendAuthOTP = async (userId: string, email: string) => {
|
||||
try {
|
||||
const { data: { session } } = await supabase.auth.getSession();
|
||||
const token = session?.access_token || import.meta.env.VITE_SUPABASE_ANON_KEY;
|
||||
|
||||
console.log('Sending OTP request', { userId, email, hasSession: !!session });
|
||||
|
||||
const response = await fetch(
|
||||
`${import.meta.env.VITE_SUPABASE_URL}/functions/v1/send-auth-otp`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${session?.access_token || import.meta.env.VITE_SUPABASE_ANON_KEY}`,
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ user_id: userId, email }),
|
||||
}
|
||||
);
|
||||
|
||||
console.log('OTP response status:', response.status);
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
console.error('OTP request failed:', response.status, errorText);
|
||||
return {
|
||||
success: false,
|
||||
message: `HTTP ${response.status}: ${errorText}`
|
||||
};
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
console.log('OTP result:', result);
|
||||
return result;
|
||||
} catch (error: any) {
|
||||
console.error('Error sending OTP:', error);
|
||||
|
||||
@@ -7,18 +7,22 @@ import { Label } from '@/components/ui/label';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { toast } from '@/hooks/use-toast';
|
||||
import { z } from 'zod';
|
||||
import { ArrowLeft } from 'lucide-react';
|
||||
import { ArrowLeft, Mail } from 'lucide-react';
|
||||
|
||||
const emailSchema = z.string().email('Invalid email address');
|
||||
const passwordSchema = z.string().min(6, 'Password must be at least 6 characters');
|
||||
|
||||
export default function Auth() {
|
||||
const [isLogin, setIsLogin] = useState(true);
|
||||
const [showOTP, setShowOTP] = useState(false);
|
||||
const [email, setEmail] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [name, setName] = useState('');
|
||||
const [otpCode, setOtpCode] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
const { signIn, signUp, user } = useAuth();
|
||||
const [pendingUserId, setPendingUserId] = useState<string | null>(null);
|
||||
const [resendCountdown, setResendCountdown] = useState(0);
|
||||
const { signIn, signUp, user, sendAuthOTP, verifyAuthOTP } = useAuth();
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
@@ -27,6 +31,14 @@ export default function Auth() {
|
||||
}
|
||||
}, [user, navigate]);
|
||||
|
||||
// Countdown timer for resend OTP
|
||||
useEffect(() => {
|
||||
if (resendCountdown > 0) {
|
||||
const timer = setTimeout(() => setResendCountdown(resendCountdown - 1), 1000);
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
}, [resendCountdown]);
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
@@ -55,17 +67,89 @@ export default function Auth() {
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
const { error } = await signUp(email, password, name);
|
||||
|
||||
const { error, data } = await signUp(email, password, name);
|
||||
if (error) {
|
||||
if (error.message.includes('already registered')) {
|
||||
toast({ title: 'Error', description: 'This email is already registered. Please login instead.', variant: 'destructive' });
|
||||
} else {
|
||||
toast({ title: 'Error', description: error.message, variant: 'destructive' });
|
||||
}
|
||||
} else {
|
||||
toast({ title: 'Success', description: 'Check your email to confirm your account' });
|
||||
setLoading(false);
|
||||
} else if (data?.user) {
|
||||
// User created, now send OTP
|
||||
const userId = data.user.id;
|
||||
console.log('User created successfully:', { userId, email, session: data.session });
|
||||
|
||||
const result = await sendAuthOTP(userId, email);
|
||||
|
||||
console.log('OTP send result:', result);
|
||||
|
||||
if (result.success) {
|
||||
setPendingUserId(userId);
|
||||
setShowOTP(true);
|
||||
setResendCountdown(60); // 60 seconds cooldown
|
||||
toast({
|
||||
title: 'OTP Terkirim',
|
||||
description: 'Kode verifikasi telah dikirim ke email Anda. Silakan cek inbox.',
|
||||
});
|
||||
} else {
|
||||
toast({ title: 'Error', description: result.message, variant: 'destructive' });
|
||||
}
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleOTPSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (!pendingUserId) {
|
||||
toast({ title: 'Error', description: 'Session expired. Please try again.', variant: 'destructive' });
|
||||
setShowOTP(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (otpCode.length !== 6) {
|
||||
toast({ title: 'Error', description: 'OTP harus 6 digit', variant: 'destructive' });
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
|
||||
const result = await verifyAuthOTP(pendingUserId, otpCode);
|
||||
|
||||
if (result.success) {
|
||||
toast({
|
||||
title: 'Verifikasi Berhasil',
|
||||
description: 'Email Anda telah terverifikasi. Silakan login.',
|
||||
});
|
||||
setShowOTP(false);
|
||||
setIsLogin(true);
|
||||
// Reset form
|
||||
setName('');
|
||||
setOtpCode('');
|
||||
setPendingUserId(null);
|
||||
} else {
|
||||
toast({ title: 'Error', description: result.message, variant: 'destructive' });
|
||||
}
|
||||
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
const handleResendOTP = async () => {
|
||||
if (resendCountdown > 0 || !pendingUserId) return;
|
||||
|
||||
setLoading(true);
|
||||
|
||||
const result = await sendAuthOTP(pendingUserId, email);
|
||||
|
||||
if (result.success) {
|
||||
setResendCountdown(60);
|
||||
toast({ title: 'OTP Terkirim Ulang', description: 'Kode verifikasi baru telah dikirim ke email Anda.' });
|
||||
} else {
|
||||
toast({ title: 'Error', description: result.message, variant: 'destructive' });
|
||||
}
|
||||
|
||||
setLoading(false);
|
||||
};
|
||||
@@ -81,66 +165,141 @@ export default function Auth() {
|
||||
</Button>
|
||||
</Link>
|
||||
|
||||
<Card className="border-2 border-border shadow-md">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-2xl">{isLogin ? 'Login' : 'Sign Up'}</CardTitle>
|
||||
<CardDescription>
|
||||
{isLogin ? 'Enter your credentials to access your account' : 'Create a new account to get started'}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
{!isLogin && (
|
||||
{!showOTP ? (
|
||||
<Card className="border-2 border-border shadow-md">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-2xl">{isLogin ? 'Login' : 'Daftar'}</CardTitle>
|
||||
<CardDescription>
|
||||
{isLogin ? 'Masuk untuk mengakses akun Anda' : 'Buat akun baru untuk memulai'}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
{!isLogin && (
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="name">Nama</Label>
|
||||
<Input
|
||||
id="name"
|
||||
type="text"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
placeholder="Nama lengkap"
|
||||
className="border-2"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="name">Name</Label>
|
||||
<Label htmlFor="email">Email</Label>
|
||||
<Input
|
||||
id="name"
|
||||
type="text"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
placeholder="Your name"
|
||||
id="email"
|
||||
type="email"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
placeholder="email@anda.com"
|
||||
className="border-2"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="email">Email</Label>
|
||||
<Input
|
||||
id="email"
|
||||
type="email"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
placeholder="your@email.com"
|
||||
className="border-2"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="password">Password</Label>
|
||||
<Input
|
||||
id="password"
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
placeholder="••••••••"
|
||||
className="border-2"
|
||||
/>
|
||||
</div>
|
||||
<Button type="submit" className="w-full shadow-sm" disabled={loading}>
|
||||
{loading ? 'Loading...' : isLogin ? 'Login' : 'Sign Up'}
|
||||
</Button>
|
||||
</form>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="password">Password</Label>
|
||||
<Input
|
||||
id="password"
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
placeholder="••••••••"
|
||||
className="border-2"
|
||||
/>
|
||||
</div>
|
||||
<Button type="submit" className="w-full shadow-sm" disabled={loading}>
|
||||
{loading ? 'Memuat...' : isLogin ? 'Masuk' : 'Daftar'}
|
||||
</Button>
|
||||
</form>
|
||||
|
||||
<div className="mt-4 text-center">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setIsLogin(!isLogin)}
|
||||
className="text-sm text-muted-foreground hover:underline"
|
||||
>
|
||||
{isLogin ? "Don't have an account? Sign up" : 'Already have an account? Login'}
|
||||
</button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<div className="mt-4 text-center">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setIsLogin(!isLogin)}
|
||||
className="text-sm text-muted-foreground hover:underline"
|
||||
>
|
||||
{isLogin ? 'Belum punya akun? Daftar' : 'Sudah punya akun? Masuk'}
|
||||
</button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
) : (
|
||||
<Card className="border-2 border-border shadow-md">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-2xl flex items-center gap-2">
|
||||
<Mail className="w-6 h-6" />
|
||||
Verifikasi Email
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
Masukkan kode 6 digit yang telah dikirim ke <strong>{email}</strong>
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<form onSubmit={handleOTPSubmit} className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="otp">Kode OTP</Label>
|
||||
<Input
|
||||
id="otp"
|
||||
type="text"
|
||||
value={otpCode}
|
||||
onChange={(e) => {
|
||||
// Only allow numbers, max 6 digits
|
||||
const value = e.target.value.replace(/\D/g, '').slice(0, 6);
|
||||
setOtpCode(value);
|
||||
}}
|
||||
placeholder="123456"
|
||||
className="border-2 text-center text-2xl tracking-widest font-mono"
|
||||
maxLength={6}
|
||||
autoFocus
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Masukkan 6 digit kode dari email Anda
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Button type="submit" className="w-full shadow-sm" disabled={loading || otpCode.length !== 6}>
|
||||
{loading ? 'Memverifikasi...' : 'Verifikasi'}
|
||||
</Button>
|
||||
|
||||
<div className="text-center space-y-2">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Tidak menerima kode?
|
||||
</p>
|
||||
<Button
|
||||
type="button"
|
||||
variant="link"
|
||||
onClick={handleResendOTP}
|
||||
disabled={resendCountdown > 0 || loading}
|
||||
className="text-sm"
|
||||
>
|
||||
{resendCountdown > 0
|
||||
? `Kirim ulang dalam ${resendCountdown} detik`
|
||||
: 'Kirim ulang kode'}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="pt-4 border-t">
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
onClick={() => {
|
||||
setShowOTP(false);
|
||||
setOtpCode('');
|
||||
setPendingUserId(null);
|
||||
setResendCountdown(0);
|
||||
}}
|
||||
className="w-full text-sm"
|
||||
>
|
||||
Kembali ke form pendaftaran
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user