feat(i18n): add verification messages in multiple languages
This commit is contained in:
@@ -271,6 +271,11 @@
|
|||||||
"deleteFailed": "فشل حذف المستخدم",
|
"deleteFailed": "فشل حذف المستخدم",
|
||||||
"roleUpdated": "تم تحديث دور المستخدم",
|
"roleUpdated": "تم تحديث دور المستخدم",
|
||||||
"roleUpdateFailed": "فشل تحديث الدور",
|
"roleUpdateFailed": "فشل تحديث الدور",
|
||||||
|
"verify": "توثيق",
|
||||||
|
"unverify": "إلغاء التوثيق",
|
||||||
|
"verified": "تم توثيق المستخدم",
|
||||||
|
"unverified": "تم إلغاء توثيق المستخدم",
|
||||||
|
"verifyFailed": "فشل تحديث التوثيق",
|
||||||
"deleteConfirmTitle": "حذف المستخدم؟",
|
"deleteConfirmTitle": "حذف المستخدم؟",
|
||||||
"deleteConfirmDescription": "هذا الإجراء لا يمكن التراجع عنه. سيتم حذف جميع بيانات المستخدم نهائياً."
|
"deleteConfirmDescription": "هذا الإجراء لا يمكن التراجع عنه. سيتم حذف جميع بيانات المستخدم نهائياً."
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -305,6 +305,11 @@
|
|||||||
"deleteFailed": "Failed to delete user",
|
"deleteFailed": "Failed to delete user",
|
||||||
"roleUpdated": "User role updated",
|
"roleUpdated": "User role updated",
|
||||||
"roleUpdateFailed": "Failed to update role",
|
"roleUpdateFailed": "Failed to update role",
|
||||||
|
"verify": "Verify",
|
||||||
|
"unverify": "Unverify",
|
||||||
|
"verified": "User verified",
|
||||||
|
"unverified": "User unverified",
|
||||||
|
"verifyFailed": "Failed to update verification",
|
||||||
"deleteConfirmTitle": "Delete User?",
|
"deleteConfirmTitle": "Delete User?",
|
||||||
"deleteConfirmDescription": "This action cannot be undone. All user data will be permanently deleted."
|
"deleteConfirmDescription": "This action cannot be undone. All user data will be permanently deleted."
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -265,6 +265,11 @@
|
|||||||
"deleteFailed": "Error al eliminar usuario",
|
"deleteFailed": "Error al eliminar usuario",
|
||||||
"roleUpdated": "Rol de usuario actualizado",
|
"roleUpdated": "Rol de usuario actualizado",
|
||||||
"roleUpdateFailed": "Error al actualizar rol",
|
"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?",
|
"deleteConfirmTitle": "¿Eliminar Usuario?",
|
||||||
"deleteConfirmDescription": "Esta acción no se puede deshacer. Todos los datos del usuario serán eliminados permanentemente."
|
"deleteConfirmDescription": "Esta acción no se puede deshacer. Todos los datos del usuario serán eliminados permanentemente."
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -265,6 +265,11 @@
|
|||||||
"deleteFailed": "ユーザーの削除に失敗しました",
|
"deleteFailed": "ユーザーの削除に失敗しました",
|
||||||
"roleUpdated": "ユーザーの役割を更新しました",
|
"roleUpdated": "ユーザーの役割を更新しました",
|
||||||
"roleUpdateFailed": "役割の更新に失敗しました",
|
"roleUpdateFailed": "役割の更新に失敗しました",
|
||||||
|
"verify": "認証",
|
||||||
|
"unverify": "認証解除",
|
||||||
|
"verified": "ユーザーを認証しました",
|
||||||
|
"unverified": "ユーザーの認証を解除しました",
|
||||||
|
"verifyFailed": "認証の更新に失敗しました",
|
||||||
"deleteConfirmTitle": "ユーザーを削除しますか?",
|
"deleteConfirmTitle": "ユーザーを削除しますか?",
|
||||||
"deleteConfirmDescription": "この操作は取り消せません。すべてのユーザーデータが完全に削除されます。"
|
"deleteConfirmDescription": "この操作は取り消せません。すべてのユーザーデータが完全に削除されます。"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -305,6 +305,11 @@
|
|||||||
"deleteFailed": "Kullanıcı silinemedi",
|
"deleteFailed": "Kullanıcı silinemedi",
|
||||||
"roleUpdated": "Kullanıcı rolü güncellendi",
|
"roleUpdated": "Kullanıcı rolü güncellendi",
|
||||||
"roleUpdateFailed": "Rol güncellenemedi",
|
"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?",
|
"deleteConfirmTitle": "Kullanıcı Silinsin mi?",
|
||||||
"deleteConfirmDescription": "Bu işlem geri alınamaz. Tüm kullanıcı verileri kalıcı olarak silinecektir."
|
"deleteConfirmDescription": "Bu işlem geri alınamaz. Tüm kullanıcı verileri kalıcı olarak silinecektir."
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -265,6 +265,11 @@
|
|||||||
"deleteFailed": "删除用户失败",
|
"deleteFailed": "删除用户失败",
|
||||||
"roleUpdated": "用户角色已更新",
|
"roleUpdated": "用户角色已更新",
|
||||||
"roleUpdateFailed": "更新角色失败",
|
"roleUpdateFailed": "更新角色失败",
|
||||||
|
"verify": "验证",
|
||||||
|
"unverify": "取消验证",
|
||||||
|
"verified": "用户已验证",
|
||||||
|
"unverified": "用户验证已取消",
|
||||||
|
"verifyFailed": "更新验证失败",
|
||||||
"deleteConfirmTitle": "删除用户?",
|
"deleteConfirmTitle": "删除用户?",
|
||||||
"deleteConfirmDescription": "此操作无法撤销。所有用户数据将被永久删除。"
|
"deleteConfirmDescription": "此操作无法撤销。所有用户数据将被永久删除。"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ model User {
|
|||||||
avatar String?
|
avatar String?
|
||||||
role UserRole @default(USER)
|
role UserRole @default(USER)
|
||||||
locale String @default("en")
|
locale String @default("en")
|
||||||
|
verified Boolean @default(false)
|
||||||
emailVerified DateTime?
|
emailVerified DateTime?
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { notFound } from "next/navigation";
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { getTranslations, getLocale } from "next-intl/server";
|
import { getTranslations, getLocale } from "next-intl/server";
|
||||||
import { formatDistanceToNow } from "@/lib/date";
|
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 { auth } from "@/lib/auth";
|
||||||
import { db } from "@/lib/db";
|
import { db } from "@/lib/db";
|
||||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||||
@@ -75,6 +75,7 @@ export default async function UserProfilePage({ params, searchParams }: UserProf
|
|||||||
email: true,
|
email: true,
|
||||||
avatar: true,
|
avatar: true,
|
||||||
role: true,
|
role: true,
|
||||||
|
verified: true,
|
||||||
createdAt: true,
|
createdAt: true,
|
||||||
_count: {
|
_count: {
|
||||||
select: {
|
select: {
|
||||||
@@ -283,6 +284,9 @@ export default async function UserProfilePage({ params, searchParams }: UserProf
|
|||||||
<div>
|
<div>
|
||||||
<div className="flex items-center gap-2 mb-1">
|
<div className="flex items-center gap-2 mb-1">
|
||||||
<h1 className="text-2xl font-bold">{user.name || user.username}</h1>
|
<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" && (
|
{user.role === "ADMIN" && (
|
||||||
<Badge variant="default" className="text-xs">Admin</Badge>
|
<Badge variant="default" className="text-xs">Admin</Badge>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ export default async function AdminPage() {
|
|||||||
name: true,
|
name: true,
|
||||||
avatar: true,
|
avatar: true,
|
||||||
role: true,
|
role: true,
|
||||||
|
verified: true,
|
||||||
createdAt: true,
|
createdAt: true,
|
||||||
_count: {
|
_count: {
|
||||||
select: {
|
select: {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { NextRequest, NextResponse } from "next/server";
|
|||||||
import { auth } from "@/lib/auth";
|
import { auth } from "@/lib/auth";
|
||||||
import { db } from "@/lib/db";
|
import { db } from "@/lib/db";
|
||||||
|
|
||||||
// Update user (role change)
|
// Update user (role change or verification)
|
||||||
export async function PATCH(
|
export async function PATCH(
|
||||||
request: NextRequest,
|
request: NextRequest,
|
||||||
{ params }: { params: Promise<{ id: string }> }
|
{ params }: { params: Promise<{ id: string }> }
|
||||||
@@ -15,15 +15,25 @@ export async function PATCH(
|
|||||||
|
|
||||||
const { id } = await params;
|
const { id } = await params;
|
||||||
const body = await request.json();
|
const body = await request.json();
|
||||||
const { role } = body;
|
const { role, verified } = body;
|
||||||
|
|
||||||
if (!["ADMIN", "USER"].includes(role)) {
|
// Build update data
|
||||||
return NextResponse.json({ error: "Invalid role" }, { status: 400 });
|
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({
|
const user = await db.user.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: { role },
|
data: updateData,
|
||||||
});
|
});
|
||||||
|
|
||||||
return NextResponse.json(user);
|
return NextResponse.json(user);
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { useState } from "react";
|
|||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { useTranslations, useLocale } from "next-intl";
|
import { useTranslations, useLocale } from "next-intl";
|
||||||
import { formatDistanceToNow } from "@/lib/date";
|
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 { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
@@ -42,6 +42,7 @@ interface User {
|
|||||||
name: string | null;
|
name: string | null;
|
||||||
avatar: string | null;
|
avatar: string | null;
|
||||||
role: "ADMIN" | "USER";
|
role: "ADMIN" | "USER";
|
||||||
|
verified: boolean;
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
_count: {
|
_count: {
|
||||||
prompts: number;
|
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 () => {
|
const handleDelete = async () => {
|
||||||
if (!deleteUserId) return;
|
if (!deleteUserId) return;
|
||||||
|
|
||||||
@@ -130,7 +148,10 @@ export function UsersTable({ users }: UsersTableProps) {
|
|||||||
</AvatarFallback>
|
</AvatarFallback>
|
||||||
</Avatar>
|
</Avatar>
|
||||||
<div>
|
<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 className="text-xs text-muted-foreground">@{user.username}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -165,6 +186,10 @@ export function UsersTable({ users }: UsersTableProps) {
|
|||||||
{t("removeAdmin")}
|
{t("removeAdmin")}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
)}
|
)}
|
||||||
|
<DropdownMenuItem onClick={() => handleVerifyToggle(user.id, !user.verified)}>
|
||||||
|
<BadgeCheck className="h-4 w-4 mr-2" />
|
||||||
|
{user.verified ? t("unverify") : t("verify")}
|
||||||
|
</DropdownMenuItem>
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
className="text-destructive"
|
className="text-destructive"
|
||||||
|
|||||||
Reference in New Issue
Block a user