feat(gamification): aylık uzman sezonları — ilk 3'e otomatik üyelik uzatması
Parça Uzmanları sıralaması artık aylık sezon: liderlik tablosu içinde
bulunulan TR-ayının (Europe/Istanbul) puanlarını gösterir ve her ayın
1'i 00:00 TR'de kendiliğinden sıfırlanır. expert-rewards cron'u
(BullMQ scheduler, "0 0 1 * *" tz=Europe/Istanbul) aynı anda biten
sezonu kapatır ve ilk 3 oylayıcıya üyelik uzatması verir:
1. → 30 gün, 2. → 15 gün, 3. → 7 gün (EXPERT_REWARD_LADDER).
Ödül mekaniği referral'la birebir: canlı active/trial abonelik endDate
+gün uzar, yoksa günler users.referral_credit_days'e bankalanır (sonraki
trial/aktivasyonda tüketilir). oem_expert_rewards (migration 0017,
period+rank UNIQUE) hem denetim kaydı hem run-once garantisi — retry ya
da elle tetik çift ödül veremez. Sıralama ölçütü job ve leaderboard'da
birebir aynı (puan desc, eşitlikte puana erken ulaşan önde).
Web: uzmanlar sayfasına sezon şeridi ("Haziran 2026 sezonu" + 🥇1 ay ·
🥈15 gün · 🥉7 gün rozetleri); alt küçük-punto kural satırına aylık
sıfırlama + ödül notu eklendi. Leaderboard cevabına periodStart eklendi.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -23,6 +23,24 @@ interface LeaderboardEntry {
|
||||
interface LeaderboardResponse {
|
||||
entries: LeaderboardEntry[];
|
||||
me: { rank: number | null; points: number; votes: number };
|
||||
// İçinde bulunulan TR-ayı sezonunun başlangıcı (ISO) — sezon etiketi bundan üretilir.
|
||||
periodStart?: string;
|
||||
}
|
||||
|
||||
// Aylık ödül merdiveni (API'deki EXPERT_REWARD_LADDER ile aynı).
|
||||
const SEASON_PRIZES = [
|
||||
{ key: "r1", medal: "🥇", label: "1 ay üyelik" },
|
||||
{ key: "r2", medal: "🥈", label: "15 gün" },
|
||||
{ key: "r3", medal: "🥉", label: "7 gün" },
|
||||
] as const;
|
||||
|
||||
function seasonLabel(periodStart?: string): string {
|
||||
const date = periodStart ? new Date(periodStart) : new Date();
|
||||
return new Intl.DateTimeFormat("tr-TR", {
|
||||
month: "long",
|
||||
year: "numeric",
|
||||
timeZone: "Europe/Istanbul",
|
||||
}).format(date);
|
||||
}
|
||||
|
||||
export const Route = createFileRoute("/dashboard/uzmanlar")({
|
||||
@@ -69,11 +87,28 @@ function ExpertsPage() {
|
||||
Parça Uzmanları
|
||||
</h1>
|
||||
<p className="mt-1 text-sm text-muted-foreground">
|
||||
OEM kodlarını oyla, puan topla, sıralamada yüksel — doğru bilgi bütün parça
|
||||
satıcılarının işini hızlandırır.
|
||||
OEM kodlarını oyla, puan topla, sıralamada yüksel — doğru bilgi bütün parça satıcılarının
|
||||
işini hızlandırır.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Sezon şeridi: ayın ilk 3 uzmanı üyelik kazanır */}
|
||||
<div className="flex flex-wrap items-center gap-x-3 gap-y-2 rounded-xl border border-amber-400/30 bg-amber-400/5 px-4 py-3 text-sm">
|
||||
<span className="font-semibold capitalize">{seasonLabel(data?.periodStart)} sezonu</span>
|
||||
<span className="text-muted-foreground">ayın ilk 3 uzmanına üyelik:</span>
|
||||
<span className="flex flex-wrap items-center gap-2">
|
||||
{SEASON_PRIZES.map(({ key, medal, label }) => (
|
||||
<span
|
||||
key={key}
|
||||
className="inline-flex items-center gap-1 rounded-full border border-border bg-background px-2.5 py-0.5 text-xs font-medium"
|
||||
>
|
||||
<span aria-hidden="true">{medal}</span>
|
||||
{label}
|
||||
</span>
|
||||
))}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{isLoading ? (
|
||||
<div className="space-y-3">
|
||||
{SKELETON_KEYS.map((k) => (
|
||||
@@ -128,7 +163,9 @@ function ExpertsPage() {
|
||||
<p className="pt-2 text-center text-[11px] leading-relaxed text-muted-foreground/70">
|
||||
Oy ver <span className="font-semibold text-muted-foreground">+1</span> · çoğunluğu tuttur{" "}
|
||||
<span className="font-semibold text-muted-foreground">+2 bonus</span> · kodu ilk
|
||||
değerlendiren <span className="font-semibold text-muted-foreground">3 puanı</span> kapar
|
||||
değerlendiren <span className="font-semibold text-muted-foreground">3 puanı</span> kapar ·
|
||||
sıralama her ayın 1'i 00:00'da sıfırlanır, biten ayın ilk 3'üne üyelik uzatması otomatik
|
||||
tanımlanır
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user