Skip to content

Admin Yönetim Paneli (Admin Console)

Kurs moderasyonu, finansal yönetim, ödeme/iade, fiyat katmanları (admin.price-tiers).

Coursio’da Admin rolüne sahip kullanıcılar, Admin Console üzerinden kurs onaylama/reddetme, platform geliri ve bekleyen ödemeleri yönetme, iade (refund) takibi ve fiyat katmanları (tier) güncellemesi yapar. Bu sayfa kullanıcı ve kurs moderasyonu, finansal yönetim (toplam gelir, bekleyen ödemeler, iade) ve admin.price-tiers.tsx ile global fiyat güncellemelerini açıklar.

  • Yetki: Tüm admin route’larında session.user.role === "admin" kontrolü yapılır; değilse 401 veya 404.
  • Layout: app/routes/admin.tsx — sidebar (Kurslar, Kullanıcılar, Finans, Ödemeler, İadeler, Fiyat Katmanları, Abonelik vb.) ve bildirim kutusu (Son Hareketler / Tüm Bildirimler).
  • Dashboard: admin._index.tsxgetAdminStats(db) ile özet metrikler (kullanıcı sayıları, onay bekleyen kurs, gelir özeti).

Dosya: app/routes/admin.courses.tsx

Loader:

  • Tüm kurslar eğitmen bilgisiyle sayfalanır (courses + users join); alanlar: id, title, status, slug, price, thumbnailUrl, instructor.name, instructor.email.
  • status değerleri: draft, pending_review, published, archived.

Liste:

  • Her satırda kurs başlığı, eğitmen adı/e-posta, durum badge’i (İnceleme Bekliyor / Yayında / Taslak), fiyat, thumbnail.
  • İnceleme Bekliyor (pending_review): “İncele” ve “Onayla” / “Reddet” aksiyonları.

Karar mekanizması:

  • Onayla: GraphQL updateCourse(id, status: "published") → kurs yayına alınır.
  • Reddet: GraphQL updateCourse(id, status: "draft") → kurs taslağa çekilir.
  • İşlem sonrası toast ve revalidate() ile liste güncellenir.

Önizleme: “İncele” ile admin.course.$slug.preview sayfasına gidilir.

Dosya: app/routes/admin.course.$slug.preview.tsx

URL: /admin/course/:slug/preview

Loader:

  • Admin oturumu kontrolü; getCourseBySlug(db, params.slug) ile kurs; getCourseCurriculum(db, course.id) ile müfredat.
  • Toplam süre ve ders sayısı müfredattan hesaplanır; bölgesel fiyat getRegionalPrice(db, course.priceTierId, country) ile alınır.

Sayfa: Kurs başlığı, açıklama, öğrenme çıktıları, müfredat (bölüm/ders listesi, video hazır bilgisi), fiyat. “Denetim Listesine Dön” ile /admin/courses’a dönülür; onay/red işlemi kurs listesi sayfasındaki butonlarla yapılır.

Dosya: app/routes/admin.finances.tsx

Loader: getAdminStats(db) ve getAdminSystemStats(db) (app/lib/db-queries.ts).

getAdminStats: earnings tablosundan (status ≠ refunded) satış türüne göre ayrıştırma:

  • Brüt satış geliri, organik/direkt/affiliate satış sayıları ve payları.
  • Abonelik: brüt abonelik geliri, platform payı (%60), eğitmen havuzu (%40).
  • Toplam iade tutarı, net platform karı (totalRevenue) vb.

getAdminSystemStats:

  • Tamamlanan öğretmen ödemeleri toplamı (payout_requests.status = 'completed').
  • Bekleyen ödemeler toplamı (payout_requests.status = 'pending').

Sayfa kartları (örnek):

  • Net Platform Karı (totalRevenue).
  • Organik Satış Payı (purchaseRevenue, %55 komisyon).
  • Bekleyen Ödemeler (system.pendingPayoutAmount).
  • Toplam İade Tutarı (totalRefundedAmount).
  • Affiliate satış payı ve affiliate komisyonu.
  • Abonelik ekonomisi: brüt gelir, platform %60, eğitmen havuzu %40.
  • “Gelir Havuzunu Dağıt” butonu → adminDistributeSubscriptionEarnings(totalPool) mutation (abonelik havuzunu eğitmenlere dağıtım).

Dosya: app/routes/admin.payouts.tsx

Loader: payout_requests (pending/processing/completed/rejected) listesi; her talep için eğitmen bilgisi (ad, e-posta, ülke, manualPayoutDetails — TR için IBAN/Payoneer).

Action (Form POST):

  • intent: approve | reject, payoutId: talep ID.
  • Onay (approve):
    • Eğitmen TR değilse ve stripeConnectId varsa: Stripe transfers.create ile platform bakiyesinden eğitmen Connect hesabına transfer; stripeTransferId kaydedilir.
    • TR ise: manuel ödeme (admin bankadan gönderir); sadece durum güncellenir.
    • payout_requests: status = completed, processedAt, stripeTransferId (varsa).
    • İlgili earnings: status = withdrawn.
  • Red (reject): payout_requests.status = 'rejected'; ilgili earnings status = completed (tekrar talep edilebilir).

Dosya: app/routes/admin.refunds.tsx

Loader:

  • İade edilmiş kayıtlar: enrollments + courses join; refundedAt IS NOT NULL ve refundStatus = 'completed'.
  • Öğrenci ve eğitmen adı/e-posta ayrı sorgularla eklenir.
  • Toplam iade tutarı (course price toplamı) hesaplanır.

Sayfa: Tablo — öğrenci, kurs, eğitmen, kayıt tarihi, iade tarihi, iade sebebi (not_satisfied, wrong_course, technical_issues, other), kurs fiyatı. Arama ve filtre (örn. sebep). İade işleminin kendisi (Stripe refund vb.) genelde webhook veya ayrı refund aksiyonu ile yapılır; bu sayfa geçmiş iadeleri listeler ve raporlar.

Dosya: app/routes/admin.price-tiers.tsx

URL: /admin/price-tiers (admin layout altında).

Amaç: Bölgesel fiyatlandırmada kullanılan price_tiers ve tier_prices tablolarını yönetmek; eğitmenler kurslarına bir tier atar, bu sayfadan global fiyat güncellenir.

  • Admin kontrolü; priceTiers tablosu order artan sırada; her tier için tierPrices (currency, amount) çekilir.
  • Dönüş: tiers — her biri id, name, order, prices: [{ id, tierId, currency, amount }].
intentAçıklama
createTierYeni katman: name, orderprice_tiers insert.
updateTierKatman güncelle: tierId, name, orderprice_tiers update.
deleteTierKatman sil: tierIdprice_tiers delete (cascade ile tier_prices da silinir).
addPriceTier’a fiyat ekle: tierId, currency, amounttier_prices insert.
updatePriceFiyat güncelle: priceId, amounttier_prices update.
deletePriceFiyat sil: priceIdtier_prices delete.

Para birimleri: USD, EUR, TRY, GBP (CURRENCIES sabiti). Her tier için bu para birimlerinde ayrı satır; eksik currency’ler için “Fiyat ekle” formu.

  • Başlık: “Fiyat Katmanları” — “Eğitmenlerin kurslarına atayabileceği katmanları ve bölgesel fiyatları buradan yönetin.”
  • Yeni katman ekle: Form — name, order; submit → createTier.
  • Mevcut katmanlar: Accordion/liste — her tier için ad, sıra, fiyat sayısı; genişletilince:
    • Tier ad/sıra güncelleme formu (updateTier).
    • Fiyat listesi: currency + amount + “Kaydet” (updatePrice) + “Sil” (deletePrice).
    • Eksik currency için “Fiyat ekle” (addPrice) — currency select + amount.
    • “Katmanı sil” (deleteTier).

Böylece global fiyat güncellemeleri tek yerden yapılır; kurslar price_tier_id ile bu katmanlara bağlı olduğu için bölgesel fiyatlar (pricing-engine, tier_prices) otomatik güncellenir.

KonuAçıklama
Kurs moderasyonuadmin.courses: liste (status badge); Onayla → updateCourse(…, published); Reddet → draft. Önizleme: admin.course.$slug.preview.
Finansal yönetimadmin.finances: getAdminStats (gelir, organik/affiliate/abonelik, iade); getAdminSystemStats (bekleyen ödemeler); abonelik havuzu dağıtımı.
Bekleyen ödemeleradmin.payouts: liste; approve → Stripe transfer (TR değilse) veya manuel (TR); reject → status güncelleme.
İade yönetimiadmin.refunds: iade edilmiş enrollment listesi, toplam iade tutarı, arama/filtre.
Fiyat katmanlarıadmin.price-tiers: tier CRUD + tier_prices CRUD (currency/amount); global fiyat güncellemeleri.

İlgili mimari: Bölgesel Fiyatlandırma (tier_prices kullanımı), Stripe Connect ve TR Payout (ödeme onayı).

  • app/routes/admin.tsx — Admin layout; session ve role kontrolü; Son hareketler / Tüm bildirimler.
  • app/routes/admin._index.tsx — Admin dashboard.
  • app/routes/admin.courses.tsx — Kurs listesi, onay/red.
  • app/routes/admin.course.$slug.preview.tsx — Kurs önizleme.
  • app/routes/admin.users.tsx, admin.finances.tsx, admin.payouts.tsx, admin.refunds.tsx, admin.messages.tsx, admin.price-tiers.tsx, admin.subscription.tsx — Alt sayfalar.
  • app/routes/api.admin.activities.ts — GET /api/admin/activities.
  • app/lib/db-queries.ts — getAdminStats, getAdminNotificationCounts, getAdminRecentActivities, getAdminAllActivities.
  • app/lib/stripe-connect-payout.ts — processPayoutTransfer (admin onayı sonrası).