feat(i18n): add verification messages in multiple languages

This commit is contained in:
Fatih Kadir Akın
2025-12-12 13:50:47 +03:00
parent 731bcf0f7d
commit 7c94ece179
11 changed files with 79 additions and 8 deletions
+5
View File
@@ -271,6 +271,11 @@
"deleteFailed": "فشل حذف المستخدم",
"roleUpdated": "تم تحديث دور المستخدم",
"roleUpdateFailed": "فشل تحديث الدور",
"verify": "توثيق",
"unverify": "إلغاء التوثيق",
"verified": "تم توثيق المستخدم",
"unverified": "تم إلغاء توثيق المستخدم",
"verifyFailed": "فشل تحديث التوثيق",
"deleteConfirmTitle": "حذف المستخدم؟",
"deleteConfirmDescription": "هذا الإجراء لا يمكن التراجع عنه. سيتم حذف جميع بيانات المستخدم نهائياً."
},
+5
View File
@@ -305,6 +305,11 @@
"deleteFailed": "Failed to delete user",
"roleUpdated": "User role updated",
"roleUpdateFailed": "Failed to update role",
"verify": "Verify",
"unverify": "Unverify",
"verified": "User verified",
"unverified": "User unverified",
"verifyFailed": "Failed to update verification",
"deleteConfirmTitle": "Delete User?",
"deleteConfirmDescription": "This action cannot be undone. All user data will be permanently deleted."
},
+5
View File
@@ -265,6 +265,11 @@
"deleteFailed": "Error al eliminar usuario",
"roleUpdated": "Rol de usuario actualizado",
"roleUpdateFailed": "Error al actualizar rol",
"verify": "Verificar",
"unverify": "Quitar verificación",
"verified": "Usuario verificado",
"unverified": "Usuario no verificado",
"verifyFailed": "Error al actualizar verificación",
"deleteConfirmTitle": "¿Eliminar Usuario?",
"deleteConfirmDescription": "Esta acción no se puede deshacer. Todos los datos del usuario serán eliminados permanentemente."
},
+5
View File
@@ -265,6 +265,11 @@
"deleteFailed": "ユーザーの削除に失敗しました",
"roleUpdated": "ユーザーの役割を更新しました",
"roleUpdateFailed": "役割の更新に失敗しました",
"verify": "認証",
"unverify": "認証解除",
"verified": "ユーザーを認証しました",
"unverified": "ユーザーの認証を解除しました",
"verifyFailed": "認証の更新に失敗しました",
"deleteConfirmTitle": "ユーザーを削除しますか?",
"deleteConfirmDescription": "この操作は取り消せません。すべてのユーザーデータが完全に削除されます。"
},
+5
View File
@@ -305,6 +305,11 @@
"deleteFailed": "Kullanıcı silinemedi",
"roleUpdated": "Kullanıcı rolü güncellendi",
"roleUpdateFailed": "Rol güncellenemedi",
"verify": "Doğrula",
"unverify": "Doğrulamayı Kaldır",
"verified": "Kullanıcı doğrulandı",
"unverified": "Kullanıcı doğrulaması kaldırıldı",
"verifyFailed": "Doğrulama güncellenemedi",
"deleteConfirmTitle": "Kullanıcı Silinsin mi?",
"deleteConfirmDescription": "Bu işlem geri alınamaz. Tüm kullanıcı verileri kalıcı olarak silinecektir."
},
+5
View File
@@ -265,6 +265,11 @@
"deleteFailed": "删除用户失败",
"roleUpdated": "用户角色已更新",
"roleUpdateFailed": "更新角色失败",
"verify": "验证",
"unverify": "取消验证",
"verified": "用户已验证",
"unverified": "用户验证已取消",
"verifyFailed": "更新验证失败",
"deleteConfirmTitle": "删除用户?",
"deleteConfirmDescription": "此操作无法撤销。所有用户数据将被永久删除。"
},
+1
View File
@@ -28,6 +28,7 @@ model User {
avatar String?
role UserRole @default(USER)
locale String @default("en")
verified Boolean @default(false)
emailVerified DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
+5 -1
View File
@@ -3,7 +3,7 @@ import { notFound } from "next/navigation";
import Link from "next/link";
import { getTranslations, getLocale } from "next-intl/server";
import { formatDistanceToNow } from "@/lib/date";
import { Calendar, ArrowBigUp, FileText, Settings, GitPullRequest, Clock, Check, X, Pin } from "lucide-react";
import { Calendar, ArrowBigUp, FileText, Settings, GitPullRequest, Clock, Check, X, Pin, BadgeCheck } from "lucide-react";
import { auth } from "@/lib/auth";
import { db } from "@/lib/db";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
@@ -75,6 +75,7 @@ export default async function UserProfilePage({ params, searchParams }: UserProf
email: true,
avatar: true,
role: true,
verified: true,
createdAt: true,
_count: {
select: {
@@ -283,6 +284,9 @@ export default async function UserProfilePage({ params, searchParams }: UserProf
<div>
<div className="flex items-center gap-2 mb-1">
<h1 className="text-2xl font-bold">{user.name || user.username}</h1>
{user.verified && (
<BadgeCheck className="h-5 w-5 text-blue-500" />
)}
{user.role === "ADMIN" && (
<Badge variant="default" className="text-xs">Admin</Badge>
)}
+1
View File
@@ -59,6 +59,7 @@ export default async function AdminPage() {
name: true,
avatar: true,
role: true,
verified: true,
createdAt: true,
_count: {
select: {
+13 -3
View File
@@ -2,7 +2,7 @@ import { NextRequest, NextResponse } from "next/server";
import { auth } from "@/lib/auth";
import { db } from "@/lib/db";
// Update user (role change)
// Update user (role change or verification)
export async function PATCH(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
@@ -15,15 +15,25 @@ export async function PATCH(
const { id } = await params;
const body = await request.json();
const { role } = body;
const { role, verified } = body;
// Build update data
const updateData: { role?: "ADMIN" | "USER"; verified?: boolean } = {};
if (role !== undefined) {
if (!["ADMIN", "USER"].includes(role)) {
return NextResponse.json({ error: "Invalid role" }, { status: 400 });
}
updateData.role = role;
}
if (verified !== undefined) {
updateData.verified = verified;
}
const user = await db.user.update({
where: { id },
data: { role },
data: updateData,
});
return NextResponse.json(user);
+27 -2
View File
@@ -4,7 +4,7 @@ import { useState } from "react";
import { useRouter } from "next/navigation";
import { useTranslations, useLocale } from "next-intl";
import { formatDistanceToNow } from "@/lib/date";
import { MoreHorizontal, Shield, User, Trash2 } from "lucide-react";
import { MoreHorizontal, Shield, User, Trash2, BadgeCheck } from "lucide-react";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
@@ -42,6 +42,7 @@ interface User {
name: string | null;
avatar: string | null;
role: "ADMIN" | "USER";
verified: boolean;
createdAt: Date;
_count: {
prompts: number;
@@ -76,6 +77,23 @@ export function UsersTable({ users }: UsersTableProps) {
}
};
const handleVerifyToggle = async (userId: string, verified: boolean) => {
try {
const res = await fetch(`/api/admin/users/${userId}`, {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ verified }),
});
if (!res.ok) throw new Error("Failed to update verification");
toast.success(verified ? t("verified") : t("unverified"));
router.refresh();
} catch {
toast.error(t("verifyFailed"));
}
};
const handleDelete = async () => {
if (!deleteUserId) return;
@@ -130,7 +148,10 @@ export function UsersTable({ users }: UsersTableProps) {
</AvatarFallback>
</Avatar>
<div>
<div className="font-medium">{user.name || user.username}</div>
<div className="font-medium flex items-center gap-1">
{user.name || user.username}
{user.verified && <BadgeCheck className="h-4 w-4 text-blue-500" />}
</div>
<div className="text-xs text-muted-foreground">@{user.username}</div>
</div>
</div>
@@ -165,6 +186,10 @@ export function UsersTable({ users }: UsersTableProps) {
{t("removeAdmin")}
</DropdownMenuItem>
)}
<DropdownMenuItem onClick={() => handleVerifyToggle(user.id, !user.verified)}>
<BadgeCheck className="h-4 w-4 mr-2" />
{user.verified ? t("unverify") : t("verify")}
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem
className="text-destructive"