feat(tutorials): add video training academy management with YouTube parser and live preview

This commit is contained in:
mstfyldz
2026-08-22 13:49:00 +03:00
parent 2d149f1178
commit 7f690cc6d3
29 changed files with 2476 additions and 1034 deletions
@@ -0,0 +1,145 @@
'use client'
import { useActionState, useState } from 'react'
import Link from 'next/link'
import { createLicenseAction, type CreateLicenseState } from '../actions'
import type { CustomerOption } from '@/lib/supabaseAdmin'
const initialState: CreateLicenseState = {}
export default function NewLicenseForm({ customers }: { customers: CustomerOption[] }) {
const [state, formAction, pending] = useActionState(createLicenseAction, initialState)
const [copied, setCopied] = useState(false)
if (state.result) {
const { id, licenseKey } = state.result
return (
<div className="rounded-lg bg-white dark:bg-gray-950 border border-gray-200 dark:border-gray-800 shadow-sm p-6 space-y-5">
<div className="rounded-md bg-amber-50 dark:bg-amber-950/40 border border-amber-200 dark:border-amber-900 px-4 py-3 text-sm text-amber-800 dark:text-amber-400">
Bu anahtar bir daha gösterilmeyecek şimdi kopyalayıp müşteriye iletin.
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Lisans Anahtarı</label>
<div className="flex items-center gap-2">
<code className="flex-1 rounded-md border border-gray-300 dark:border-gray-700 bg-gray-50 dark:bg-gray-900 px-3 py-2 text-lg font-mono tracking-wider text-gray-900 dark:text-white">
{licenseKey}
</code>
<button
type="button"
onClick={() => {
navigator.clipboard.writeText(licenseKey)
setCopied(true)
setTimeout(() => setCopied(false), 2000)
}}
className="rounded-md bg-indigo-600 hover:bg-indigo-700 text-white text-sm font-medium px-4 py-2 transition-colors whitespace-nowrap"
>
{copied ? 'Kopyalandı ✓' : 'Kopyala'}
</button>
</div>
</div>
<div className="flex items-center gap-3 pt-2">
<Link href={`/admin/licenses/${id}`} className="rounded-md bg-gray-900 hover:bg-gray-800 dark:bg-white dark:hover:bg-gray-200 text-white dark:text-gray-900 text-sm font-medium px-4 py-2 transition-colors">
Lisans Detayına Git
</Link>
<Link href="/admin/licenses" className="text-sm text-gray-500 hover:text-gray-900 dark:hover:text-white">
Lisanslara Dön
</Link>
</div>
</div>
)
}
return (
<form action={formAction} className="rounded-lg bg-white dark:bg-gray-950 border border-gray-200 dark:border-gray-800 shadow-sm p-6 space-y-5">
{state.error && (
<div className="rounded-md bg-red-50 dark:bg-red-950/40 border border-red-200 dark:border-red-900 px-4 py-3 text-sm text-red-700 dark:text-red-400">
{state.error}
</div>
)}
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Müşteri (opsiyonel)</label>
<select
name="customer_email"
defaultValue=""
className="w-full rounded-md border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-900 px-3 py-2 text-sm text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-indigo-500"
>
<option value=""> Bağlantı yok </option>
{customers.map((c) => (
<option key={c.id} value={c.email}>
{c.name || '(isimsiz)'} {c.email}
</option>
))}
</select>
<p className="mt-1 text-xs text-gray-400">Kullanıcılar listesinden seçin. Boş bırakırsanız sonra da eşleştirebilirsiniz.</p>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Plan</label>
<input
type="text"
name="plan"
defaultValue="professional"
list="plan-options"
className="w-full rounded-md border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-900 px-3 py-2 text-sm text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-indigo-500"
/>
<datalist id="plan-options">
<option value="trial" />
<option value="starter" />
<option value="professional" />
<option value="enterprise" />
</datalist>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Durum</label>
<select
name="status"
defaultValue="active"
className="w-full rounded-md border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-900 px-3 py-2 text-sm text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-indigo-500"
>
<option value="active">active</option>
<option value="expired">expired</option>
<option value="revoked">revoked</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Maks. Cihaz</label>
<input
type="number"
name="max_devices"
min={1}
defaultValue={2}
className="w-full rounded-md border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-900 px-3 py-2 text-sm text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-indigo-500"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Bitiş Tarihi (opsiyonel)</label>
<input
type="date"
name="expires_at"
className="w-full rounded-md border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-900 px-3 py-2 text-sm text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-indigo-500"
/>
</div>
</div>
<div className="flex items-center gap-3 pt-2">
<button
type="submit"
disabled={pending}
className="rounded-md bg-indigo-600 hover:bg-indigo-700 disabled:opacity-60 text-white text-sm font-medium px-4 py-2 transition-colors"
>
{pending ? 'Oluşturuluyor...' : 'Lisans Oluştur'}
</button>
<Link href="/admin/licenses" className="text-sm text-gray-500 hover:text-gray-900 dark:hover:text-white">
Vazgeç
</Link>
</div>
</form>
)
}