Files
Beyhan Oğur 6d95e27114 first commit
2026-04-26 22:16:43 +03:00

310 lines
15 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { useEffect, useState, useRef } from "react";
import { useAppDispatch, useAppSelector } from "@/lib/hooks";
import { fetchProfile, updateProfile, changePassword, changeEmail } from "@/lib/features/auth/authSlice";
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 { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { getAvatarUrl } from "@/lib/utils";
import Swal from "sweetalert2";
import { Loader2, Camera, Shield, Mail, User as UserIcon } from "lucide-react";
export default function ProfilePage() {
const dispatch = useAppDispatch();
const { user, isLoading } = useAppSelector((state) => state.auth);
const [file, setFile] = useState<File | null>(null);
const fileInputRef = useRef<HTMLInputElement>(null);
// Form states
const [username, setUsername] = useState("");
// Password change states
const [currentPassword, setCurrentPassword] = useState("");
const [newPassword, setNewPassword] = useState("");
// Email change states
const [newEmail, setNewEmail] = useState("");
const [emailPassword, setEmailPassword] = useState("");
const [isMounted, setIsMounted] = useState(false);
useEffect(() => {
setIsMounted(true);
dispatch(fetchProfile());
}, [dispatch]);
useEffect(() => {
if (user) {
setUsername(user.username);
}
}, [user]);
const handleAvatarChange = (e: React.ChangeEvent<HTMLInputElement>) => {
if (e.target.files && e.target.files[0]) {
setFile(e.target.files[0]);
}
};
const handleProfileUpdate = () => {
const formData = new FormData();
formData.append("user_name", username);
if (file) {
formData.append("avatar", file);
}
dispatch(updateProfile(formData))
.unwrap()
.then(() => {
Swal.fire("Başarılı", "Profil bilgileriniz güncellendi.", "success");
setFile(null);
})
.catch((err) => {
Swal.fire("Hata", err || "Profil güncellenemedi.", "error");
});
};
const handleChangePassword = () => {
if (!currentPassword || !newPassword) {
Swal.fire("Uyarı", "Lütfen tüm alanları doldurun.", "warning");
return;
}
if (newPassword.length < 6) {
Swal.fire("Uyarı", "Yeni şifre en az 6 karakter olmalıdır.", "warning");
return;
}
dispatch(changePassword({ current_password: currentPassword, new_password: newPassword }))
.unwrap()
.then(() => {
Swal.fire("Başarılı", "Şifreniz değiştirildi. Lütfen yeni şifrenizle tekrar giriş yapın.", "success");
setCurrentPassword("");
setNewPassword("");
})
.catch((err) => {
Swal.fire("Hata", err || "Şifre değiştirilemedi.", "error");
});
};
const handleChangeEmail = () => {
if (!newEmail || !emailPassword) {
Swal.fire("Uyarı", "Lütfen tüm alanları doldurun.", "warning");
return;
}
dispatch(changeEmail({ new_email: newEmail, password: emailPassword }))
.unwrap()
.then((res: any) => {
Swal.fire({
title: "Başarılı",
text: `Email adresiniz güncellendi. Lütfen ${newEmail} adresine gönderilen doğrulama linkine tıklayın.`,
icon: "success"
});
setNewEmail("");
setEmailPassword("");
})
.catch((err) => {
Swal.fire("Hata", err || "Email değiştirilemedi.", "error");
});
};
if (!isMounted) {
return <div className="flex justify-center pt-10"><Loader2 className="h-8 w-8 animate-spin" /></div>;
}
if (!user && isLoading) {
return <div className="flex justify-center pt-10"><Loader2 className="h-8 w-8 animate-spin" /></div>;
}
if (!user) {
return <div className="text-center pt-10">Kullanıcı bilgileri yüklenemedi.</div>;
}
return (
<div className="max-w-4xl mx-auto space-y-8">
<h1 className="text-3xl font-bold tracking-tight">Profilim</h1>
{/* Profile Info & Edit */}
<div className="grid gap-6 md:grid-cols-2">
<Card>
<CardHeader>
<CardTitle>Profil Bilgileri</CardTitle>
<CardDescription>Kişisel bilgilerinizi buradan güncelleyebilirsiniz.</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
<div className="flex flex-col items-center space-y-4">
<div className="relative">
<Avatar className="h-24 w-24 border-2 border-border">
<AvatarImage src={file ? URL.createObjectURL(file) : getAvatarUrl(user.avatar_url)} />
<AvatarFallback className="text-xl font-bold">
{user.username.slice(0, 2).toUpperCase()}
</AvatarFallback>
</Avatar>
<Button
size="icon"
variant="secondary"
className="absolute bottom-0 right-0 h-8 w-8 rounded-full shadow-md"
onClick={() => fileInputRef.current?.click()}
>
<Camera className="h-4 w-4" />
</Button>
<input
type="file"
ref={fileInputRef}
className="hidden"
accept="image/*"
onChange={handleAvatarChange}
/>
</div>
<div className="text-center">
<p className="font-semibold text-lg">{user.username}</p>
<p className="text-sm text-muted-foreground">{user.email}</p>
<div className="mt-2 flex items-center justify-center gap-2">
{user.roles.map(role => (
<span key={role.id} className="inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 border-transparent bg-primary text-primary-foreground hover:bg-primary/80">
{role.name}
</span>
))}
{user.is_oauth_user ? (
<span className="inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors border-transparent bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-300">
OAuth
</span>
) : (
user.email_verified && <span className="inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors border-transparent bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-300">
Email Onaylı
</span>
)}
</div>
</div>
</div>
<div className="space-y-2">
<Label htmlFor="username">Kullanıcı Adı</Label>
<div className="relative">
<UserIcon className="absolute left-3 top-3 h-4 w-4 text-muted-foreground" />
<Input
id="username"
value={username}
onChange={(e) => setUsername(e.target.value)}
className="pl-9"
/>
</div>
</div>
</CardContent>
<CardFooter>
<Button onClick={handleProfileUpdate} disabled={isLoading}>
{isLoading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
Güncelle
</Button>
</CardFooter>
</Card>
{/* Security Settings - Only for non-OAuth users */}
{!user.is_oauth_user && (
<div className="space-y-6">
{/* Change Password */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Shield className="h-5 w-5" /> Şifre Değiştir
</CardTitle>
<CardDescription>Güvenliğiniz için güçlü bir şifre kullanın.</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="space-y-2">
<Label htmlFor="current-password">Mevcut Şifre</Label>
<Input
id="current-password"
type="password"
value={currentPassword}
onChange={(e) => setCurrentPassword(e.target.value)}
/>
</div>
<div className="space-y-2">
<Label htmlFor="new-password">Yeni Şifre</Label>
<Input
id="new-password"
type="password"
value={newPassword}
onChange={(e) => setNewPassword(e.target.value)}
/>
</div>
</CardContent>
<CardFooter>
<Button onClick={handleChangePassword} disabled={isLoading} variant="outline">
Şifreyi Değiştir
</Button>
</CardFooter>
</Card>
{/* Change Email */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Mail className="h-5 w-5" /> Email Değiştir
</CardTitle>
<CardDescription>Email adresinizi değiştirirseniz yeniden doğrulama yapmanız gerekir.</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="space-y-2">
<Label htmlFor="new-email">Yeni Email Adresi</Label>
<Input
id="new-email"
type="email"
value={newEmail}
onChange={(e) => setNewEmail(e.target.value)}
/>
</div>
<div className="space-y-2">
<Label htmlFor="email-password">Şifreniz (Onay için)</Label>
<Input
id="email-password"
type="password"
value={emailPassword}
onChange={(e) => setEmailPassword(e.target.value)}
/>
</div>
</CardContent>
<CardFooter>
<Button onClick={handleChangeEmail} disabled={isLoading} variant="outline">
Email'i Güncelle
</Button>
</CardFooter>
</Card>
</div>
)}
{/* OAuth Info - Only for OAuth users */}
{user.is_oauth_user && (
<Card>
<CardHeader>
<CardTitle>Bağlı Hesaplar</CardTitle>
<CardDescription>Hesabınız aşağıdaki sosyal medya platformlarına bağlıdır.</CardDescription>
</CardHeader>
<CardContent>
<div className="flex gap-4">
{user.social_accounts?.map((account) => (
<div key={account.id} className="flex items-center gap-3 p-3 border rounded-lg bg-muted/50 w-full">
<div className="h-8 w-8 rounded-full bg-primary/10 flex items-center justify-center">
{account.provider === 'google' ? 'G' : account.provider.charAt(0).toUpperCase()}
</div>
<div>
<p className="font-medium capitalize">{account.provider}</p>
<p className="text-xs text-muted-foreground">{account.email}</p>
</div>
</div>
))}
{(!user.social_accounts || user.social_accounts.length === 0) && (
<p className="text-sm text-muted-foreground">Bağlı hesap bilgisi bulunamadı (OAuth User flag true olmasına rağmen).</p>
)}
</div>
</CardContent>
</Card>
)}
</div>
</div>
);
}