diff --git a/src/app/(app)/admin/kullanicilar/page.tsx b/src/app/(app)/admin/kullanicilar/page.tsx new file mode 100644 index 0000000..6875a05 --- /dev/null +++ b/src/app/(app)/admin/kullanicilar/page.tsx @@ -0,0 +1,18 @@ +import type { Metadata } from "next"; +import { PageHeader } from "@/components/page-header"; +import { UsersTable } from "@/features/admin-users/components/users-table"; +import { MOCK_ADMIN_USERS } from "@/features/admin-users/mock"; +import { MOCK_SESSION_USER } from "@/features/auth/mock"; +import { tr } from "@/i18n/tr"; + +export const metadata: Metadata = { title: tr.adminUsers.title }; + +export default function AdminUsersPage() { + // FAZ 3: const users = await listUsers() (Better Auth admin API) + return ( + <> + + + + ); +} diff --git a/src/app/(app)/admin/layout.tsx b/src/app/(app)/admin/layout.tsx new file mode 100644 index 0000000..7cc8092 --- /dev/null +++ b/src/app/(app)/admin/layout.tsx @@ -0,0 +1,11 @@ +import { notFound } from "next/navigation"; +import { MOCK_SESSION_USER } from "@/features/auth/mock"; + +/** + * Yalnızca sistem admini. Admin olmayana 404 (sayfanın varlığını bile göstermez). + * FAZ 3: `await requireRole("admin")` (lib/dal.ts) — sunucu tarafı gerçek kontrol. + */ +export default function AdminLayout({ children }: { children: React.ReactNode }) { + if (MOCK_SESSION_USER.role !== "admin") notFound(); + return children; +} diff --git a/src/features/admin-users/components/user-action-dialogs.tsx b/src/features/admin-users/components/user-action-dialogs.tsx new file mode 100644 index 0000000..0e35b64 --- /dev/null +++ b/src/features/admin-users/components/user-action-dialogs.tsx @@ -0,0 +1,219 @@ +"use client"; + +import { zodResolver } from "@hookform/resolvers/zod"; +import { Loader2 } from "lucide-react"; +import { useEffect, useState } from "react"; +import { Controller, useForm } from "react-hook-form"; +import { PasswordInput } from "@/components/password-input"; +import { ResponsiveDialog } from "@/components/responsive-dialog"; +import { + AlertDialog, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from "@/components/ui/alert-dialog"; +import { Button } from "@/components/ui/button"; +import { Field, FieldDescription, FieldError, FieldGroup, FieldLabel } from "@/components/ui/field"; +import { Textarea } from "@/components/ui/textarea"; +import { tr } from "@/i18n/tr"; +import type { ActionResult } from "@/lib/action-result"; +import { + banUserSchema, + setPasswordSchema, + type BanUserInput, + type SetPasswordInput, +} from "@/shared/schemas/user"; +import type { AdminUser } from "../types"; + +const t = tr.adminUsers; + +type DialogProps = { + user: AdminUser | undefined; + open: boolean; + onOpenChange: (open: boolean) => void; + onConfirm: (user: AdminUser, values: T) => Promise; +}; + +export function SetPasswordDialog({ + user, + open, + onOpenChange, + onConfirm, +}: DialogProps<{ password: string }>) { + const form = useForm({ + resolver: zodResolver(setPasswordSchema), + defaultValues: { password: "" }, + }); + useEffect(() => { + if (open) form.reset({ password: "" }); + }, [open, form]); + + async function onSubmit(values: SetPasswordInput) { + if (!user) return; + const result = await onConfirm(user, values); + if (result.ok) onOpenChange(false); + else form.setError("root", { message: result.error }); + } + + return ( + + + + + } + > +
+ + ( + + {t.password.newPassword} + + {t.form.passwordHint} + {fieldState.invalid ? : null} + + )} + /> + {form.formState.errors.root ? ( + {form.formState.errors.root.message} + ) : null} + +
+
+ ); +} + +export function BanUserDialog({ + user, + open, + onOpenChange, + onConfirm, +}: DialogProps<{ reason?: string }>) { + const form = useForm({ + resolver: zodResolver(banUserSchema), + defaultValues: { reason: "" }, + }); + useEffect(() => { + if (open) form.reset({ reason: "" }); + }, [open, form]); + + async function onSubmit(values: { reason?: string }) { + if (!user) return; + const result = await onConfirm(user, values); + if (result.ok) onOpenChange(false); + else form.setError("root", { message: result.error }); + } + + return ( + + + + + } + > +
+ + ( + + {t.ban.reason} +