fix: p7/p4 denetim bulguları — derin sayfa sıra yılı ve başlık uzunluğu, giriş sayacı, rybbit host
- derin /bolum ve /universite sayfalarında sıra aralığı hangi yıldan geldiğini söyler (2025 / 2024 / 2024–2025); 2024 sırası '2025 verisi' diye sunulmaz - <title> yuvarlanmış rakamlarla ve şablon eki olmadan ≤~86 karakter - giriş formunda tek geri sayım interval'i (çift sayaç butonu erken açıyordu) - RYBBIT_HOST sonundaki '/' atılır Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -14,7 +14,12 @@ import {
|
|||||||
sayfaMetadata,
|
sayfaMetadata,
|
||||||
ustSegmentOgGorseli,
|
ustSegmentOgGorseli,
|
||||||
} from "@/lib/seo";
|
} 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 { BolumProgramListesi } from "./bolum-program-listesi";
|
||||||
import { VeriKaynagi } from "./veri-kaynagi";
|
import { VeriKaynagi } from "./veri-kaynagi";
|
||||||
import { CtaSiraForm } from "@/features/sihirbaz/components/cta-sira-form";
|
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 minSira: number | null = null;
|
||||||
let maxSira: number | null = null;
|
let maxSira: number | null = null;
|
||||||
let verisiz = 0;
|
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) {
|
for (const p of dilim) {
|
||||||
const efektif = p.sira2025 ?? p.sira2024;
|
const efektif = p.sira2025 ?? p.sira2024;
|
||||||
if (efektif == null) {
|
if (efektif == null) {
|
||||||
verisiz += 1;
|
verisiz += 1;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if (p.sira2025 == null) yalniz2024 += 1;
|
||||||
if (minSira == null || efektif < minSira) minSira = efektif;
|
if (minSira == null || efektif < minSira) minSira = efektif;
|
||||||
if (maxSira == null || efektif > maxSira) maxSira = efektif;
|
if (maxSira == null || efektif > maxSira) maxSira = efektif;
|
||||||
}
|
}
|
||||||
return { tumProgramlar, dilim, minSira, maxSira, verisiz };
|
return { tumProgramlar, dilim, minSira, maxSira, verisiz, yalniz2024 };
|
||||||
|
}
|
||||||
|
|
||||||
|
/** <title> 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,
|
minSira,
|
||||||
maxSira,
|
maxSira,
|
||||||
verisiz,
|
verisiz,
|
||||||
|
yalniz2024,
|
||||||
}: ReturnType<typeof bolumSayfaDilimi>): string | null {
|
}: ReturnType<typeof bolumSayfaDilimi>): string | null {
|
||||||
if (dilim.length === 0) return null;
|
if (dilim.length === 0) return null;
|
||||||
if (minSira == null || maxSira == null) {
|
if (minSira == null || maxSira == null) {
|
||||||
return "taban verisi olmayan programlar";
|
return "taban verisi olmayan programlar";
|
||||||
}
|
}
|
||||||
|
const yil = siraYiliEtiketi(yalniz2024, dilim.length - verisiz);
|
||||||
const aralik =
|
const aralik =
|
||||||
minSira === maxSira
|
minSira === maxSira
|
||||||
? `taban sırası ${sayi(minSira)} olan programlar`
|
? `${yil} taban sırası ${sayi(minSira)} olan programlar`
|
||||||
: `taban sırası ${sayi(minSira)}–${sayi(maxSira)} arası programlar`;
|
: `${yil} taban sırası ${sayi(minSira)}–${sayi(maxSira)} arası programlar`;
|
||||||
return verisiz > 0 ? `${aralik} ile taban verisi olmayanlar` : aralik;
|
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 sayfaDilim = bolumSayfaDilimi(bolum, sayfa);
|
||||||
const aralik = siraAraligiMetni(sayfaDilim);
|
const aralik = siraAraligiMetni(sayfaDilim);
|
||||||
const { minSira, maxSira } = sayfaDilim;
|
const { minSira, maxSira } = sayfaDilim;
|
||||||
// <title> kısa kalsın: aralığın yalnız rakamları; açıklama tam cümleyi taşır.
|
// <title> 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 =
|
const baslikEki =
|
||||||
minSira != null && maxSira != null
|
minSira != null && maxSira != null
|
||||||
? minSira === maxSira
|
? minSira === maxSira
|
||||||
? `Sıra ${sayi(minSira)}, Sayfa ${sayfa}`
|
? `Sıra ${kisaSira(minSira)}, s. ${sayfa}`
|
||||||
: `Sıra ${sayi(minSira)}–${sayi(maxSira)}, Sayfa ${sayfa}`
|
: `Sıra ${kisaSira(minSira)}–${kisaSira(maxSira)}, s. ${sayfa}`
|
||||||
: `Sayfa ${sayfa}`;
|
: `Sayfa ${sayfa}`;
|
||||||
return sayfaMetadata({
|
const baslik = `${bolum.ad} Taban Puanları (${baslikEki})`;
|
||||||
title: `${bolum.ad} Taban Puanları 2026 (${baslikEki})`,
|
const meta = sayfaMetadata({
|
||||||
|
title: baslik,
|
||||||
description: aralik
|
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.`,
|
: `${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}`,
|
path: `/bolum/${slug}/sayfa/${sayfa}`,
|
||||||
openGraph: {
|
openGraph: {
|
||||||
@@ -122,6 +144,7 @@ export function bolumSayfaMetadata(slug: string, sayfa: number): Metadata {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
return { ...meta, title: { absolute: baslik } };
|
||||||
}
|
}
|
||||||
const enIyi =
|
const enIyi =
|
||||||
stats.minSira != null
|
stats.minSira != null
|
||||||
|
|||||||
@@ -13,6 +13,12 @@ export function sayfaDilimi<T>(liste: readonly T[], sayfa: number): T[] {
|
|||||||
return liste.slice((sayfa - 1) * SAYFA_BOYU, sayfa * SAYFA_BOYU);
|
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ü
|
* Ü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
|
* cümlesi — iki sayfa birebir aynı metni kullansın diye tek kaynak. Sunucu
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import {
|
|||||||
Sayfalama,
|
Sayfalama,
|
||||||
SAYFA_BOYU,
|
SAYFA_BOYU,
|
||||||
sayfaDilimi,
|
sayfaDilimi,
|
||||||
|
siraYiliEtiketi,
|
||||||
} from "./sayfalama";
|
} from "./sayfalama";
|
||||||
import { PagePixelDivider } from "@/components/pixel-decor";
|
import { PagePixelDivider } from "@/components/pixel-decor";
|
||||||
import { UniversiteKonumHaritasi } from "./universite-konum-haritasi";
|
import { UniversiteKonumHaritasi } from "./universite-konum-haritasi";
|
||||||
@@ -95,9 +96,13 @@ function fakulteOzeti({
|
|||||||
if (siralar.length === 0) return adlar[0];
|
if (siralar.length === 0) return adlar[0];
|
||||||
const min = Math.min(...siralar);
|
const min = Math.min(...siralar);
|
||||||
const max = Math.max(...siralar);
|
const max = Math.max(...siralar);
|
||||||
|
const yil = siraYiliEtiketi(
|
||||||
|
dilim.filter((p) => p.sira2025 == null && p.sira2024 != null).length,
|
||||||
|
siralar.length,
|
||||||
|
);
|
||||||
return min === max
|
return min === max
|
||||||
? `${adlar[0]} (taban sırası ${sayi(min)})`
|
? `${adlar[0]} (${yil} taban sırası ${sayi(min)})`
|
||||||
: `${adlar[0]} (taban sırası ${sayi(min)}–${sayi(max)} arası)`;
|
: `${adlar[0]} (${yil} taban sırası ${sayi(min)}–${sayi(max)} arası)`;
|
||||||
}
|
}
|
||||||
if (adlar.length === 2) return `${adlar[0]} ve ${adlar[1]}`;
|
if (adlar.length === 2) return `${adlar[0]} ve ${adlar[1]}`;
|
||||||
return `${adlar[0]}, ${adlar[1]} ve ${adlar.length - 2} birim daha`;
|
return `${adlar[0]}, ${adlar[1]} ve ${adlar.length - 2} birim daha`;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { Mail } from "lucide-react";
|
import { Mail } from "lucide-react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { authClient } from "@/lib/auth-client";
|
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
|
// 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.
|
// 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<ReturnType<typeof setInterval> | null>(null);
|
||||||
function geriSayimBaslat() {
|
function geriSayimBaslat() {
|
||||||
|
if (sayacRef.current) clearInterval(sayacRef.current);
|
||||||
setBekleme(60);
|
setBekleme(60);
|
||||||
const sayac = setInterval(() => {
|
sayacRef.current = setInterval(() => {
|
||||||
setBekleme((s) => {
|
setBekleme((s) => {
|
||||||
if (s <= 1) {
|
if (s <= 1) {
|
||||||
clearInterval(sayac);
|
if (sayacRef.current) clearInterval(sayacRef.current);
|
||||||
|
sayacRef.current = null;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
return s - 1;
|
return s - 1;
|
||||||
});
|
});
|
||||||
}, 1000);
|
}, 1000);
|
||||||
}
|
}
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
if (sayacRef.current) clearInterval(sayacRef.current);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
async function googleIleGiris() {
|
async function googleIleGiris() {
|
||||||
setBusy("google");
|
setBusy("google");
|
||||||
|
|||||||
@@ -10,7 +10,9 @@ import "server-only";
|
|||||||
* Env yoksa (dev, self-host anahtarı tanımsız) hiçbir istek yapılmaz.
|
* 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 SITE_ID = process.env.RYBBIT_SITE_ID;
|
||||||
const API_KEY = process.env.RYBBIT_API_KEY;
|
const API_KEY = process.env.RYBBIT_API_KEY;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user