first commit

This commit is contained in:
Beyhan Oğur
2026-04-26 22:16:43 +03:00
commit 6d95e27114
97 changed files with 15687 additions and 0 deletions

View File

@@ -0,0 +1,13 @@
export default function DashboardLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<div className="flex min-h-screen flex-col">
<main className="flex-1 p-6 md:p-8 pt-6">
{children}
</main>
</div>
);
}

View File

@@ -0,0 +1,309 @@
"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>
);
}