276 lines
9.3 KiB
TypeScript
276 lines
9.3 KiB
TypeScript
"use client";
|
||
|
||
import { useSession, signOut } from "next-auth/react";
|
||
import { useState, useEffect, 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";
|
||
|
||
interface UserData {
|
||
id: number;
|
||
email: string;
|
||
first_name: string;
|
||
last_name: string;
|
||
is_active: boolean;
|
||
date_joined: string;
|
||
}
|
||
|
||
export default function ProfilePage() {
|
||
const { data: session, status } = useSession();
|
||
const router = useRouter();
|
||
const [userData, setUserData] = useState<UserData | null>(null);
|
||
const [formData, setFormData] = useState({
|
||
first_name: "",
|
||
last_name: "",
|
||
});
|
||
const [loading, setLoading] = useState(true);
|
||
const [updating, setUpdating] = useState(false);
|
||
const [error, setError] = useState("");
|
||
const [success, setSuccess] = useState(false);
|
||
|
||
useEffect(() => {
|
||
if (status === "unauthenticated") {
|
||
router.push("/auth/login");
|
||
}
|
||
}, [status, router]);
|
||
|
||
useEffect(() => {
|
||
const fetchUserData = async () => {
|
||
if (!session?.accessToken) return;
|
||
|
||
try {
|
||
const response = await fetch(`${API_BASE_URL}/auth/users/me/`, {
|
||
headers: {
|
||
Authorization: `Bearer ${session.accessToken}`,
|
||
},
|
||
});
|
||
|
||
if (response.ok) {
|
||
const data = await response.json();
|
||
setUserData(data);
|
||
setFormData({
|
||
first_name: data.first_name || "",
|
||
last_name: data.last_name || "",
|
||
});
|
||
} else if (response.status === 401) {
|
||
// Token expired, logout
|
||
signOut({ callbackUrl: "/auth/login" });
|
||
}
|
||
} catch (err) {
|
||
console.error("Error fetching user data:", err);
|
||
setError("Kullanıcı bilgileri yüklenemedi.");
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
};
|
||
|
||
if (session?.accessToken) {
|
||
fetchUserData();
|
||
}
|
||
}, [session]);
|
||
|
||
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||
setFormData({
|
||
...formData,
|
||
[e.target.name]: e.target.value,
|
||
});
|
||
};
|
||
|
||
const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
|
||
e.preventDefault();
|
||
setError("");
|
||
setSuccess(false);
|
||
setUpdating(true);
|
||
|
||
if (!session?.accessToken) return;
|
||
|
||
try {
|
||
const response = await fetch(`${API_BASE_URL}/auth/users/me/`, {
|
||
method: "PATCH",
|
||
headers: {
|
||
"Content-Type": "application/json",
|
||
Authorization: `Bearer ${session.accessToken}`,
|
||
},
|
||
body: JSON.stringify(formData),
|
||
});
|
||
|
||
if (response.ok) {
|
||
const data = await response.json();
|
||
setUserData(data);
|
||
setSuccess(true);
|
||
setTimeout(() => setSuccess(false), 3000);
|
||
} else if (response.status === 401) {
|
||
signOut({ callbackUrl: "/auth/login" });
|
||
} else {
|
||
const data = await response.json();
|
||
setError(data.detail || "Profil güncellenemedi.");
|
||
}
|
||
} catch (err) {
|
||
setError("Bir hata oluştu. Lütfen tekrar deneyin.");
|
||
console.error("Update profile error:", err);
|
||
} finally {
|
||
setUpdating(false);
|
||
}
|
||
};
|
||
|
||
const handleLogout = () => {
|
||
signOut({ callbackUrl: "/auth/login" });
|
||
};
|
||
|
||
if (status === "loading" || loading) {
|
||
return (
|
||
<div className="min-h-screen flex items-center justify-center bg-gray-50">
|
||
<div className="text-center">
|
||
<div className="inline-block animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600"></div>
|
||
<p className="mt-4 text-gray-600">Yükleniyor...</p>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
if (!session) {
|
||
return null;
|
||
}
|
||
|
||
return (
|
||
<div className="min-h-screen bg-gray-50 py-12 px-4 sm:px-6 lg:px-8">
|
||
<div className="max-w-3xl mx-auto">
|
||
<div className="bg-white shadow rounded-lg">
|
||
{/* Header */}
|
||
<div className="px-4 py-5 sm:px-6 border-b border-gray-200">
|
||
<div className="flex items-center justify-between">
|
||
<div>
|
||
<h1 className="text-2xl font-bold text-gray-900">Profil</h1>
|
||
<p className="mt-1 text-sm text-gray-500">
|
||
Hesap bilgilerinizi görüntüleyin ve güncelleyin.
|
||
</p>
|
||
</div>
|
||
<Link
|
||
href="/dashboard"
|
||
className="inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50"
|
||
>
|
||
Dashboard
|
||
</Link>
|
||
</div>
|
||
</div>
|
||
|
||
{/* User Info */}
|
||
{userData && (
|
||
<div className="px-4 py-5 sm:p-6">
|
||
<dl className="grid grid-cols-1 gap-x-4 gap-y-6 sm:grid-cols-2">
|
||
<div className="sm:col-span-2">
|
||
<dt className="text-sm font-medium text-gray-500">Email</dt>
|
||
<dd className="mt-1 text-sm text-gray-900">{userData.email}</dd>
|
||
</div>
|
||
<div>
|
||
<dt className="text-sm font-medium text-gray-500">Üyelik Tarihi</dt>
|
||
<dd className="mt-1 text-sm text-gray-900">
|
||
{new Date(userData.date_joined).toLocaleDateString("tr-TR", {
|
||
year: "numeric",
|
||
month: "long",
|
||
day: "numeric",
|
||
})}
|
||
</dd>
|
||
</div>
|
||
<div>
|
||
<dt className="text-sm font-medium text-gray-500">Hesap Durumu</dt>
|
||
<dd className="mt-1">
|
||
<span
|
||
className={`inline-flex px-2 py-1 text-xs font-semibold rounded-full ${
|
||
userData.is_active
|
||
? "bg-green-100 text-green-800"
|
||
: "bg-red-100 text-red-800"
|
||
}`}
|
||
>
|
||
{userData.is_active ? "Aktif" : "Pasif"}
|
||
</span>
|
||
</dd>
|
||
</div>
|
||
</dl>
|
||
</div>
|
||
)}
|
||
|
||
{/* Update Form */}
|
||
<div className="px-4 py-5 sm:p-6 border-t border-gray-200">
|
||
<h2 className="text-lg font-medium text-gray-900 mb-4">
|
||
Profil Bilgilerini Güncelle
|
||
</h2>
|
||
<form onSubmit={handleSubmit} className="space-y-4">
|
||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||
<div>
|
||
<label
|
||
htmlFor="first_name"
|
||
className="block text-sm font-medium text-gray-700 mb-1"
|
||
>
|
||
Ad
|
||
</label>
|
||
<input
|
||
type="text"
|
||
name="first_name"
|
||
id="first_name"
|
||
className="appearance-none block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
|
||
value={formData.first_name}
|
||
onChange={handleChange}
|
||
/>
|
||
</div>
|
||
<div>
|
||
<label
|
||
htmlFor="last_name"
|
||
className="block text-sm font-medium text-gray-700 mb-1"
|
||
>
|
||
Soyad
|
||
</label>
|
||
<input
|
||
type="text"
|
||
name="last_name"
|
||
id="last_name"
|
||
className="appearance-none block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
|
||
value={formData.last_name}
|
||
onChange={handleChange}
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
{success && (
|
||
<div className="rounded-md bg-green-50 p-4">
|
||
<p className="text-sm text-green-800">
|
||
Profil bilgileriniz başarıyla güncellendi!
|
||
</p>
|
||
</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={updating}
|
||
className="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium 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"
|
||
>
|
||
{updating ? "Güncelleniyor..." : "Profili Güncelle"}
|
||
</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
|
||
{/* Logout Button */}
|
||
<div className="px-4 py-5 sm:p-6 border-t border-gray-200">
|
||
<button
|
||
onClick={handleLogout}
|
||
className="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-red-600 hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500"
|
||
>
|
||
Çıkış Yap
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|