feat(i18n): add verification messages in multiple languages
This commit is contained in:
@@ -271,6 +271,11 @@
|
||||
"deleteFailed": "فشل حذف المستخدم",
|
||||
"roleUpdated": "تم تحديث دور المستخدم",
|
||||
"roleUpdateFailed": "فشل تحديث الدور",
|
||||
"verify": "توثيق",
|
||||
"unverify": "إلغاء التوثيق",
|
||||
"verified": "تم توثيق المستخدم",
|
||||
"unverified": "تم إلغاء توثيق المستخدم",
|
||||
"verifyFailed": "فشل تحديث التوثيق",
|
||||
"deleteConfirmTitle": "حذف المستخدم؟",
|
||||
"deleteConfirmDescription": "هذا الإجراء لا يمكن التراجع عنه. سيتم حذف جميع بيانات المستخدم نهائياً."
|
||||
},
|
||||
|
||||
@@ -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."
|
||||
},
|
||||
|
||||
@@ -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."
|
||||
},
|
||||
|
||||
@@ -265,6 +265,11 @@
|
||||
"deleteFailed": "ユーザーの削除に失敗しました",
|
||||
"roleUpdated": "ユーザーの役割を更新しました",
|
||||
"roleUpdateFailed": "役割の更新に失敗しました",
|
||||
"verify": "認証",
|
||||
"unverify": "認証解除",
|
||||
"verified": "ユーザーを認証しました",
|
||||
"unverified": "ユーザーの認証を解除しました",
|
||||
"verifyFailed": "認証の更新に失敗しました",
|
||||
"deleteConfirmTitle": "ユーザーを削除しますか?",
|
||||
"deleteConfirmDescription": "この操作は取り消せません。すべてのユーザーデータが完全に削除されます。"
|
||||
},
|
||||
|
||||
@@ -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."
|
||||
},
|
||||
|
||||
@@ -265,6 +265,11 @@
|
||||
"deleteFailed": "删除用户失败",
|
||||
"roleUpdated": "用户角色已更新",
|
||||
"roleUpdateFailed": "更新角色失败",
|
||||
"verify": "验证",
|
||||
"unverify": "取消验证",
|
||||
"verified": "用户已验证",
|
||||
"unverified": "用户验证已取消",
|
||||
"verifyFailed": "更新验证失败",
|
||||
"deleteConfirmTitle": "删除用户?",
|
||||
"deleteConfirmDescription": "此操作无法撤销。所有用户数据将被永久删除。"
|
||||
},
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -59,6 +59,7 @@ export default async function AdminPage() {
|
||||
name: true,
|
||||
avatar: true,
|
||||
role: true,
|
||||
verified: true,
|
||||
createdAt: true,
|
||||
_count: {
|
||||
select: {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user