first commit
This commit is contained in:
170
app/auth/activate/[uid]/[token]/page.tsx
Normal file
170
app/auth/activate/[uid]/[token]/page.tsx
Normal file
@@ -0,0 +1,170 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { useParams, useRouter } from "next/navigation";
|
||||
import Link from "next/link";
|
||||
|
||||
const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000/api/v1";
|
||||
|
||||
export default function ActivatePage() {
|
||||
const params = useParams();
|
||||
const router = useRouter();
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [success, setSuccess] = useState(false);
|
||||
const [error, setError] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
const activateAccount = async () => {
|
||||
const { uid, token } = params;
|
||||
|
||||
if (!uid || !token) {
|
||||
setError("Geçersiz aktivasyon linki.");
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/auth/users/activation/`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
uid,
|
||||
token,
|
||||
}),
|
||||
});
|
||||
|
||||
if (response.ok || response.status === 204) {
|
||||
setSuccess(true);
|
||||
setTimeout(() => {
|
||||
router.push("/auth/login");
|
||||
}, 3000);
|
||||
} else {
|
||||
const data = await response.json();
|
||||
setError(
|
||||
data.detail ||
|
||||
"Aktivasyon başarısız. Link geçersiz veya süresi dolmuş olabilir."
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
setError("Bir hata oluştu. Lütfen daha sonra tekrar deneyin.");
|
||||
console.error("Activation error:", err);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
activateAccount();
|
||||
}, [params, router]);
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-gray-50">
|
||||
<div className="max-w-md w-full space-y-8 p-8 bg-white rounded-lg shadow">
|
||||
<div className="text-center">
|
||||
{loading && (
|
||||
<>
|
||||
<div className="mx-auto flex items-center justify-center h-12 w-12 rounded-full bg-blue-100">
|
||||
<svg
|
||||
className="animate-spin h-6 w-6 text-blue-600"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<circle
|
||||
className="opacity-25"
|
||||
cx="12"
|
||||
cy="12"
|
||||
r="10"
|
||||
stroke="currentColor"
|
||||
strokeWidth="4"
|
||||
/>
|
||||
<path
|
||||
className="opacity-75"
|
||||
fill="currentColor"
|
||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<h2 className="mt-6 text-center text-3xl font-bold text-gray-900">
|
||||
Hesabınız Aktifleştiriliyor...
|
||||
</h2>
|
||||
<p className="mt-2 text-center text-sm text-gray-600">
|
||||
Lütfen bekleyin.
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
|
||||
{success && (
|
||||
<>
|
||||
<div className="mx-auto flex items-center justify-center h-12 w-12 rounded-full bg-green-100">
|
||||
<svg
|
||||
className="h-6 w-6 text-green-600"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M5 13l4 4L19 7"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<h2 className="mt-6 text-center text-3xl font-bold text-gray-900">
|
||||
Aktivasyon Başarılı!
|
||||
</h2>
|
||||
<p className="mt-2 text-center text-sm text-gray-600">
|
||||
Hesabınız başarıyla aktifleştirildi. Artık giriş yapabilirsiniz.
|
||||
</p>
|
||||
<p className="mt-2 text-center text-sm text-gray-500">
|
||||
Giriş sayfasına yönlendiriliyorsunuz...
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
|
||||
{error && (
|
||||
<>
|
||||
<div className="mx-auto flex items-center justify-center h-12 w-12 rounded-full bg-red-100">
|
||||
<svg
|
||||
className="h-6 w-6 text-red-600"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<h2 className="mt-6 text-center text-3xl font-bold text-gray-900">
|
||||
Aktivasyon Başarısız
|
||||
</h2>
|
||||
<p className="mt-2 text-center text-sm text-red-600">
|
||||
{error}
|
||||
</p>
|
||||
<div className="mt-6 space-y-3">
|
||||
<Link
|
||||
href="/auth/resend-activation"
|
||||
className="block w-full text-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700"
|
||||
>
|
||||
Aktivasyon Emaili Tekrar Gönder
|
||||
</Link>
|
||||
<Link
|
||||
href="/auth/login"
|
||||
className="block w-full text-center py-2 px-4 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50"
|
||||
>
|
||||
Giriş Sayfasına Dön
|
||||
</Link>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
137
app/auth/error/page.tsx
Normal file
137
app/auth/error/page.tsx
Normal file
@@ -0,0 +1,137 @@
|
||||
"use client";
|
||||
|
||||
import { useSearchParams } from "next/navigation";
|
||||
import Link from "next/link";
|
||||
import { Suspense } from "react";
|
||||
|
||||
function ErrorContent() {
|
||||
const searchParams = useSearchParams();
|
||||
const error = searchParams.get("error");
|
||||
|
||||
const errorMessages: Record<string, { title: string; description: string }> = {
|
||||
Configuration: {
|
||||
title: "Yapılandırma Hatası",
|
||||
description: "Authentication sisteminde bir yapılandırma hatası oluştu.",
|
||||
},
|
||||
AccessDenied: {
|
||||
title: "Erişim Reddedildi",
|
||||
description: "Bu kaynağa erişim izniniz yok.",
|
||||
},
|
||||
Verification: {
|
||||
title: "Doğrulama Hatası",
|
||||
description: "Doğrulama linki geçersiz veya süresi dolmuş.",
|
||||
},
|
||||
OAuthSignin: {
|
||||
title: "OAuth Giriş Hatası",
|
||||
description: "OAuth sağlayıcısına bağlanırken bir hata oluştu.",
|
||||
},
|
||||
OAuthCallback: {
|
||||
title: "OAuth Callback Hatası",
|
||||
description: "OAuth callback işlemi başarısız oldu.",
|
||||
},
|
||||
OAuthCreateAccount: {
|
||||
title: "Hesap Oluşturma Hatası",
|
||||
description: "OAuth ile hesap oluşturulurken bir hata oluştu.",
|
||||
},
|
||||
EmailCreateAccount: {
|
||||
title: "Email Hesap Oluşturma Hatası",
|
||||
description: "Email ile hesap oluşturulurken bir hata oluştu.",
|
||||
},
|
||||
Callback: {
|
||||
title: "Callback Hatası",
|
||||
description: "Authentication callback işlemi başarısız oldu.",
|
||||
},
|
||||
OAuthAccountNotLinked: {
|
||||
title: "Hesap Bağlantısı Hatası",
|
||||
description:
|
||||
"Bu email adresi zaten farklı bir yöntemle kayıtlı. Lütfen o yöntemle giriş yapın.",
|
||||
},
|
||||
EmailSignin: {
|
||||
title: "Email Giriş Hatası",
|
||||
description: "Email doğrulama linki gönderilemedi.",
|
||||
},
|
||||
CredentialsSignin: {
|
||||
title: "Giriş Başarısız",
|
||||
description: "Email veya şifreniz hatalı. Lütfen tekrar deneyin.",
|
||||
},
|
||||
SessionRequired: {
|
||||
title: "Oturum Gerekli",
|
||||
description: "Bu sayfaya erişmek için giriş yapmanız gerekiyor.",
|
||||
},
|
||||
Default: {
|
||||
title: "Bir Hata Oluştu",
|
||||
description: "Beklenmeyen bir hata oluştu. Lütfen tekrar deneyin.",
|
||||
},
|
||||
};
|
||||
|
||||
const errorInfo = error ? errorMessages[error] || errorMessages.Default : errorMessages.Default;
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-gray-50 py-12 px-4 sm:px-6 lg:px-8">
|
||||
<div className="max-w-md w-full space-y-8">
|
||||
<div className="text-center">
|
||||
<div className="mx-auto flex items-center justify-center h-12 w-12 rounded-full bg-red-100">
|
||||
<svg
|
||||
className="h-6 w-6 text-red-600"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<h2 className="mt-6 text-center text-3xl font-bold text-gray-900">
|
||||
{errorInfo.title}
|
||||
</h2>
|
||||
<p className="mt-2 text-center text-sm text-gray-600">{errorInfo.description}</p>
|
||||
{error && (
|
||||
<p className="mt-2 text-center text-xs text-gray-500">
|
||||
Hata kodu: {error}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="mt-8 space-y-3">
|
||||
<Link
|
||||
href="/auth/login"
|
||||
className="block w-full text-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700"
|
||||
>
|
||||
Giriş Sayfasına Dön
|
||||
</Link>
|
||||
<Link
|
||||
href="/auth/register"
|
||||
className="block w-full text-center py-2 px-4 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50"
|
||||
>
|
||||
Yeni Hesap Oluştur
|
||||
</Link>
|
||||
<Link
|
||||
href="/"
|
||||
className="block w-full text-center py-2 px-4 text-sm font-medium text-blue-600 hover:text-blue-500"
|
||||
>
|
||||
Ana Sayfaya Dön
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function AuthErrorPage() {
|
||||
return (
|
||||
<Suspense fallback={
|
||||
<div className="min-h-screen flex items-center justify-center bg-gray-50">
|
||||
<div className="text-center">
|
||||
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto"></div>
|
||||
<p className="mt-4 text-gray-600">Yükleniyor...</p>
|
||||
</div>
|
||||
</div>
|
||||
}>
|
||||
<ErrorContent />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
198
app/auth/login/page.tsx
Normal file
198
app/auth/login/page.tsx
Normal file
@@ -0,0 +1,198 @@
|
||||
"use client";
|
||||
|
||||
import { signIn } from "next-auth/react";
|
||||
import { useState, FormEvent } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import Link from "next/link";
|
||||
|
||||
export default function LoginPage() {
|
||||
const [email, setEmail] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [error, setError] = useState("");
|
||||
const [loading, setLoading] = useState(false);
|
||||
const router = useRouter();
|
||||
|
||||
const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
setError("");
|
||||
setLoading(true);
|
||||
|
||||
try {
|
||||
const result = await signIn("credentials", {
|
||||
email,
|
||||
password,
|
||||
redirect: false,
|
||||
});
|
||||
|
||||
if (result?.error) {
|
||||
setError("Giriş başarısız. Email veya şifrenizi kontrol edin. Hesabınızı aktifleştirdiğinizden emin olun.");
|
||||
} else if (result?.ok) {
|
||||
router.push("/dashboard");
|
||||
router.refresh();
|
||||
}
|
||||
} catch {
|
||||
setError("Bir hata oluştu. Lütfen tekrar deneyin.");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSocialLogin = async (provider: "google" | "github") => {
|
||||
setError("");
|
||||
try {
|
||||
await signIn(provider, {
|
||||
callbackUrl: "/dashboard",
|
||||
});
|
||||
} catch {
|
||||
setError(`${provider === "google" ? "Google" : "GitHub"} ile giriş başarısız.`);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-gray-50 py-12 px-4 sm:px-6 lg:px-8">
|
||||
<div className="max-w-md w-full space-y-8">
|
||||
<div>
|
||||
<h2 className="mt-6 text-center text-3xl font-bold text-gray-900">
|
||||
Hesabınıza Giriş Yapın
|
||||
</h2>
|
||||
<p className="mt-2 text-center text-sm text-gray-600">
|
||||
Veya{" "}
|
||||
<Link
|
||||
href="/auth/register"
|
||||
className="font-medium text-blue-600 hover:text-blue-500"
|
||||
>
|
||||
yeni hesap oluşturun
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Social Login Buttons */}
|
||||
<div className="space-y-3">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleSocialLogin("google")}
|
||||
className="w-full flex items-center justify-center gap-3 py-2 px-4 border border-gray-300 rounded-md shadow-sm bg-white text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
|
||||
>
|
||||
<svg className="w-5 h-5" viewBox="0 0 24 24">
|
||||
<path
|
||||
fill="#4285F4"
|
||||
d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"
|
||||
/>
|
||||
<path
|
||||
fill="#34A853"
|
||||
d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"
|
||||
/>
|
||||
<path
|
||||
fill="#FBBC05"
|
||||
d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"
|
||||
/>
|
||||
<path
|
||||
fill="#EA4335"
|
||||
d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"
|
||||
/>
|
||||
</svg>
|
||||
Google ile Giriş Yap
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleSocialLogin("github")}
|
||||
className="w-full flex items-center justify-center gap-3 py-2 px-4 border border-gray-300 rounded-md shadow-sm bg-white text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
|
||||
>
|
||||
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
GitHub ile Giriş Yap
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="relative">
|
||||
<div className="absolute inset-0 flex items-center">
|
||||
<div className="w-full border-t border-gray-300" />
|
||||
</div>
|
||||
<div className="relative flex justify-center text-sm">
|
||||
<span className="px-2 bg-gray-50 text-gray-500">Veya email ile</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Email/Password Form */}
|
||||
<form className="mt-8 space-y-6" onSubmit={handleSubmit}>
|
||||
<div className="rounded-md shadow-sm space-y-4">
|
||||
<div>
|
||||
<label htmlFor="email" className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Email adresi
|
||||
</label>
|
||||
<input
|
||||
id="email"
|
||||
name="email"
|
||||
type="email"
|
||||
autoComplete="email"
|
||||
required
|
||||
className="appearance-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-md focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
|
||||
placeholder="ornek@email.com"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="password" className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Şifre
|
||||
</label>
|
||||
<input
|
||||
id="password"
|
||||
name="password"
|
||||
type="password"
|
||||
autoComplete="current-password"
|
||||
required
|
||||
className="appearance-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-md focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
|
||||
placeholder="Şifreniz"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="text-sm">
|
||||
<Link
|
||||
href="/auth/resend-activation"
|
||||
className="font-medium text-blue-600 hover:text-blue-500"
|
||||
>
|
||||
Aktivasyon emaili tekrar gönder
|
||||
</Link>
|
||||
</div>
|
||||
<div className="text-sm">
|
||||
<Link
|
||||
href="/auth/password-reset"
|
||||
className="font-medium text-blue-600 hover:text-blue-500"
|
||||
>
|
||||
Şifremi unuttum
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<div className="rounded-md bg-red-50 p-4">
|
||||
<p className="text-sm text-red-800">{error}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
className="group relative w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
{loading ? "Giriş yapılıyor..." : "Giriş Yap"}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
203
app/auth/password-reset/confirm/[uid]/[token]/page.tsx
Normal file
203
app/auth/password-reset/confirm/[uid]/[token]/page.tsx
Normal file
@@ -0,0 +1,203 @@
|
||||
"use client";
|
||||
|
||||
import { useState, FormEvent } from "react";
|
||||
import { useParams, useRouter } from "next/navigation";
|
||||
import Link from "next/link";
|
||||
|
||||
const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000/api/v1";
|
||||
|
||||
export default function PasswordResetConfirmPage() {
|
||||
const params = useParams();
|
||||
const router = useRouter();
|
||||
const [formData, setFormData] = useState({
|
||||
new_password: "",
|
||||
re_new_password: "",
|
||||
});
|
||||
const [error, setError] = useState("");
|
||||
const [fieldErrors, setFieldErrors] = useState<Record<string, string[]>>({});
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [success, setSuccess] = useState(false);
|
||||
|
||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setFormData({
|
||||
...formData,
|
||||
[e.target.name]: e.target.value,
|
||||
});
|
||||
// Clear field error when user starts typing
|
||||
if (fieldErrors[e.target.name]) {
|
||||
const newErrors = { ...fieldErrors };
|
||||
delete newErrors[e.target.name];
|
||||
setFieldErrors(newErrors);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
setError("");
|
||||
setFieldErrors({});
|
||||
setLoading(true);
|
||||
|
||||
const { uid, token } = params;
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/auth/users/reset_password_confirm/`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
uid,
|
||||
token,
|
||||
new_password: formData.new_password,
|
||||
re_new_password: formData.re_new_password,
|
||||
}),
|
||||
});
|
||||
|
||||
if (response.ok || response.status === 204) {
|
||||
setSuccess(true);
|
||||
setTimeout(() => {
|
||||
router.push("/auth/login");
|
||||
}, 3000);
|
||||
} else {
|
||||
const data = await response.json();
|
||||
if (data.new_password || data.re_new_password) {
|
||||
setFieldErrors(data);
|
||||
} else if (data.detail) {
|
||||
setError(data.detail);
|
||||
} else if (data.token) {
|
||||
setError("Şifre sıfırlama linki geçersiz veya süresi dolmuş.");
|
||||
} else {
|
||||
setError("Şifre sıfırlama başarısız. Lütfen bilgilerinizi kontrol edin.");
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
setError("Bir hata oluştu. Lütfen tekrar deneyin.");
|
||||
console.error("Password reset confirm error:", err);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (success) {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-gray-50">
|
||||
<div className="max-w-md w-full space-y-8 p-8 bg-white rounded-lg shadow">
|
||||
<div className="text-center">
|
||||
<div className="mx-auto flex items-center justify-center h-12 w-12 rounded-full bg-green-100">
|
||||
<svg
|
||||
className="h-6 w-6 text-green-600"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M5 13l4 4L19 7"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<h2 className="mt-6 text-center text-3xl font-bold text-gray-900">
|
||||
Şifre Sıfırlama Başarılı!
|
||||
</h2>
|
||||
<p className="mt-2 text-center text-sm text-gray-600">
|
||||
Şifreniz başarıyla değiştirildi. Artık yeni şifrenizle giriş yapabilirsiniz.
|
||||
</p>
|
||||
<p className="mt-2 text-center text-sm text-gray-500">
|
||||
Giriş sayfasına yönlendiriliyorsunuz...
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-gray-50 py-12 px-4 sm:px-6 lg:px-8">
|
||||
<div className="max-w-md w-full space-y-8">
|
||||
<div>
|
||||
<h2 className="mt-6 text-center text-3xl font-bold text-gray-900">
|
||||
Yeni Şifre Belirle
|
||||
</h2>
|
||||
<p className="mt-2 text-center text-sm text-gray-600">
|
||||
Hesabınız için yeni bir şifre oluşturun.
|
||||
</p>
|
||||
</div>
|
||||
<form className="mt-8 space-y-6" onSubmit={handleSubmit}>
|
||||
<div className="rounded-md shadow-sm space-y-4">
|
||||
<div>
|
||||
<label htmlFor="new_password" className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Yeni Şifre
|
||||
</label>
|
||||
<input
|
||||
id="new_password"
|
||||
name="new_password"
|
||||
type="password"
|
||||
autoComplete="new-password"
|
||||
required
|
||||
className={`appearance-none relative block w-full px-3 py-2 border ${
|
||||
fieldErrors.new_password ? "border-red-300" : "border-gray-300"
|
||||
} placeholder-gray-500 text-gray-900 rounded-md focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm`}
|
||||
placeholder="Minimum 8 karakter"
|
||||
value={formData.new_password}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
{fieldErrors.new_password && (
|
||||
<p className="mt-1 text-sm text-red-600">{fieldErrors.new_password[0]}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="re_new_password" className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Yeni Şifre Tekrar
|
||||
</label>
|
||||
<input
|
||||
id="re_new_password"
|
||||
name="re_new_password"
|
||||
type="password"
|
||||
autoComplete="new-password"
|
||||
required
|
||||
className={`appearance-none relative block w-full px-3 py-2 border ${
|
||||
fieldErrors.re_new_password ? "border-red-300" : "border-gray-300"
|
||||
} placeholder-gray-500 text-gray-900 rounded-md focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm`}
|
||||
placeholder="Şifrenizi tekrar girin"
|
||||
value={formData.re_new_password}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
{fieldErrors.re_new_password && (
|
||||
<p className="mt-1 text-sm text-red-600">{fieldErrors.re_new_password[0]}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<div className="rounded-md bg-red-50 p-4">
|
||||
<p className="text-sm text-red-800">{error}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
className="group relative w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
{loading ? "Şifre değiştiriliyor..." : "Şifreyi Değiştir"}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="text-center">
|
||||
<Link
|
||||
href="/auth/login"
|
||||
className="text-sm font-medium text-blue-600 hover:text-blue-500"
|
||||
>
|
||||
Giriş sayfasına dön
|
||||
</Link>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
153
app/auth/password-reset/page.tsx
Normal file
153
app/auth/password-reset/page.tsx
Normal file
@@ -0,0 +1,153 @@
|
||||
"use client";
|
||||
|
||||
import { useState, FormEvent } from "react";
|
||||
import Link from "next/link";
|
||||
|
||||
const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000/api/v1";
|
||||
|
||||
export default function PasswordResetPage() {
|
||||
const [email, setEmail] = useState("");
|
||||
const [error, setError] = useState("");
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [success, setSuccess] = useState(false);
|
||||
|
||||
const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
setError("");
|
||||
setLoading(true);
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/auth/users/reset_password/`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ email }),
|
||||
});
|
||||
|
||||
if (response.ok || response.status === 204) {
|
||||
setSuccess(true);
|
||||
} else {
|
||||
const data = await response.json();
|
||||
setError(data.detail || data.email?.[0] || "Email gönderilemedi.");
|
||||
}
|
||||
} catch (err) {
|
||||
setError("Bir hata oluştu. Lütfen tekrar deneyin.");
|
||||
console.error("Password reset error:", err);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (success) {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-gray-50">
|
||||
<div className="max-w-md w-full space-y-8 p-8 bg-white rounded-lg shadow">
|
||||
<div className="text-center">
|
||||
<div className="mx-auto flex items-center justify-center h-12 w-12 rounded-full bg-green-100">
|
||||
<svg
|
||||
className="h-6 w-6 text-green-600"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M5 13l4 4L19 7"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<h2 className="mt-6 text-center text-3xl font-bold text-gray-900">
|
||||
Email Gönderildi!
|
||||
</h2>
|
||||
<p className="mt-2 text-center text-sm text-gray-600">
|
||||
Şifre sıfırlama linki email adresinize gönderildi. Lütfen email adresinizi kontrol edin.
|
||||
</p>
|
||||
<p className="mt-2 text-center text-xs text-gray-500">
|
||||
Email gelmedi mi? Spam klasörünü kontrol etmeyi unutmayın.
|
||||
</p>
|
||||
<div className="mt-6">
|
||||
<Link
|
||||
href="/auth/login"
|
||||
className="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700"
|
||||
>
|
||||
Giriş Sayfasına Dön
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-gray-50 py-12 px-4 sm:px-6 lg:px-8">
|
||||
<div className="max-w-md w-full space-y-8">
|
||||
<div>
|
||||
<h2 className="mt-6 text-center text-3xl font-bold text-gray-900">
|
||||
Şifremi Unuttum
|
||||
</h2>
|
||||
<p className="mt-2 text-center text-sm text-gray-600">
|
||||
Email adresinizi girin, size şifre sıfırlama linki gönderelim.
|
||||
</p>
|
||||
</div>
|
||||
<form className="mt-8 space-y-6" onSubmit={handleSubmit}>
|
||||
<div>
|
||||
<label htmlFor="email" className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Email adresi
|
||||
</label>
|
||||
<input
|
||||
id="email"
|
||||
name="email"
|
||||
type="email"
|
||||
autoComplete="email"
|
||||
required
|
||||
className="appearance-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-md focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
|
||||
placeholder="ornek@email.com"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<div className="rounded-md bg-red-50 p-4">
|
||||
<p className="text-sm text-red-800">{error}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
className="group relative w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
{loading ? "Gönderiliyor..." : "Şifre Sıfırlama Linki Gönder"}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="text-center space-y-2">
|
||||
<div>
|
||||
<Link
|
||||
href="/auth/login"
|
||||
className="text-sm font-medium text-blue-600 hover:text-blue-500"
|
||||
>
|
||||
Giriş sayfasına dön
|
||||
</Link>
|
||||
</div>
|
||||
<div>
|
||||
<Link
|
||||
href="/auth/register"
|
||||
className="text-sm font-medium text-blue-600 hover:text-blue-500"
|
||||
>
|
||||
Hesabınız yok mu? Kayıt olun
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
257
app/auth/register/page.tsx
Normal file
257
app/auth/register/page.tsx
Normal file
@@ -0,0 +1,257 @@
|
||||
"use client";
|
||||
|
||||
import { useState, FormEvent } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import Link from "next/link";
|
||||
|
||||
const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000/api/v1";
|
||||
|
||||
export default function RegisterPage() {
|
||||
const [formData, setFormData] = useState({
|
||||
email: "",
|
||||
password: "",
|
||||
re_password: "",
|
||||
first_name: "",
|
||||
last_name: "",
|
||||
});
|
||||
const [error, setError] = useState<string>("");
|
||||
const [fieldErrors, setFieldErrors] = useState<Record<string, string[]>>({});
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [success, setSuccess] = useState(false);
|
||||
const router = useRouter();
|
||||
|
||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setFormData({
|
||||
...formData,
|
||||
[e.target.name]: e.target.value,
|
||||
});
|
||||
// Clear field error when user starts typing
|
||||
if (fieldErrors[e.target.name]) {
|
||||
const newErrors = { ...fieldErrors };
|
||||
delete newErrors[e.target.name];
|
||||
setFieldErrors(newErrors);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
setError("");
|
||||
setFieldErrors({});
|
||||
setLoading(true);
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/auth/users/`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(formData),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
if (data.email || data.password || data.first_name || data.last_name) {
|
||||
setFieldErrors(data);
|
||||
} else if (data.detail) {
|
||||
setError(data.detail);
|
||||
} else {
|
||||
setError("Kayıt başarısız. Lütfen bilgilerinizi kontrol edin.");
|
||||
}
|
||||
} else {
|
||||
setSuccess(true);
|
||||
setTimeout(() => {
|
||||
router.push("/auth/login");
|
||||
}, 3000);
|
||||
}
|
||||
} catch (err) {
|
||||
setError("Bir hata oluştu. Lütfen tekrar deneyin.");
|
||||
console.error("Registration error:", err);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (success) {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-gray-50">
|
||||
<div className="max-w-md w-full space-y-8 p-8 bg-white rounded-lg shadow">
|
||||
<div className="text-center">
|
||||
<div className="mx-auto flex items-center justify-center h-12 w-12 rounded-full bg-green-100">
|
||||
<svg
|
||||
className="h-6 w-6 text-green-600"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M5 13l4 4L19 7"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<h2 className="mt-6 text-center text-3xl font-bold text-gray-900">
|
||||
Kayıt Başarılı!
|
||||
</h2>
|
||||
<p className="mt-2 text-center text-sm text-gray-600">
|
||||
Email adresinize gönderilen aktivasyon linkine tıklayarak hesabınızı aktifleştirin.
|
||||
</p>
|
||||
<p className="mt-2 text-center text-sm text-gray-500">
|
||||
Giriş sayfasına yönlendiriliyorsunuz...
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-gray-50 py-12 px-4 sm:px-6 lg:px-8">
|
||||
<div className="max-w-md w-full space-y-8">
|
||||
<div>
|
||||
<h2 className="mt-6 text-center text-3xl font-bold text-gray-900">
|
||||
Hesap Oluştur
|
||||
</h2>
|
||||
<p className="mt-2 text-center text-sm text-gray-600">
|
||||
Veya{" "}
|
||||
<Link
|
||||
href="/auth/login"
|
||||
className="font-medium text-blue-600 hover:text-blue-500"
|
||||
>
|
||||
mevcut hesabınızla giriş yapın
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
<form className="mt-8 space-y-6" onSubmit={handleSubmit}>
|
||||
<div className="rounded-md shadow-sm space-y-4">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label htmlFor="first_name" className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Ad
|
||||
</label>
|
||||
<input
|
||||
id="first_name"
|
||||
name="first_name"
|
||||
type="text"
|
||||
className={`appearance-none relative block w-full px-3 py-2 border ${
|
||||
fieldErrors.first_name ? "border-red-300" : "border-gray-300"
|
||||
} placeholder-gray-500 text-gray-900 rounded-md focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm`}
|
||||
placeholder="Ad"
|
||||
value={formData.first_name}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
{fieldErrors.first_name && (
|
||||
<p className="mt-1 text-sm text-red-600">{fieldErrors.first_name[0]}</p>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="last_name" className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Soyad
|
||||
</label>
|
||||
<input
|
||||
id="last_name"
|
||||
name="last_name"
|
||||
type="text"
|
||||
className={`appearance-none relative block w-full px-3 py-2 border ${
|
||||
fieldErrors.last_name ? "border-red-300" : "border-gray-300"
|
||||
} placeholder-gray-500 text-gray-900 rounded-md focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm`}
|
||||
placeholder="Soyad"
|
||||
value={formData.last_name}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
{fieldErrors.last_name && (
|
||||
<p className="mt-1 text-sm text-red-600">{fieldErrors.last_name[0]}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="email" className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Email adresi
|
||||
</label>
|
||||
<input
|
||||
id="email"
|
||||
name="email"
|
||||
type="email"
|
||||
autoComplete="email"
|
||||
required
|
||||
className={`appearance-none relative block w-full px-3 py-2 border ${
|
||||
fieldErrors.email ? "border-red-300" : "border-gray-300"
|
||||
} placeholder-gray-500 text-gray-900 rounded-md focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm`}
|
||||
placeholder="ornek@email.com"
|
||||
value={formData.email}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
{fieldErrors.email && (
|
||||
<p className="mt-1 text-sm text-red-600">{fieldErrors.email[0]}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="password" className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Şifre
|
||||
</label>
|
||||
<input
|
||||
id="password"
|
||||
name="password"
|
||||
type="password"
|
||||
autoComplete="new-password"
|
||||
required
|
||||
className={`appearance-none relative block w-full px-3 py-2 border ${
|
||||
fieldErrors.password ? "border-red-300" : "border-gray-300"
|
||||
} placeholder-gray-500 text-gray-900 rounded-md focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm`}
|
||||
placeholder="Minimum 8 karakter"
|
||||
value={formData.password}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
{fieldErrors.password && (
|
||||
<p className="mt-1 text-sm text-red-600">{fieldErrors.password[0]}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="re_password" className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Şifre Tekrar
|
||||
</label>
|
||||
<input
|
||||
id="re_password"
|
||||
name="re_password"
|
||||
type="password"
|
||||
autoComplete="new-password"
|
||||
required
|
||||
className={`appearance-none relative block w-full px-3 py-2 border ${
|
||||
fieldErrors.re_password ? "border-red-300" : "border-gray-300"
|
||||
} placeholder-gray-500 text-gray-900 rounded-md focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm`}
|
||||
placeholder="Şifrenizi tekrar girin"
|
||||
value={formData.re_password}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
{fieldErrors.re_password && (
|
||||
<p className="mt-1 text-sm text-red-600">{fieldErrors.re_password[0]}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<div className="rounded-md bg-red-50 p-4">
|
||||
<p className="text-sm text-red-800">{error}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
className="group relative w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
{loading ? "Kayıt yapılıyor..." : "Kayıt Ol"}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
140
app/auth/resend-activation/page.tsx
Normal file
140
app/auth/resend-activation/page.tsx
Normal file
@@ -0,0 +1,140 @@
|
||||
"use client";
|
||||
|
||||
import { useState, FormEvent } from "react";
|
||||
import Link from "next/link";
|
||||
|
||||
const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000/api/v1";
|
||||
|
||||
export default function ResendActivationPage() {
|
||||
const [email, setEmail] = useState("");
|
||||
const [error, setError] = useState("");
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [success, setSuccess] = useState(false);
|
||||
|
||||
const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
setError("");
|
||||
setLoading(true);
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/auth/users/resend_activation/`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ email }),
|
||||
});
|
||||
|
||||
if (response.ok || response.status === 204) {
|
||||
setSuccess(true);
|
||||
} else {
|
||||
const data = await response.json();
|
||||
setError(data.detail || data.email?.[0] || "Email gönderilemedi.");
|
||||
}
|
||||
} catch (err) {
|
||||
setError("Bir hata oluştu. Lütfen tekrar deneyin.");
|
||||
console.error("Resend activation error:", err);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (success) {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-gray-50">
|
||||
<div className="max-w-md w-full space-y-8 p-8 bg-white rounded-lg shadow">
|
||||
<div className="text-center">
|
||||
<div className="mx-auto flex items-center justify-center h-12 w-12 rounded-full bg-green-100">
|
||||
<svg
|
||||
className="h-6 w-6 text-green-600"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M5 13l4 4L19 7"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<h2 className="mt-6 text-center text-3xl font-bold text-gray-900">
|
||||
Email Gönderildi!
|
||||
</h2>
|
||||
<p className="mt-2 text-center text-sm text-gray-600">
|
||||
Aktivasyon emaili başarıyla gönderildi. Lütfen email adresinizi kontrol edin.
|
||||
</p>
|
||||
<div className="mt-6">
|
||||
<Link
|
||||
href="/auth/login"
|
||||
className="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700"
|
||||
>
|
||||
Giriş Sayfasına Dön
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-gray-50 py-12 px-4 sm:px-6 lg:px-8">
|
||||
<div className="max-w-md w-full space-y-8">
|
||||
<div>
|
||||
<h2 className="mt-6 text-center text-3xl font-bold text-gray-900">
|
||||
Aktivasyon Emaili Tekrar Gönder
|
||||
</h2>
|
||||
<p className="mt-2 text-center text-sm text-gray-600">
|
||||
Email adresinizi girin, size yeni bir aktivasyon linki gönderelim.
|
||||
</p>
|
||||
</div>
|
||||
<form className="mt-8 space-y-6" onSubmit={handleSubmit}>
|
||||
<div>
|
||||
<label htmlFor="email" className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Email adresi
|
||||
</label>
|
||||
<input
|
||||
id="email"
|
||||
name="email"
|
||||
type="email"
|
||||
autoComplete="email"
|
||||
required
|
||||
className="appearance-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-md focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
|
||||
placeholder="ornek@email.com"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<div className="rounded-md bg-red-50 p-4">
|
||||
<p className="text-sm text-red-800">{error}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
className="group relative w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
{loading ? "Gönderiliyor..." : "Aktivasyon Emaili Gönder"}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="text-center">
|
||||
<Link
|
||||
href="/auth/login"
|
||||
className="text-sm font-medium text-blue-600 hover:text-blue-500"
|
||||
>
|
||||
Giriş sayfasına dön
|
||||
</Link>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user