add admin
This commit is contained in:
parent
2e44c5865d
commit
b052bbedf5
@ -218,6 +218,7 @@
|
|||||||
},
|
},
|
||||||
"admin": {
|
"admin": {
|
||||||
"dashboard": "Admin Dashboard",
|
"dashboard": "Admin Dashboard",
|
||||||
|
"dashboardDesc": "Manage users, prompts, and system settings",
|
||||||
"totalUsers": "Total Users",
|
"totalUsers": "Total Users",
|
||||||
"totalPrompts": "Total Prompts",
|
"totalPrompts": "Total Prompts",
|
||||||
"sharedPrompts": "Shared Prompts",
|
"sharedPrompts": "Shared Prompts",
|
||||||
@ -238,7 +239,10 @@
|
|||||||
"approve": "Approve",
|
"approve": "Approve",
|
||||||
"reject": "Reject",
|
"reject": "Reject",
|
||||||
"allPrompts": "All Prompts",
|
"allPrompts": "All Prompts",
|
||||||
"allPromptsDesc": "Review all shared prompts"
|
"allPromptsDesc": "Review all shared prompts",
|
||||||
|
"loadingAdmin": "Loading admin panel...",
|
||||||
|
"loadingDashboard": "Loading dashboard statistics...",
|
||||||
|
"loadingPrompts": "Loading prompts for review..."
|
||||||
},
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
"generic": "Something went wrong. Please try again.",
|
"generic": "Something went wrong. Please try again.",
|
||||||
|
@ -218,6 +218,7 @@
|
|||||||
},
|
},
|
||||||
"admin": {
|
"admin": {
|
||||||
"dashboard": "管理员后台",
|
"dashboard": "管理员后台",
|
||||||
|
"dashboardDesc": "管理用户、提示词和系统设置",
|
||||||
"totalUsers": "用户总数",
|
"totalUsers": "用户总数",
|
||||||
"totalPrompts": "提示词总数",
|
"totalPrompts": "提示词总数",
|
||||||
"sharedPrompts": "用户共享提示词",
|
"sharedPrompts": "用户共享提示词",
|
||||||
@ -238,7 +239,10 @@
|
|||||||
"approve": "通过",
|
"approve": "通过",
|
||||||
"reject": "拒绝",
|
"reject": "拒绝",
|
||||||
"allPrompts": "所有提示词",
|
"allPrompts": "所有提示词",
|
||||||
"allPromptsDesc": "审核所有共享提示词"
|
"allPromptsDesc": "审核所有共享提示词",
|
||||||
|
"loadingAdmin": "加载管理员后台中...",
|
||||||
|
"loadingDashboard": "加载统计数据中...",
|
||||||
|
"loadingPrompts": "加载审核提示词中..."
|
||||||
},
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
"generic": "出现错误,请重试。",
|
"generic": "出现错误,请重试。",
|
||||||
|
@ -4,6 +4,7 @@ import { useUser } from '@/hooks/useUser'
|
|||||||
import { useRouter } from 'next/navigation'
|
import { useRouter } from 'next/navigation'
|
||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
import { useTranslations } from 'next-intl'
|
import { useTranslations } from 'next-intl'
|
||||||
|
import { Header } from '@/components/layout/Header'
|
||||||
|
|
||||||
export default function AdminLayout({
|
export default function AdminLayout({
|
||||||
children,
|
children,
|
||||||
@ -22,9 +23,15 @@ export default function AdminLayout({
|
|||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-background">
|
<div className="min-h-screen">
|
||||||
<div className="flex items-center justify-center h-screen">
|
<Header />
|
||||||
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary"></div>
|
<div className="max-w-7xl mx-auto px-4 py-8">
|
||||||
|
<div className="flex items-center justify-center min-h-96">
|
||||||
|
<div className="flex flex-col items-center gap-3">
|
||||||
|
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary"></div>
|
||||||
|
<p className="text-sm text-muted-foreground">{t('loadingAdmin')}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@ -35,17 +42,9 @@ export default function AdminLayout({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-background">
|
<div className="min-h-screen">
|
||||||
<div className="border-b border-border bg-card">
|
<Header />
|
||||||
<div className="container mx-auto px-4 py-4">
|
{children}
|
||||||
<h1 className="text-2xl font-bold text-foreground">
|
|
||||||
{t('dashboard')}
|
|
||||||
</h1>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="container mx-auto px-4 py-8">
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
@ -38,15 +38,22 @@ export default function AdminDashboard() {
|
|||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
<div className="max-w-7xl mx-auto px-4 py-8">
|
||||||
{[...Array(4)].map((_, i) => (
|
{/* Header */}
|
||||||
<Card key={i} className="p-6">
|
<div className="mb-8">
|
||||||
<div className="animate-pulse">
|
<h1 className="text-2xl sm:text-3xl font-bold text-foreground mb-2">{t('dashboard')}</h1>
|
||||||
<div className="h-4 bg-muted rounded w-3/4 mb-2"></div>
|
<p className="text-muted-foreground">
|
||||||
<div className="h-8 bg-muted rounded w-1/2"></div>
|
{t('dashboardDesc') || 'Manage users, prompts, and system settings'}
|
||||||
</div>
|
</p>
|
||||||
</Card>
|
</div>
|
||||||
))}
|
|
||||||
|
{/* Loading State */}
|
||||||
|
<div className="flex items-center justify-center min-h-96">
|
||||||
|
<div className="flex flex-col items-center gap-3">
|
||||||
|
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary"></div>
|
||||||
|
<p className="text-sm text-muted-foreground">{t('loadingDashboard')}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -79,78 +86,112 @@ export default function AdminDashboard() {
|
|||||||
]
|
]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-8">
|
<div className="max-w-7xl mx-auto px-4 py-8">
|
||||||
{/* Stats Grid */}
|
{/* Header */}
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
<div className="mb-8">
|
||||||
{statCards.map((stat, index) => {
|
<h1 className="text-2xl sm:text-3xl font-bold text-foreground mb-2">{t('dashboard')}</h1>
|
||||||
const Icon = stat.icon
|
<p className="text-muted-foreground">
|
||||||
return (
|
{t('dashboardDesc') || 'Manage users, prompts, and system settings'}
|
||||||
<Card key={index} className="p-6 hover:shadow-md transition-shadow">
|
</p>
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div>
|
|
||||||
<p className="text-sm font-medium text-muted-foreground">
|
|
||||||
{stat.title}
|
|
||||||
</p>
|
|
||||||
<p className="text-3xl font-bold text-foreground">
|
|
||||||
{stat.value.toLocaleString()}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<Icon className={`h-8 w-8 ${stat.color}`} />
|
|
||||||
</div>
|
|
||||||
</Card>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Quick Actions */}
|
<div className="space-y-6 lg:space-y-8">
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
{/* Stats Grid */}
|
||||||
<Card className="p-6">
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 lg:gap-6">
|
||||||
<h3 className="text-lg font-semibold text-foreground mb-4">
|
{statCards.map((stat, index) => {
|
||||||
{t('quickActions')}
|
const Icon = stat.icon
|
||||||
</h3>
|
return (
|
||||||
<div className="space-y-3">
|
<Card key={index} className="p-4 lg:p-6 hover:shadow-md transition-all duration-200 border-border hover:border-primary/20">
|
||||||
<Link
|
<div className="flex items-center justify-between">
|
||||||
href="/admin/review"
|
<div className="min-w-0 flex-1">
|
||||||
className="block p-3 rounded-lg border border-border hover:bg-accent hover:text-accent-foreground transition-colors"
|
<p className="text-xs sm:text-sm font-medium text-muted-foreground mb-1 truncate">
|
||||||
>
|
{stat.title}
|
||||||
<div className="flex items-center gap-3">
|
</p>
|
||||||
<CheckCircle className="h-5 w-5 text-primary" />
|
<p className="text-xl sm:text-2xl lg:text-3xl font-bold text-foreground">
|
||||||
<div>
|
{stat.value.toLocaleString()}
|
||||||
<div className="font-medium">
|
</p>
|
||||||
{t('allPrompts')}
|
|
||||||
</div>
|
</div>
|
||||||
<div className="text-sm text-muted-foreground">
|
<div className="flex-shrink-0 ml-3">
|
||||||
{t('allPromptsDesc')}
|
<div className="p-2 lg:p-3 rounded-full bg-muted/50">
|
||||||
|
<Icon className={`h-5 w-5 lg:h-6 lg:w-6 ${stat.color}`} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Card>
|
||||||
</Link>
|
)
|
||||||
</div>
|
})}
|
||||||
</Card>
|
</div>
|
||||||
|
|
||||||
<Card className="p-6">
|
{/* Quick Actions and System Status */}
|
||||||
<h3 className="text-lg font-semibold text-foreground mb-4">
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||||
{t('systemStatus')}
|
{/* Quick Actions */}
|
||||||
</h3>
|
<Card className="p-4 lg:p-6">
|
||||||
<div className="space-y-3">
|
<div className="flex items-center gap-3 mb-4 lg:mb-6">
|
||||||
<div className="flex items-center justify-between">
|
<div className="p-2 rounded-lg bg-primary/10">
|
||||||
<span className="text-sm text-muted-foreground">
|
<CheckCircle className="h-5 w-5 text-primary" />
|
||||||
{t('databaseStatus')}
|
</div>
|
||||||
</span>
|
<h3 className="text-lg font-semibold text-foreground">
|
||||||
<span className="text-sm font-medium text-green-600">
|
{t('quickActions')}
|
||||||
{t('healthy')}
|
</h3>
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-between">
|
<div className="space-y-3">
|
||||||
<span className="text-sm text-muted-foreground">
|
<Link
|
||||||
{t('authStatus')}
|
href="/admin/review"
|
||||||
</span>
|
className="group block p-3 lg:p-4 rounded-lg border border-border hover:border-primary/30 hover:bg-accent/50 transition-all duration-200"
|
||||||
<span className="text-sm font-medium text-green-600">
|
>
|
||||||
{t('healthy')}
|
<div className="flex items-center gap-3">
|
||||||
</span>
|
<div className="p-2 rounded-md bg-primary/5 group-hover:bg-primary/10 transition-colors">
|
||||||
|
<FileText className="h-4 w-4 text-primary" />
|
||||||
|
</div>
|
||||||
|
<div className="min-w-0 flex-1">
|
||||||
|
<div className="font-medium text-foreground group-hover:text-primary transition-colors">
|
||||||
|
{t('allPrompts')}
|
||||||
|
</div>
|
||||||
|
<div className="text-sm text-muted-foreground mt-0.5">
|
||||||
|
{t('allPromptsDesc')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Card>
|
||||||
</Card>
|
|
||||||
|
{/* System Status */}
|
||||||
|
<Card className="p-4 lg:p-6">
|
||||||
|
<div className="flex items-center gap-3 mb-4 lg:mb-6">
|
||||||
|
<div className="p-2 rounded-lg bg-green-100 dark:bg-green-900/30">
|
||||||
|
<CheckCircle className="h-5 w-5 text-green-600" />
|
||||||
|
</div>
|
||||||
|
<h3 className="text-lg font-semibold text-foreground">
|
||||||
|
{t('systemStatus')}
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-3">
|
||||||
|
<div className="flex items-center justify-between p-3 rounded-lg bg-muted/30">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<div className="w-2 h-2 rounded-full bg-green-500"></div>
|
||||||
|
<span className="text-sm font-medium text-foreground">
|
||||||
|
{t('databaseStatus')}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<span className="text-sm font-medium text-green-600">
|
||||||
|
{t('healthy')}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-between p-3 rounded-lg bg-muted/30">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<div className="w-2 h-2 rounded-full bg-green-500"></div>
|
||||||
|
<span className="text-sm font-medium text-foreground">
|
||||||
|
{t('authStatus')}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<span className="text-sm font-medium text-green-600">
|
||||||
|
{t('healthy')}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
@ -73,104 +73,123 @@ export default function AdminReviewPage() {
|
|||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="max-w-7xl mx-auto px-4 py-8">
|
||||||
<div className="flex items-center justify-between">
|
{/* Header */}
|
||||||
<h1 className="text-2xl font-bold text-foreground">
|
<div className="mb-8">
|
||||||
{t('reviewPrompts') || 'Review Prompts'}
|
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 mb-4">
|
||||||
</h1>
|
<div>
|
||||||
|
<h1 className="text-2xl sm:text-3xl font-bold text-foreground mb-2">
|
||||||
|
{t('allPrompts')}
|
||||||
|
</h1>
|
||||||
|
<p className="text-muted-foreground">
|
||||||
|
{t('reviewPromptsDesc')}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-4">
|
|
||||||
{[...Array(3)].map((_, i) => (
|
{/* Loading State */}
|
||||||
<Card key={i} className="p-6">
|
<div className="flex items-center justify-center min-h-96">
|
||||||
<div className="animate-pulse space-y-4">
|
<div className="flex flex-col items-center gap-3">
|
||||||
<div className="h-4 bg-muted rounded w-3/4"></div>
|
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary"></div>
|
||||||
<div className="h-20 bg-muted rounded"></div>
|
<p className="text-sm text-muted-foreground">{t('loadingPrompts')}</p>
|
||||||
<div className="flex gap-2">
|
</div>
|
||||||
<div className="h-9 bg-muted rounded w-20"></div>
|
|
||||||
<div className="h-9 bg-muted rounded w-20"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Card>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="max-w-7xl mx-auto px-4 py-8">
|
||||||
<div className="flex items-center justify-between">
|
{/* Header */}
|
||||||
<h1 className="text-2xl font-bold text-foreground">
|
<div className="mb-8">
|
||||||
{t('allPrompts')}
|
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 mb-4">
|
||||||
</h1>
|
<div>
|
||||||
<div className="flex items-center gap-2">
|
<h1 className="text-2xl sm:text-3xl font-bold text-foreground mb-2">
|
||||||
<Badge variant="secondary">
|
{t('allPrompts')}
|
||||||
{prompts.filter(p => !p.visibility || p.visibility === 'under_review').length} {t('pending')}
|
</h1>
|
||||||
</Badge>
|
<p className="text-muted-foreground">
|
||||||
<Badge variant="outline">
|
{t('reviewPromptsDesc')}
|
||||||
{prompts.filter(p => p.visibility === 'published').length} {t('published')}
|
</p>
|
||||||
</Badge>
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Badge variant="secondary" className="text-xs sm:text-sm">
|
||||||
|
{prompts.filter(p => !p.visibility || p.visibility === 'under_review').length} {t('pending')}
|
||||||
|
</Badge>
|
||||||
|
<Badge variant="outline" className="text-xs sm:text-sm">
|
||||||
|
{prompts.filter(p => p.visibility === 'published').length} {t('published')}
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Content */}
|
||||||
{prompts.length === 0 ? (
|
{prompts.length === 0 ? (
|
||||||
<Card className="p-8 text-center">
|
<Card className="p-8 lg:p-12 text-center">
|
||||||
<CheckCircle className="h-12 w-12 text-green-500 mx-auto mb-4" />
|
<div className="max-w-md mx-auto">
|
||||||
<h3 className="text-lg font-semibold text-foreground mb-2">
|
<CheckCircle className="h-12 w-12 lg:h-16 lg:w-16 text-green-500 mx-auto mb-4" />
|
||||||
{t('noPromptsPending')}
|
<h3 className="text-lg lg:text-xl font-semibold text-foreground mb-2">
|
||||||
</h3>
|
{t('noPromptsPending')}
|
||||||
<p className="text-muted-foreground">
|
</h3>
|
||||||
{t('allPromptsReviewed')}
|
<p className="text-muted-foreground">
|
||||||
</p>
|
{t('allPromptsReviewed')}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
) : (
|
) : (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4 lg:space-y-6">
|
||||||
{prompts.map((prompt) => (
|
{prompts.map((prompt) => (
|
||||||
<Card key={prompt.id} className="p-6">
|
<Card key={prompt.id} className="p-4 lg:p-6 border-border hover:border-primary/20 transition-all duration-200">
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex items-start justify-between">
|
<div className="flex flex-col sm:flex-row sm:items-start sm:justify-between gap-3">
|
||||||
<div className="space-y-1">
|
<div className="space-y-1 min-w-0 flex-1">
|
||||||
<h3 className="text-lg font-semibold text-foreground">
|
<h3 className="text-lg lg:text-xl font-semibold text-foreground break-words">
|
||||||
{prompt.name}
|
{prompt.name}
|
||||||
</h3>
|
</h3>
|
||||||
{prompt.description && (
|
{prompt.description && (
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground line-clamp-2 break-words">
|
||||||
{prompt.description}
|
{prompt.description}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<Badge
|
<div className="flex-shrink-0">
|
||||||
variant={prompt.visibility === 'published' ? 'default' : 'outline'}
|
<Badge
|
||||||
className={prompt.visibility === 'published' ? 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200' : ''}
|
variant={prompt.visibility === 'published' ? 'default' : 'outline'}
|
||||||
>
|
className={`text-xs sm:text-sm ${
|
||||||
{prompt.visibility === 'published' ? t('published') : t('underReview')}
|
prompt.visibility === 'published'
|
||||||
</Badge>
|
? 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200'
|
||||||
|
: ''
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{prompt.visibility === 'published' ? t('published') : t('underReview')}
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Metadata */}
|
{/* Metadata */}
|
||||||
<div className="flex items-center gap-4 text-sm text-muted-foreground">
|
<div className="flex flex-col sm:flex-row sm:items-center gap-2 sm:gap-4 text-sm text-muted-foreground">
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<UserIcon className="h-4 w-4" />
|
<UserIcon className="h-4 w-4 flex-shrink-0" />
|
||||||
<span>{prompt.user.username || prompt.user.email}</span>
|
<span className="truncate">{prompt.user.username || prompt.user.email}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<Calendar className="h-4 w-4" />
|
<Calendar className="h-4 w-4 flex-shrink-0" />
|
||||||
<span>
|
<span className="whitespace-nowrap">
|
||||||
{formatDistanceToNow(new Date(prompt.createdAt), { addSuffix: true })}
|
{formatDistanceToNow(new Date(prompt.createdAt), { addSuffix: true })}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Content Preview */}
|
{/* Content Preview */}
|
||||||
<div className="border border-border rounded-lg p-4 bg-muted/50">
|
<div className="border border-border rounded-lg p-3 lg:p-4 bg-muted/30">
|
||||||
<div className="flex items-center gap-2 mb-2">
|
<div className="flex items-center gap-2 mb-2">
|
||||||
<Eye className="h-4 w-4 text-muted-foreground" />
|
<Eye className="h-4 w-4 text-muted-foreground flex-shrink-0" />
|
||||||
<span className="text-sm font-medium text-muted-foreground">
|
<span className="text-sm font-medium text-muted-foreground">
|
||||||
{t('promptContent')}
|
{t('promptContent')}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-sm text-foreground max-h-32 overflow-y-auto">
|
<div className="text-sm text-foreground max-h-32 lg:max-h-40 overflow-y-auto break-words">
|
||||||
{prompt.content.length > 500
|
{prompt.content.length > 500
|
||||||
? `${prompt.content.slice(0, 500)}...`
|
? `${prompt.content.slice(0, 500)}...`
|
||||||
: prompt.content
|
: prompt.content
|
||||||
@ -179,11 +198,11 @@ export default function AdminReviewPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Actions */}
|
{/* Actions */}
|
||||||
<div className="flex items-center gap-3 pt-2">
|
<div className="flex flex-col sm:flex-row sm:items-center gap-2 sm:gap-3 pt-2 border-t border-border/50">
|
||||||
{prompt.visibility !== 'published' && (
|
{prompt.visibility !== 'published' && (
|
||||||
<Button
|
<Button
|
||||||
onClick={() => handleApprove(prompt.id)}
|
onClick={() => handleApprove(prompt.id)}
|
||||||
className="bg-green-600 hover:bg-green-700 text-white"
|
className="bg-green-600 hover:bg-green-700 text-white w-full sm:w-auto"
|
||||||
size="sm"
|
size="sm"
|
||||||
>
|
>
|
||||||
<CheckCircle className="h-4 w-4 mr-2" />
|
<CheckCircle className="h-4 w-4 mr-2" />
|
||||||
@ -194,6 +213,7 @@ export default function AdminReviewPage() {
|
|||||||
onClick={() => handleReject(prompt.id)}
|
onClick={() => handleReject(prompt.id)}
|
||||||
variant="destructive"
|
variant="destructive"
|
||||||
size="sm"
|
size="sm"
|
||||||
|
className="w-full sm:w-auto"
|
||||||
>
|
>
|
||||||
<XCircle className="h-4 w-4 mr-2" />
|
<XCircle className="h-4 w-4 mr-2" />
|
||||||
{t('reject')}
|
{t('reject')}
|
||||||
|
Loading…
Reference in New Issue
Block a user