| 'use client'; |
|
|
| import { useState, useEffect, useMemo, useCallback } from 'react'; |
| import { useAuth } from '@/contexts/AuthContext'; |
| import { useMarket } from '@/contexts/MarketContext'; |
| import { useSearchParams } from 'next/navigation'; |
| import { Mail, Lock, User, AlertCircle, Loader2 } from 'lucide-react'; |
| import { isSupabaseEnabled } from '@/lib/supabase/client'; |
| import { validatePassword, getStrengthColor, getStrengthLabel, getStrengthPercent } from '@/lib/password-validation'; |
|
|
| export default function AuthForm() { |
| const { market } = useMarket(); |
| const isUS = market === 'us'; |
| const [isLogin, setIsLogin] = useState(true); |
| const [email, setEmail] = useState(''); |
| const [password, setPassword] = useState(''); |
| const [loading, setLoading] = useState(false); |
| const [error, setError] = useState(''); |
| const { signIn, signUp } = useAuth(); |
| const searchParams = useSearchParams(); |
| const passwordCheck = useMemo(() => validatePassword(password), [password]); |
| const t = useCallback((tr: string, en: string) => isUS ? en : tr, [isUS]); |
| const strengthLabel = isUS |
| ? passwordCheck.strength === 'fair' |
| ? 'Fair' |
| : passwordCheck.strength === 'good' |
| ? 'Good' |
| : passwordCheck.strength === 'strong' |
| ? 'Strong' |
| : getStrengthLabel(passwordCheck.strength) |
| : getStrengthLabel(passwordCheck.strength) |
|
|
| |
| useEffect(() => { |
| if (searchParams.get('banned') === '1') { |
| setError(t('Hesabınız engellenmiştir. Yönetici ile iletişime geçin.', 'Your account has been blocked. Contact the administrator.')); |
| } |
| }, [searchParams, t]); |
|
|
| const handleSubmit = async (e: React.FormEvent) => { |
| e.preventDefault(); |
| setError(''); |
| setLoading(true); |
|
|
| |
| if (!isLogin && !passwordCheck.valid) { |
| setError(t('Şifre gereksinimleri: ', 'Password requirements: ') + passwordCheck.errors.join(', ')); |
| setLoading(false); |
| return; |
| } |
|
|
| try { |
| const { error } = isLogin |
| ? await signIn(email, password) |
| : await signUp(email, password); |
|
|
| if (error) { |
| setError(error.message); |
| } else { |
| if (!isLogin) { |
| setError(t('Kayıt başarılı! Lütfen email adresinizi doğrulayın.', 'Registration successful! Please verify your email address.')); |
| } else { |
| |
| |
| const hasSbCookie = () => typeof document !== 'undefined' && document.cookie.includes('sb-') |
|
|
| let attempts = 0 |
| while (!hasSbCookie() && attempts < 20) { |
| |
| |
| await new Promise((r) => setTimeout(r, 50)) |
| attempts += 1 |
| } |
|
|
| if (!hasSbCookie()) { |
| setError( |
| t( |
| 'Giriş başarılı görünüyor ama oturum cookie yazılamadı. Lütfen siteyi sadece http://localhost:3001 üzerinden açın (127.0.0.1 değil) ve tarayıcıda cookie engeli olmadığından emin olun.', |
| 'Sign-in appears successful but the session cookie could not be written. Open the site only via http://localhost:3001 (not 127.0.0.1) and make sure cookies are not blocked in the browser.' |
| ) |
| ) |
| return |
| } |
|
|
| window.location.replace('/'); |
| } |
| } |
| } catch (err) { |
| setError(t('Bir hata oluştu. Lütfen tekrar deneyin.', 'An error occurred. Please try again.')); |
| } finally { |
| setLoading(false); |
| } |
| }; |
|
|
| return ( |
| <div className="max-w-md mx-auto"> |
| <div className="bg-white rounded-lg shadow-lg p-8"> |
| <div className="text-center mb-8"> |
| <User className="w-12 h-12 text-primary-600 mx-auto mb-4" /> |
| <h2 className="text-2xl font-bold text-gray-900"> |
| {isLogin ? t('Giriş Yap', 'Sign In') : t('Kayıt Ol', 'Sign Up')} |
| </h2> |
| <p className="text-gray-600 mt-2"> |
| {isLogin |
| ? t('Portföyünüze erişmek için giriş yapın', 'Sign in to access your portfolio') |
| : t('Yeni bir hesap oluşturun', 'Create a new account')} |
| </p> |
| </div> |
| |
| <form onSubmit={handleSubmit} className="space-y-6"> |
| {!isSupabaseEnabled && ( |
| <div className="flex items-start gap-2 p-4 bg-amber-50 border border-amber-200 rounded-lg"> |
| <AlertCircle className="w-5 h-5 text-amber-700 flex-shrink-0 mt-0.5" /> |
| <p className="text-sm text-amber-800"> |
| {t('Giriş/Kayıt devre dışı: Supabase yapılandırılmamış. Netlify/yerel ortamda', 'Sign-in/Sign-up disabled: Supabase is not configured. In Netlify/local environments add')} |
| <span className="font-mono"> NEXT_PUBLIC_SUPABASE_URL</span> ve |
| <span className="font-mono"> NEXT_PUBLIC_SUPABASE_ANON_KEY</span> {t('değişkenlerini ekleyin.', 'environment variables.')} |
| </p> |
| </div> |
| )} |
| <div> |
| <label htmlFor="auth-email" className="block text-sm font-medium text-gray-700 mb-2"> |
| Email |
| </label> |
| <div className="relative"> |
| <Mail className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-5 h-5" aria-hidden="true" /> |
| <input |
| id="auth-email" |
| type="email" |
| value={email} |
| onChange={(e) => setEmail(e.target.value)} |
| className="w-full pl-10 pr-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-primary-500" |
| placeholder="ornek@email.com" |
| required |
| autoComplete="email" |
| /> |
| </div> |
| </div> |
| |
| <div> |
| <label htmlFor="auth-password" className="block text-sm font-medium text-gray-700 mb-2"> |
| {t('Şifre', 'Password')} |
| </label> |
| <div className="relative"> |
| <Lock className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-5 h-5" aria-hidden="true" /> |
| <input |
| id="auth-password" |
| type="password" |
| value={password} |
| onChange={(e) => setPassword(e.target.value)} |
| className="w-full pl-10 pr-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-primary-500" |
| placeholder="••••••••" |
| required |
| minLength={8} |
| autoComplete={isLogin ? 'current-password' : 'new-password'} |
| /> |
| </div> |
| {!isLogin && password.length > 0 && ( |
| <div className="mt-2"> |
| <div className="flex items-center gap-2"> |
| <div className="flex-1 h-1.5 bg-gray-200 rounded-full overflow-hidden"> |
| <div |
| className={`h-full rounded-full transition-all duration-300 ${getStrengthColor(passwordCheck.strength)}`} |
| style={{ width: `${getStrengthPercent(passwordCheck.strength)}%` }} |
| /> |
| </div> |
| <span className="text-xs font-medium text-gray-600">{strengthLabel}</span> |
| </div> |
| {passwordCheck.errors.length > 0 && ( |
| <ul className="mt-1 space-y-0.5"> |
| {passwordCheck.errors.map((err, i) => ( |
| <li key={i} className="text-xs text-red-500">• {err}</li> |
| ))} |
| </ul> |
| )} |
| </div> |
| )} |
| {!isLogin && password.length === 0 && ( |
| <p className="text-xs text-gray-500 mt-1">{t('En az 8 karakter, büyük harf, küçük harf ve rakam', 'At least 8 characters, uppercase, lowercase, and a number')}</p> |
| )} |
| </div> |
| |
| {error && ( |
| <div className="flex items-start gap-2 p-4 bg-red-50 border border-red-200 rounded-lg"> |
| <AlertCircle className="w-5 h-5 text-red-600 flex-shrink-0 mt-0.5" /> |
| <p className="text-sm text-red-600">{error}</p> |
| </div> |
| )} |
| |
| <button |
| type="submit" |
| disabled={loading || !isSupabaseEnabled} |
| className="w-full bg-primary-600 text-white py-3 rounded-lg hover:bg-primary-700 disabled:bg-gray-300 disabled:cursor-not-allowed flex items-center justify-center gap-2 font-medium" |
| > |
| {loading ? ( |
| <> |
| <Loader2 className="w-5 h-5 animate-spin" /> |
| {t('İşlem yapılıyor...', 'Processing...')} |
| </> |
| ) : isLogin ? ( |
| t('Giriş Yap', 'Sign In') |
| ) : ( |
| t('Kayıt Ol', 'Sign Up') |
| )} |
| </button> |
| </form> |
| |
| <div className="mt-6 text-center"> |
| <button |
| onClick={() => { |
| setIsLogin(!isLogin); |
| setError(''); |
| }} |
| className="text-primary-600 hover:text-primary-800 font-medium" |
| > |
| {isLogin |
| ? t('Hesabınız yok mu? Kayıt olun', 'Don\'t have an account? Sign up') |
| : t('Zaten hesabınız var mı? Giriş yapın', 'Already have an account? Sign in')} |
| </button> |
| </div> |
| </div> |
| </div> |
| ); |
| } |
|
|