From 97eebbd72533618c31327efd8f5ee7aabb03d7f1 Mon Sep 17 00:00:00 2001 From: Kenan KARAER Date: Wed, 3 Dec 2025 22:38:44 +0300 Subject: [PATCH] Feat: Implement User Create and Edit pages --- .../dashboard/settings/users/[id]/actions.ts | 47 +++++++ .../settings/users/[id]/edit-user-form.tsx | 115 ++++++++++++++++ .../dashboard/settings/users/[id]/page.tsx | 40 ++++++ .../dashboard/settings/users/new/actions.ts | 52 +++++++ src/app/dashboard/settings/users/new/page.tsx | 17 +++ .../settings/users/new/user-form.tsx | 130 ++++++++++++++++++ src/app/dashboard/settings/users/page.tsx | 8 +- src/lib/supabase/admin.ts | 35 +++++ 8 files changed, 441 insertions(+), 3 deletions(-) create mode 100644 src/app/dashboard/settings/users/[id]/actions.ts create mode 100644 src/app/dashboard/settings/users/[id]/edit-user-form.tsx create mode 100644 src/app/dashboard/settings/users/[id]/page.tsx create mode 100644 src/app/dashboard/settings/users/new/actions.ts create mode 100644 src/app/dashboard/settings/users/new/page.tsx create mode 100644 src/app/dashboard/settings/users/new/user-form.tsx create mode 100644 src/lib/supabase/admin.ts diff --git a/src/app/dashboard/settings/users/[id]/actions.ts b/src/app/dashboard/settings/users/[id]/actions.ts new file mode 100644 index 0000000..f3e133f --- /dev/null +++ b/src/app/dashboard/settings/users/[id]/actions.ts @@ -0,0 +1,47 @@ +'use server' + +import { createClient } from "@/lib/supabase/server" +import { createAdminClient } from "@/lib/supabase/admin" +import { revalidatePath } from "next/cache" +import { logAction } from "@/lib/logger" + +export async function updateUser(userId: string, data: { full_name: string, role: string }) { + const supabase = await createClient() + + // 1. Update Profile (Regular client might work if RLS allows updating own profile or if user is admin) + // However, changing role usually requires admin privileges or specific RLS. + // Let's try with regular client first, if it fails, try admin client. + + const { error } = await supabase + .from('profiles') + .update({ + full_name: data.full_name, + role: data.role + }) + .eq('id', userId) + + if (error) { + // If regular update fails (likely due to RLS on role column), try admin client + const supabaseAdmin = await createAdminClient() + if (supabaseAdmin) { + const { error: adminError } = await supabaseAdmin + .from('profiles') + .update({ + full_name: data.full_name, + role: data.role + }) + .eq('id', userId) + + if (adminError) return { error: adminError.message } + } else { + return { error: "Yetkisiz işlem veya Service Role Key eksik." } + } + } + + await logAction('update_user', 'user', userId, { + full_name: data.full_name, + role: data.role + }) + + revalidatePath('/dashboard/settings/users') +} diff --git a/src/app/dashboard/settings/users/[id]/edit-user-form.tsx b/src/app/dashboard/settings/users/[id]/edit-user-form.tsx new file mode 100644 index 0000000..be33d2f --- /dev/null +++ b/src/app/dashboard/settings/users/[id]/edit-user-form.tsx @@ -0,0 +1,115 @@ +'use client' + +import { zodResolver } from "@hookform/resolvers/zod" +import { useForm } from "react-hook-form" +import * as z from "zod" +import { Button } from "@/components/ui/button" +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form" +import { Input } from "@/components/ui/input" +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" +import { updateUser } from "./actions" +import { useRouter } from "next/navigation" +import { useState } from "react" +import { toast } from "sonner" + +const formSchema = z.object({ + full_name: z.string().min(2, "İsim en az 2 karakter olmalıdır."), + role: z.enum(["admin", "staff"]), +}) + +interface EditUserFormProps { + user: { + id: string + full_name: string + role: "admin" | "staff" + email?: string + } +} + +export function EditUserForm({ user }: EditUserFormProps) { + const router = useRouter() + const [loading, setLoading] = useState(false) + + const form = useForm>({ + resolver: zodResolver(formSchema), + defaultValues: { + full_name: user.full_name, + role: user.role, + }, + }) + + async function onSubmit(values: z.infer) { + setLoading(true) + try { + const result = await updateUser(user.id, values) + if (result?.error) { + toast.error(result.error) + } else { + toast.success("Kullanıcı başarıyla güncellendi") + router.push('/dashboard/settings/users') + router.refresh() + } + } catch (error) { + toast.error("Bir hata oluştu") + } finally { + setLoading(false) + } + } + + return ( +
+ +
+ E-posta + +

E-posta adresi değiştirilemez.

+
+ + ( + + İsim Soyisim + + + + + + )} + /> + ( + + Rol + + + + )} + /> + + + + ) +} diff --git a/src/app/dashboard/settings/users/[id]/page.tsx b/src/app/dashboard/settings/users/[id]/page.tsx new file mode 100644 index 0000000..4f6e7d2 --- /dev/null +++ b/src/app/dashboard/settings/users/[id]/page.tsx @@ -0,0 +1,40 @@ +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" +import { EditUserForm } from "./edit-user-form" +import { createClient } from "@/lib/supabase/server" +import { notFound } from "next/navigation" + +export default async function EditUserPage({ params }: { params: { id: string } }) { + const supabase = await createClient() + + // Fetch profile + const { data: profile } = await supabase + .from('profiles') + .select('*') + .eq('id', params.id) + .single() + + if (!profile) { + notFound() + } + + // We can't easily get email from profiles if it's not stored there. + // But for editing, we mostly care about name and role. + + return ( +
+ + + Kullanıcı Düzenle + + + + + +
+ ) +} diff --git a/src/app/dashboard/settings/users/new/actions.ts b/src/app/dashboard/settings/users/new/actions.ts new file mode 100644 index 0000000..12f76fc --- /dev/null +++ b/src/app/dashboard/settings/users/new/actions.ts @@ -0,0 +1,52 @@ +'use server' + +import { createAdminClient } from "@/lib/supabase/admin" +import { createClient } from "@/lib/supabase/server" // For regular client if needed +import { revalidatePath } from "next/cache" +import { logAction } from "@/lib/logger" + +export async function createUser(data: any) { + const supabaseAdmin = await createAdminClient() + + if (!supabaseAdmin) { + return { error: "Kullanıcı oluşturmak için .env dosyasına SUPABASE_SERVICE_ROLE_KEY eklenmelidir." } + } + + // 1. Create Auth User + const { data: userData, error: userError } = await supabaseAdmin.auth.admin.createUser({ + email: data.email, + password: data.password, + email_confirm: true, + user_metadata: { + full_name: data.full_name + } + }) + + if (userError) { + return { error: userError.message } + } + + if (!userData.user) { + return { error: "Kullanıcı oluşturulamadı." } + } + + // 2. Update Profile Role (Trigger creates profile as 'staff' by default, we might need to update it if 'admin') + if (data.role === 'admin') { + const { error: profileError } = await supabaseAdmin + .from('profiles') + .update({ role: 'admin' }) + .eq('id', userData.user.id) + + if (profileError) { + // Log error but don't fail completely as user is created + console.error("Error updating role:", profileError) + } + } + + await logAction('create_user', 'user', userData.user.id, { + email: data.email, + role: data.role + }) + + revalidatePath('/dashboard/settings/users') +} diff --git a/src/app/dashboard/settings/users/new/page.tsx b/src/app/dashboard/settings/users/new/page.tsx new file mode 100644 index 0000000..3a76614 --- /dev/null +++ b/src/app/dashboard/settings/users/new/page.tsx @@ -0,0 +1,17 @@ +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" +import { UserForm } from "./user-form" + +export default function NewUserPage() { + return ( +
+ + + Yeni Kullanıcı Oluştur + + + + + +
+ ) +} diff --git a/src/app/dashboard/settings/users/new/user-form.tsx b/src/app/dashboard/settings/users/new/user-form.tsx new file mode 100644 index 0000000..86422ab --- /dev/null +++ b/src/app/dashboard/settings/users/new/user-form.tsx @@ -0,0 +1,130 @@ +'use client' + +import { zodResolver } from "@hookform/resolvers/zod" +import { useForm } from "react-hook-form" +import * as z from "zod" +import { Button } from "@/components/ui/button" +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form" +import { Input } from "@/components/ui/input" +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" +import { createUser } from "./actions" +import { useRouter } from "next/navigation" +import { useState } from "react" +import { toast } from "sonner" + +const formSchema = z.object({ + email: z.string().email("Geçerli bir e-posta adresi giriniz."), + password: z.string().min(6, "Şifre en az 6 karakter olmalıdır."), + full_name: z.string().min(2, "İsim en az 2 karakter olmalıdır."), + role: z.enum(["admin", "staff"]), +}) + +export function UserForm() { + const router = useRouter() + const [loading, setLoading] = useState(false) + + const form = useForm>({ + resolver: zodResolver(formSchema), + defaultValues: { + email: "", + password: "", + full_name: "", + role: "staff", + }, + }) + + async function onSubmit(values: z.infer) { + setLoading(true) + try { + const result = await createUser(values) + if (result?.error) { + toast.error(result.error) + } else { + toast.success("Kullanıcı başarıyla oluşturuldu") + router.push('/dashboard/settings/users') + router.refresh() + } + } catch (error) { + toast.error("Bir hata oluştu") + } finally { + setLoading(false) + } + } + + return ( +
+ + ( + + E-posta + + + + + + )} + /> + ( + + Şifre + + + + + + )} + /> + ( + + İsim Soyisim + + + + + + )} + /> + ( + + Rol + + + + )} + /> + + + + ) +} diff --git a/src/app/dashboard/settings/users/page.tsx b/src/app/dashboard/settings/users/page.tsx index 9815094..7d4eea7 100644 --- a/src/app/dashboard/settings/users/page.tsx +++ b/src/app/dashboard/settings/users/page.tsx @@ -34,11 +34,11 @@ export default async function UsersPage() {

Kullanıcı Yönetimi

Sisteme erişimi olan kullanıcıları yönetin.

- {/* + - */} +
@@ -78,7 +78,9 @@ export default async function UsersPage() { {new Date(profile.created_at).toLocaleDateString('tr-TR')} - + + + )) diff --git a/src/lib/supabase/admin.ts b/src/lib/supabase/admin.ts new file mode 100644 index 0000000..7a4b96d --- /dev/null +++ b/src/lib/supabase/admin.ts @@ -0,0 +1,35 @@ +'use server' + +import { createServerClient } from '@supabase/ssr' +import { cookies } from 'next/headers' + +export async function createAdminClient() { + const cookieStore = await cookies() + + // Check if Service Role Key exists + const serviceRoleKey = process.env.SUPABASE_SERVICE_ROLE_KEY + + if (!serviceRoleKey) { + return null + } + + return createServerClient( + process.env.NEXT_PUBLIC_SUPABASE_URL!, + serviceRoleKey, + { + cookies: { + getAll() { + return cookieStore.getAll() + }, + setAll(cookiesToSet) { + try { + cookiesToSet.forEach(({ name, value, options }) => + cookieStore.set(name, value, options) + ) + } catch { + } + }, + }, + } + ) +}