feat(tutorials): add video training academy management with YouTube parser and live preview
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
import Link from 'next/link'
|
||||
import { supabaseAdmin, listAuthUsers } from '@/lib/supabaseAdmin'
|
||||
|
||||
interface ProfileRow {
|
||||
id: string
|
||||
full_name: string | null
|
||||
bar_association_no: string | null
|
||||
bar_name: string | null
|
||||
license_status: string | null
|
||||
license_expires_at: string | null
|
||||
trial_end_date: string | null
|
||||
org_id: string | null
|
||||
created_at: string
|
||||
}
|
||||
|
||||
function statusBadge(status: string | null) {
|
||||
const map: Record<string, string> = {
|
||||
active: 'bg-green-50 text-green-700 border-green-200 dark:bg-green-950/40 dark:text-green-400 dark:border-green-900',
|
||||
trial: 'bg-amber-50 text-amber-700 border-amber-200 dark:bg-amber-950/40 dark:text-amber-400 dark:border-amber-900',
|
||||
expired: 'bg-red-50 text-red-700 border-red-200 dark:bg-red-950/40 dark:text-red-400 dark:border-red-900',
|
||||
}
|
||||
const cls = map[status || ''] || 'bg-gray-50 text-gray-600 border-gray-200 dark:bg-gray-900 dark:text-gray-400 dark:border-gray-800'
|
||||
return (
|
||||
<span className={`inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium border ${cls}`}>
|
||||
{status || 'bilinmiyor'}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
function formatDate(iso: string | null) {
|
||||
if (!iso) return '—'
|
||||
return new Date(iso).toLocaleDateString('tr-TR', { day: 'numeric', month: 'short', year: 'numeric' })
|
||||
}
|
||||
|
||||
export default async function UsersPage({ searchParams }: { searchParams: Promise<{ deleted?: string }> }) {
|
||||
const { deleted } = await searchParams
|
||||
const [{ data: profiles }, authUsers] = await Promise.all([
|
||||
supabaseAdmin
|
||||
.from('profiles')
|
||||
.select('id, full_name, bar_association_no, bar_name, license_status, license_expires_at, trial_end_date, org_id, created_at')
|
||||
.order('created_at', { ascending: false }),
|
||||
listAuthUsers(),
|
||||
])
|
||||
|
||||
const rows = (profiles as ProfileRow[] | null) || []
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{deleted && (
|
||||
<div className="rounded-md bg-green-50 dark:bg-green-950/40 border border-green-200 dark:border-green-900 px-4 py-3 text-sm text-green-700 dark:text-green-400">
|
||||
Kullanıcı silindi.
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<h2 className="text-2xl font-bold tracking-tight text-gray-900 dark:text-white">Kullanıcılar</h2>
|
||||
<p className="text-gray-500 dark:text-gray-400 mt-2">Toplam {rows.length} kullanıcı.</p>
|
||||
</div>
|
||||
|
||||
<div className="rounded-lg bg-white dark:bg-gray-950 border border-gray-200 dark:border-gray-800 shadow-sm overflow-hidden">
|
||||
<div className="overflow-x-auto">
|
||||
<table className="min-w-full divide-y divide-gray-200 dark:divide-gray-800">
|
||||
<thead className="bg-gray-50 dark:bg-gray-900">
|
||||
<tr>
|
||||
{['Kullanıcı', 'Baro', 'Durum', 'Bitiş Tarihi', 'Kayıt', ''].map((h) => (
|
||||
<th key={h} className="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">{h}</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-gray-100 dark:divide-gray-800">
|
||||
{rows.map((p) => {
|
||||
const authUser = authUsers.get(p.id)
|
||||
const expiryDate = p.license_status === 'trial' ? p.trial_end_date : p.license_expires_at
|
||||
return (
|
||||
<tr key={p.id} className="hover:bg-gray-50 dark:hover:bg-gray-900/50">
|
||||
<td className="px-4 py-3 whitespace-nowrap">
|
||||
<div className="text-sm font-medium text-gray-900 dark:text-white">{p.full_name || '(isimsiz)'}</div>
|
||||
<div className="text-xs text-gray-500">{authUser?.email || p.id}</div>
|
||||
</td>
|
||||
<td className="px-4 py-3 whitespace-nowrap text-sm text-gray-600 dark:text-gray-400">
|
||||
{p.bar_name || '—'}{p.bar_association_no ? ` · ${p.bar_association_no}` : ''}
|
||||
</td>
|
||||
<td className="px-4 py-3 whitespace-nowrap">{statusBadge(p.license_status)}</td>
|
||||
<td className="px-4 py-3 whitespace-nowrap text-sm text-gray-600 dark:text-gray-400">{formatDate(expiryDate)}</td>
|
||||
<td className="px-4 py-3 whitespace-nowrap text-sm text-gray-500">{formatDate(p.created_at)}</td>
|
||||
<td className="px-4 py-3 whitespace-nowrap text-right text-sm">
|
||||
<Link href={`/admin/users/${p.id}`} className="text-indigo-600 dark:text-indigo-400 hover:underline">
|
||||
Düzenle
|
||||
</Link>
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
})}
|
||||
{rows.length === 0 && (
|
||||
<tr>
|
||||
<td colSpan={6} className="px-4 py-8 text-center text-sm text-gray-500">Henüz kullanıcı yok.</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user