diff --git a/src/features/katalog/components/bolum-icerik.tsx b/src/features/katalog/components/bolum-icerik.tsx
index d9b5bbf..6197998 100644
--- a/src/features/katalog/components/bolum-icerik.tsx
+++ b/src/features/katalog/components/bolum-icerik.tsx
@@ -14,7 +14,12 @@ import {
sayfaMetadata,
ustSegmentOgGorseli,
} from "@/lib/seo";
-import { KATALOG_CTA_ACIKLAMA, SAYFA_BOYU, sayfaDilimi } from "./sayfalama";
+import {
+ KATALOG_CTA_ACIKLAMA,
+ SAYFA_BOYU,
+ sayfaDilimi,
+ siraYiliEtiketi,
+} from "./sayfalama";
import { BolumProgramListesi } from "./bolum-program-listesi";
import { VeriKaynagi } from "./veri-kaynagi";
import { CtaSiraForm } from "@/features/sihirbaz/components/cta-sira-form";
@@ -60,16 +65,28 @@ function bolumSayfaDilimi(bolum: BolumDetay, sayfa: number) {
let minSira: number | null = null;
let maxSira: number | null = null;
let verisiz = 0;
+ // Sırası 2025'te olmayıp 2024'ten gelen satırlar: aralık metni yılı dürüstçe
+ // söylesin (tablonun Sıralama sütunu bu satırlarda "—" gösterir).
+ let yalniz2024 = 0;
for (const p of dilim) {
const efektif = p.sira2025 ?? p.sira2024;
if (efektif == null) {
verisiz += 1;
continue;
}
+ if (p.sira2025 == null) yalniz2024 += 1;
if (minSira == null || efektif < minSira) minSira = efektif;
if (maxSira == null || efektif > maxSira) maxSira = efektif;
}
- return { tumProgramlar, dilim, minSira, maxSira, verisiz };
+ return { tumProgramlar, dilim, minSira, maxSira, verisiz, yalniz2024 };
+}
+
+/**
için kısa sıra: 2.425.061 → "2,43 mn", 95.400 → "95 bin". */
+function kisaSira(n: number): string {
+ if (n >= 1_000_000) {
+ return `${(n / 1e6).toLocaleString("tr-TR", { maximumFractionDigits: 2 })} mn`;
+ }
+ return n >= 10_000 ? `${Math.round(n / 1000)} bin` : sayi(n);
}
/**
@@ -82,15 +99,17 @@ function siraAraligiMetni({
minSira,
maxSira,
verisiz,
+ yalniz2024,
}: ReturnType): string | null {
if (dilim.length === 0) return null;
if (minSira == null || maxSira == null) {
return "taban verisi olmayan programlar";
}
+ const yil = siraYiliEtiketi(yalniz2024, dilim.length - verisiz);
const aralik =
minSira === maxSira
- ? `taban sırası ${sayi(minSira)} olan programlar`
- : `taban sırası ${sayi(minSira)}–${sayi(maxSira)} arası programlar`;
+ ? `${yil} taban sırası ${sayi(minSira)} olan programlar`
+ : `${yil} taban sırası ${sayi(minSira)}–${sayi(maxSira)} arası programlar`;
return verisiz > 0 ? `${aralik} ile taban verisi olmayanlar` : aralik;
}
@@ -102,17 +121,20 @@ export function bolumSayfaMetadata(slug: string, sayfa: number): Metadata {
const sayfaDilim = bolumSayfaDilimi(bolum, sayfa);
const aralik = siraAraligiMetni(sayfaDilim);
const { minSira, maxSira } = sayfaDilim;
- // kısa kalsın: aralığın yalnız rakamları; açıklama tam cümleyi taşır.
+ // kısa kalsın (arama sonucunda ayırt edici kısım kesilmesin):
+ // yuvarlanmış rakamlar, kök şablonun " — KolayTercih" eki yok; açıklama tam
+ // cümleyi taşır. "s. N" yuvarlamada çakışan iki sayfayı yine ayırır.
const baslikEki =
minSira != null && maxSira != null
? minSira === maxSira
- ? `Sıra ${sayi(minSira)}, Sayfa ${sayfa}`
- : `Sıra ${sayi(minSira)}–${sayi(maxSira)}, Sayfa ${sayfa}`
+ ? `Sıra ${kisaSira(minSira)}, s. ${sayfa}`
+ : `Sıra ${kisaSira(minSira)}–${kisaSira(maxSira)}, s. ${sayfa}`
: `Sayfa ${sayfa}`;
- return sayfaMetadata({
- title: `${bolum.ad} Taban Puanları 2026 (${baslikEki})`,
+ const baslik = `${bolum.ad} Taban Puanları (${baslikEki})`;
+ const meta = sayfaMetadata({
+ title: baslik,
description: aralik
- ? `${bolum.ad} taban puanları, sayfa ${sayfa}: ${aralik}. Toplam ${stats.toplam} programın 2025 yerleştirme verileri — taban puanı, başarı sıralaması ve kontenjan.`
+ ? `${bolum.ad} taban puanları, sayfa ${sayfa}: ${aralik}. Toplam ${stats.toplam} programın son yerleştirme verileri (2025; 2025'te verisi olmayanlarda 2024) — taban puanı, başarı sıralaması ve kontenjan.`
: `${bolum.ad} taban puanları ve başarı sıralamaları tablosunun ${sayfa}. sayfası — toplam ${stats.toplam} programın 2025 yerleştirme verileri.`,
path: `/bolum/${slug}/sayfa/${sayfa}`,
openGraph: {
@@ -122,6 +144,7 @@ export function bolumSayfaMetadata(slug: string, sayfa: number): Metadata {
),
},
});
+ return { ...meta, title: { absolute: baslik } };
}
const enIyi =
stats.minSira != null
diff --git a/src/features/katalog/components/sayfalama.tsx b/src/features/katalog/components/sayfalama.tsx
index f86685f..a7e56d3 100644
--- a/src/features/katalog/components/sayfalama.tsx
+++ b/src/features/katalog/components/sayfalama.tsx
@@ -13,6 +13,12 @@ export function sayfaDilimi(liste: readonly T[], sayfa: number): T[] {
return liste.slice((sayfa - 1) * SAYFA_BOYU, sayfa * SAYFA_BOYU);
}
+/** Aralıktaki sıraların hangi yıldan geldiği: hepsi 2025 / hepsi 2024 / karışık. */
+export function siraYiliEtiketi(yalniz2024: number, siraliSayi: number): string {
+ if (yalniz2024 === 0) return "2025";
+ return yalniz2024 === siraliSayi ? "2024" : "2024–2025";
+}
+
/**
* Üniversite ve bölüm tablo sayfalarının altındaki CtaSiraForm kartının köprü
* cümlesi — iki sayfa birebir aynı metni kullansın diye tek kaynak. Sunucu
diff --git a/src/features/katalog/components/universite-icerik.tsx b/src/features/katalog/components/universite-icerik.tsx
index 207f9b7..50821ab 100644
--- a/src/features/katalog/components/universite-icerik.tsx
+++ b/src/features/katalog/components/universite-icerik.tsx
@@ -23,6 +23,7 @@ import {
Sayfalama,
SAYFA_BOYU,
sayfaDilimi,
+ siraYiliEtiketi,
} from "./sayfalama";
import { PagePixelDivider } from "@/components/pixel-decor";
import { UniversiteKonumHaritasi } from "./universite-konum-haritasi";
@@ -95,9 +96,13 @@ function fakulteOzeti({
if (siralar.length === 0) return adlar[0];
const min = Math.min(...siralar);
const max = Math.max(...siralar);
+ const yil = siraYiliEtiketi(
+ dilim.filter((p) => p.sira2025 == null && p.sira2024 != null).length,
+ siralar.length,
+ );
return min === max
- ? `${adlar[0]} (taban sırası ${sayi(min)})`
- : `${adlar[0]} (taban sırası ${sayi(min)}–${sayi(max)} arası)`;
+ ? `${adlar[0]} (${yil} taban sırası ${sayi(min)})`
+ : `${adlar[0]} (${yil} taban sırası ${sayi(min)}–${sayi(max)} arası)`;
}
if (adlar.length === 2) return `${adlar[0]} ve ${adlar[1]}`;
return `${adlar[0]}, ${adlar[1]} ve ${adlar.length - 2} birim daha`;
diff --git a/src/features/kullanici/components/giris-form.tsx b/src/features/kullanici/components/giris-form.tsx
index 6b51fd0..3ec62e1 100644
--- a/src/features/kullanici/components/giris-form.tsx
+++ b/src/features/kullanici/components/giris-form.tsx
@@ -1,6 +1,6 @@
"use client";
-import { useState } from "react";
+import { useEffect, useRef, useState } from "react";
import { Mail } from "lucide-react";
import { toast } from "sonner";
import { authClient } from "@/lib/auth-client";
@@ -40,18 +40,28 @@ export function GirisForm({ callbackURL }: { callbackURL: string }) {
// 60 sn: sunucunun adres başına gönderim soğumasıyla hizalı; daha kısa
// sayaçta "Yeniden gönder" sessizce yutulur, kullanıcı gelmeyen maili bekler.
+ // Tek sayaç: "E-postayı düzelt" → yeniden gönderimde eski interval durmazsa
+ // iki sayaç birlikte sayar, buton sunucu soğuması bitmeden açılır.
+ const sayacRef = useRef | null>(null);
function geriSayimBaslat() {
+ if (sayacRef.current) clearInterval(sayacRef.current);
setBekleme(60);
- const sayac = setInterval(() => {
+ sayacRef.current = setInterval(() => {
setBekleme((s) => {
if (s <= 1) {
- clearInterval(sayac);
+ if (sayacRef.current) clearInterval(sayacRef.current);
+ sayacRef.current = null;
return 0;
}
return s - 1;
});
}, 1000);
}
+ useEffect(() => {
+ return () => {
+ if (sayacRef.current) clearInterval(sayacRef.current);
+ };
+ }, []);
async function googleIleGiris() {
setBusy("google");
diff --git a/src/lib/analitik-sunucu.ts b/src/lib/analitik-sunucu.ts
index 3e2b8cf..be212a2 100644
--- a/src/lib/analitik-sunucu.ts
+++ b/src/lib/analitik-sunucu.ts
@@ -10,7 +10,9 @@ import "server-only";
* Env yoksa (dev, self-host anahtarı tanımsız) hiçbir istek yapılmaz.
*/
-const HOST = process.env.RYBBIT_HOST;
+// Sondaki "/" atılır: secret "https://…/" girilirse `//api/track` 404 olup
+// olaylar sessizce kaybolmasın.
+const HOST = process.env.RYBBIT_HOST?.replace(/\/+$/, "");
const SITE_ID = process.env.RYBBIT_SITE_ID;
const API_KEY = process.env.RYBBIT_API_KEY;