From 5fef7460cffad95295f538176d335beebf0c647b Mon Sep 17 00:00:00 2001 From: ayrisdev Date: Tue, 1 Sep 2026 12:28:19 +0300 Subject: [PATCH] =?UTF-8?q?frontend:=20kullan=C4=B1c=C4=B1=20y=C3=B6netimi?= =?UTF-8?q?=20sayfas=C4=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit /users: kullanıcı listesi, ekleme formu, silme (kendi hesabını veya son kalan tek kullanıcıyı silmeye izin verilmiyor). Nav'a link eklendi. Co-Authored-By: Claude Sonnet 5 --- apps/frontend/app/layout.tsx | 1 + apps/frontend/app/users/actions.ts | 35 ++++++++++++++++ apps/frontend/app/users/page.tsx | 66 ++++++++++++++++++++++++++++++ 3 files changed, 102 insertions(+) create mode 100644 apps/frontend/app/users/actions.ts create mode 100644 apps/frontend/app/users/page.tsx diff --git a/apps/frontend/app/layout.tsx b/apps/frontend/app/layout.tsx index 9e8c8e9..3adc96f 100644 --- a/apps/frontend/app/layout.tsx +++ b/apps/frontend/app/layout.tsx @@ -19,6 +19,7 @@ export default async function RootLayout({ children }: { children: React.ReactNo Kanal Durumu Segment & Aday Kütüphanesi Ayarlar + Kullanıcılar {session.username}
diff --git a/apps/frontend/app/users/actions.ts b/apps/frontend/app/users/actions.ts new file mode 100644 index 0000000..c5bf115 --- /dev/null +++ b/apps/frontend/app/users/actions.ts @@ -0,0 +1,35 @@ +"use server"; + +import bcrypt from "bcryptjs"; +import { prisma } from "@streamclipper/db"; +import { revalidatePath } from "next/cache"; +import { getSession } from "../../lib/auth"; + +export async function addUser(formData: FormData) { + const username = String(formData.get("username") ?? "").trim(); + const password = String(formData.get("password") ?? ""); + + if (!username || password.length < 8) { + throw new Error("Kullanıcı adı gerekli, şifre en az 8 karakter olmalı."); + } + + const passwordHash = await bcrypt.hash(password, 12); + await prisma.user.create({ data: { username, passwordHash } }); + + revalidatePath("/users"); +} + +export async function deleteUser(userId: string) { + const session = await getSession(); + if (session?.sub === userId) { + throw new Error("Kendi hesabını silemezsin."); + } + + const totalUsers = await prisma.user.count(); + if (totalUsers <= 1) { + throw new Error("Son kalan kullanıcı silinemez."); + } + + await prisma.user.delete({ where: { id: userId } }); + revalidatePath("/users"); +} diff --git a/apps/frontend/app/users/page.tsx b/apps/frontend/app/users/page.tsx new file mode 100644 index 0000000..494e844 --- /dev/null +++ b/apps/frontend/app/users/page.tsx @@ -0,0 +1,66 @@ +import { prisma } from "@streamclipper/db"; +import { getSession } from "../../lib/auth"; +import { addUser, deleteUser } from "./actions"; +import { DeleteButton } from "../components/DeleteButton"; + +export const dynamic = "force-dynamic"; + +const fieldStyle = { + background: "var(--bg)", + border: "1px solid var(--border)", + borderRadius: "6px", + padding: "0.5rem 0.7rem", + color: "var(--text)", + fontSize: "0.9rem", +}; + +export default async function UsersPage() { + const [users, session] = await Promise.all([ + prisma.user.findMany({ orderBy: { createdAt: "asc" } }), + getSession(), + ]); + + return ( + <> +

Kullanıcılar

+ + + + + +
+ + + + + + + + + + + {users.map((u) => ( + + + + + + ))} + +
Kullanıcı AdıOluşturulma
+ {u.username} + {u.id === session?.sub && sen} + {u.createdAt.toLocaleString("tr-TR")} + {u.id !== session?.sub && users.length > 1 && ( +
+ + + )} +
+ + ); +}