From 7c94ece179b7f3059ded7160c4e04f6aec4b8516 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fatih=20Kadir=20Ak=C4=B1n?= Date: Fri, 12 Dec 2025 13:50:47 +0300 Subject: [PATCH] feat(i18n): add verification messages in multiple languages --- messages/ar.json | 5 +++++ messages/en.json | 5 +++++ messages/es.json | 5 +++++ messages/ja.json | 5 +++++ messages/tr.json | 5 +++++ messages/zh.json | 5 +++++ prisma/schema.prisma | 1 + src/app/[username]/page.tsx | 6 +++++- src/app/admin/page.tsx | 1 + src/app/api/admin/users/[id]/route.ts | 20 +++++++++++++----- src/components/admin/users-table.tsx | 29 +++++++++++++++++++++++++-- 11 files changed, 79 insertions(+), 8 deletions(-) diff --git a/messages/ar.json b/messages/ar.json index 689001b5..958facd9 100644 --- a/messages/ar.json +++ b/messages/ar.json @@ -271,6 +271,11 @@ "deleteFailed": "فشل حذف المستخدم", "roleUpdated": "تم تحديث دور المستخدم", "roleUpdateFailed": "فشل تحديث الدور", + "verify": "توثيق", + "unverify": "إلغاء التوثيق", + "verified": "تم توثيق المستخدم", + "unverified": "تم إلغاء توثيق المستخدم", + "verifyFailed": "فشل تحديث التوثيق", "deleteConfirmTitle": "حذف المستخدم؟", "deleteConfirmDescription": "هذا الإجراء لا يمكن التراجع عنه. سيتم حذف جميع بيانات المستخدم نهائياً." }, diff --git a/messages/en.json b/messages/en.json index 9c5e2d18..21d16ede 100644 --- a/messages/en.json +++ b/messages/en.json @@ -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." }, diff --git a/messages/es.json b/messages/es.json index 846ed43e..4a5340c9 100644 --- a/messages/es.json +++ b/messages/es.json @@ -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." }, diff --git a/messages/ja.json b/messages/ja.json index e80eb60d..8f183d30 100644 --- a/messages/ja.json +++ b/messages/ja.json @@ -265,6 +265,11 @@ "deleteFailed": "ユーザーの削除に失敗しました", "roleUpdated": "ユーザーの役割を更新しました", "roleUpdateFailed": "役割の更新に失敗しました", + "verify": "認証", + "unverify": "認証解除", + "verified": "ユーザーを認証しました", + "unverified": "ユーザーの認証を解除しました", + "verifyFailed": "認証の更新に失敗しました", "deleteConfirmTitle": "ユーザーを削除しますか?", "deleteConfirmDescription": "この操作は取り消せません。すべてのユーザーデータが完全に削除されます。" }, diff --git a/messages/tr.json b/messages/tr.json index ab22f6ac..039098b4 100644 --- a/messages/tr.json +++ b/messages/tr.json @@ -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." }, diff --git a/messages/zh.json b/messages/zh.json index 6da7c30a..f226aad6 100644 --- a/messages/zh.json +++ b/messages/zh.json @@ -265,6 +265,11 @@ "deleteFailed": "删除用户失败", "roleUpdated": "用户角色已更新", "roleUpdateFailed": "更新角色失败", + "verify": "验证", + "unverify": "取消验证", + "verified": "用户已验证", + "unverified": "用户验证已取消", + "verifyFailed": "更新验证失败", "deleteConfirmTitle": "删除用户?", "deleteConfirmDescription": "此操作无法撤销。所有用户数据将被永久删除。" }, diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 02a41c9a..11df2ecb 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -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 diff --git a/src/app/[username]/page.tsx b/src/app/[username]/page.tsx index c4d55df2..b067f034 100644 --- a/src/app/[username]/page.tsx +++ b/src/app/[username]/page.tsx @@ -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

{user.name || user.username}

+ {user.verified && ( + + )} {user.role === "ADMIN" && ( Admin )} diff --git a/src/app/admin/page.tsx b/src/app/admin/page.tsx index 8de81125..c2b8aa99 100644 --- a/src/app/admin/page.tsx +++ b/src/app/admin/page.tsx @@ -59,6 +59,7 @@ export default async function AdminPage() { name: true, avatar: true, role: true, + verified: true, createdAt: true, _count: { select: { diff --git a/src/app/api/admin/users/[id]/route.ts b/src/app/api/admin/users/[id]/route.ts index 41d690a0..15b39dd4 100644 --- a/src/app/api/admin/users/[id]/route.ts +++ b/src/app/api/admin/users/[id]/route.ts @@ -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; - if (!["ADMIN", "USER"].includes(role)) { - return NextResponse.json({ error: "Invalid role" }, { status: 400 }); + // 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); diff --git a/src/components/admin/users-table.tsx b/src/components/admin/users-table.tsx index 2635fd3f..58311aaf 100644 --- a/src/components/admin/users-table.tsx +++ b/src/components/admin/users-table.tsx @@ -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) {
-
{user.name || user.username}
+
+ {user.name || user.username} + {user.verified && } +
@{user.username}
@@ -165,6 +186,10 @@ export function UsersTable({ users }: UsersTableProps) { {t("removeAdmin")} )} + handleVerifyToggle(user.id, !user.verified)}> + + {user.verified ? t("unverify") : t("verify")} +