Refactor UI components for consistency and clarity across the application
All checks were successful
Deploy / deploy (push) Successful in 18m31s

- Updated AGENTS.md with new guidelines for UI consistency, including badge and modal styles.
- Refactored layout.tsx to improve the placement of the progressive blur effect.
- Enhanced NotFound component text for better user guidance.
- Replaced HeroFocusButton with HeroBaslik and KapanisCta in the Home component for improved clarity.
- Added SiteFooter to multiple pages for consistent footer display.
- Removed ListemSekmeleri component and adjusted ListemPage to streamline functionality.
- Updated various components to ensure consistent styling and user experience.
This commit is contained in:
bilalgursen
2026-08-02 04:28:27 +03:00
parent 6bd210075f
commit b4effc8545
48 changed files with 2173 additions and 1529 deletions

View File

@@ -2,13 +2,15 @@
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import Link from "next/link";
import { useRouter } from "next/navigation";
import { AramaFunnelKarti } from "@/components/arama-funnel-karti";
import { RehberKapak } from "@/components/rehber-kapak";
import { UniLogo } from "@/components/uni-logo";
import { aramaNormalize, eslesmePuani } from "@/lib/arama";
import type { RehberOzet } from "@/lib/rehber";
import { PUAN_TURU_ETIKET } from "@/lib/sihirbaz";
import {
profilDuzenlemeyiAc,
sonucaGitIste,
useTercihProfili,
} from "@/components/manuel-liste/profil-kapisi-store";
import {
@@ -56,9 +58,36 @@ const BOS_SONUCLAR: AramaSonuclari = { bolumler: [], universiteler: [] };
const REHBER_SONUC_SINIRI = 4;
const sayi = (deger: number) => deger.toLocaleString("tr-TR");
const KESFET_TONLARI = {
slate: {
pencere: "border-slate-200/80 bg-white",
nokta: "bg-slate-300",
rozet: "border-slate-200 bg-slate-50 text-slate-600",
cizgi: "bg-slate-200/90",
},
sky: {
pencere: "border-sky-200/70 bg-white",
nokta: "bg-sky-300",
rozet: "border-sky-200 bg-sky-50 text-sky-600",
cizgi: "bg-sky-100",
},
amber: {
pencere: "border-amber-200/70 bg-white",
nokta: "bg-amber-300",
rozet: "border-amber-200 bg-amber-50 text-amber-600",
cizgi: "bg-amber-100",
},
} as const;
export function KatalogArama({ rehberler }: { rehberler: RehberOzet[] }) {
const router = useRouter();
const profil = useTercihProfili();
const [acik, setAcik] = useState(false);
function siralamaGirVeyaSonucaGit() {
const { href } = sonucaGitIste();
if (href) router.push(href);
}
const [sorgu, setSorgu] = useState("");
const [sonuclar, setSonuclar] = useState<AramaSonuclari>(BOS_SONUCLAR);
const [yukleniyor, setYukleniyor] = useState(false);
@@ -162,11 +191,18 @@ export function KatalogArama({ rehberler }: { rehberler: RehberOzet[] }) {
return () => gozlemci.disconnect();
}, [acik, aramaAktif, yukleniyor, sonucVar, kenarBlurGuncelle]);
// Temizlik kapanışta değil açılışta yapılır: kapanış animasyonu sırasında
// içerik "Keşfet" ekranına zıplamaz, modal her açılışta temiz başlar.
// (X / sonuç linki `kapat` ile kapatır; orada temizlik yapılmadığı için
// eski sorgu, bayat sonuçlar ve yarıda kesilen fetch'in takılı kalan
// yükleniyor iskeleti bir sonraki açılışa taşınıyordu.)
function modalDegisti(yeniAcik: boolean) {
setAcik(yeniAcik);
if (!yeniAcik) {
if (yeniAcik) {
setSorgu("");
setSonuclar(BOS_SONUCLAR);
setYukleniyor(false);
setKenarBlur({ ust: false, alt: false });
}
}
@@ -182,42 +218,42 @@ export function KatalogArama({ rehberler }: { rehberler: RehberOzet[] }) {
<button
type="button"
onClick={profilDuzenlemeyiAc}
aria-label={`Sıralaman ${profil.sira.toLocaleString("tr-TR")} (${PUAN_TURU_ETIKET[profil.tur] ?? profil.tur}) — tercihlerini düzenle`}
className="group/profil flex cursor-pointer items-center gap-2.5 rounded-full py-1.5 pl-4 pr-1.5 transition-colors duration-200 hover:bg-slate-100"
aria-label={`Sıralamanı ve tercihlerini değiştir (${profil.sira.toLocaleString("tr-TR")})`}
className="group/profil flex cursor-pointer items-center gap-2 rounded-full py-1.5 pl-4 pr-1.5 transition-colors duration-150 hover:bg-slate-100 focus-visible:bg-slate-100 focus-visible:outline-none"
>
<Trophy className="size-4 text-orange-500" aria-hidden />
<span className="flex items-baseline gap-1.5">
<span className="font-semibold tabular-nums tracking-tight text-slate-900">
{profil.sira.toLocaleString("tr-TR")}
</span>
<span className="text-xs font-medium text-slate-400">
{PUAN_TURU_ETIKET[profil.tur] ?? profil.tur}
</span>
<Trophy className="size-4 shrink-0 text-orange-500" aria-hidden />
<span className="font-semibold tabular-nums tracking-tight text-slate-900">
{profil.sira.toLocaleString("tr-TR")}
</span>
<span className="flex items-center gap-1 rounded-full bg-slate-100 px-2.5 py-1.5 text-xs font-medium text-slate-500 transition-colors duration-200 group-hover/profil:bg-white group-hover/profil:text-slate-900">
<SquarePen className="size-3" aria-hidden />
Düzenle
<span
aria-hidden
className="flex max-w-0 items-center gap-1 overflow-hidden rounded-full bg-slate-100 px-0 py-1.5 text-xs font-medium text-slate-500 opacity-0 transition-[max-width,padding,opacity,background-color,color] duration-200 group-hover/profil:max-w-28 group-hover/profil:bg-white group-hover/profil:px-2.5 group-hover/profil:text-slate-900 group-hover/profil:opacity-100 group-focus-visible/profil:max-w-28 group-focus-visible/profil:bg-white group-focus-visible/profil:px-2.5 group-focus-visible/profil:text-slate-900 group-focus-visible/profil:opacity-100"
>
<SquarePen className="size-3 shrink-0" aria-hidden />
Değiştir
</span>
</button>
) : (
<>
<button
type="button"
onClick={siralamaGirVeyaSonucaGit}
className="flex cursor-pointer items-center gap-2 rounded-full px-4 py-1.5 text-slate-700 transition-colors duration-200 hover:bg-slate-100 hover:text-slate-900"
>
<Trophy className="size-4 text-orange-500" aria-hidden />
Sıralamanı gir
</button>
<Link
href="/#nasil-calisir"
className="rounded-full px-5 py-2 transition-colors duration-200 hover:bg-slate-100 hover:text-slate-900"
>
Nasıl çalışır?
</Link>
<Link
href="/#karsilastirma"
className="rounded-full px-5 py-2 transition-colors duration-200 hover:bg-slate-100 hover:text-slate-900"
>
Karşılaştır
</Link>
</>
)}
<button
type="button"
onClick={() => setAcik(true)}
onClick={() => modalDegisti(true)}
className="flex cursor-pointer items-center gap-2 rounded-full bg-slate-900 px-5 py-2 text-white transition-[background-color,transform] duration-150 hover:bg-slate-700 active:scale-[0.97]"
>
<Search className="size-4" aria-hidden />
@@ -231,14 +267,50 @@ export function KatalogArama({ rehberler }: { rehberler: RehberOzet[] }) {
</button>
</nav>
<button
type="button"
onClick={() => setAcik(true)}
aria-label="Bölüm, üniversite veya rehber ara"
className="ml-auto flex size-9 shrink-0 cursor-pointer items-center justify-center rounded-full border border-slate-200 bg-white text-slate-700 transition-[background-color,transform] duration-150 hover:bg-slate-100 active:scale-[0.96] sm:size-12 lg:hidden"
>
<Search className="size-4" aria-hidden />
</button>
{/* lg altında ortadaki nav gizli; sıralama + ara burada yaşar */}
<div className="ml-auto flex items-center gap-1.5 lg:hidden">
{profil ? (
<button
type="button"
onClick={profilDuzenlemeyiAc}
aria-label={`Sıralamanı ve tercihlerini değiştir (${profil.sira.toLocaleString("tr-TR")})`}
className="flex h-9 max-w-40 cursor-pointer items-center gap-1.5 rounded-full border border-slate-200 bg-white py-1 pl-2.5 pr-1.5 text-xs font-semibold text-slate-900 transition-[background-color,transform] duration-150 hover:bg-slate-100 active:scale-[0.96] sm:h-12 sm:max-w-none sm:gap-2 sm:pl-3.5 sm:pr-2 sm:text-sm"
>
<Trophy
className="size-3.5 shrink-0 text-orange-500 sm:size-4"
aria-hidden
/>
<span className="truncate tabular-nums tracking-tight">
{profil.sira.toLocaleString("tr-TR")}
</span>
<span className="inline-flex items-center gap-0.5 rounded-full bg-slate-100 px-1.5 py-0.5 text-[10px] font-medium text-slate-500 sm:text-xs">
<SquarePen className="size-2.5 sm:size-3" aria-hidden />
Değiştir
</span>
</button>
) : (
<button
type="button"
onClick={siralamaGirVeyaSonucaGit}
aria-label="Başarı sıralamanı gir"
className="flex h-9 cursor-pointer items-center gap-1.5 rounded-full border border-slate-200 bg-white py-1 pl-2.5 pr-2.5 text-xs font-semibold text-slate-900 transition-[background-color,transform] duration-150 hover:bg-slate-100 active:scale-[0.96] sm:h-12 sm:gap-2 sm:pl-3.5 sm:pr-3 sm:text-sm"
>
<Trophy
className="size-3.5 shrink-0 text-orange-500 sm:size-4"
aria-hidden
/>
<span className="truncate">Sıralama gir</span>
</button>
)}
<button
type="button"
onClick={() => modalDegisti(true)}
aria-label="Bölüm, üniversite veya rehber ara"
className="flex size-9 shrink-0 cursor-pointer items-center justify-center rounded-full border border-slate-200 bg-white text-slate-700 transition-[background-color,transform] duration-150 hover:bg-slate-100 active:scale-[0.96] sm:size-12"
>
<Search className="size-4" aria-hidden />
</button>
</div>
<Dialog open={acik} onOpenChange={modalDegisti}>
<DialogContent
@@ -423,71 +495,127 @@ export function KatalogArama({ rehberler }: { rehberler: RehberOzet[] }) {
function BaslangicIcerigi({ onSec }: { onSec: () => void }) {
return (
<div>
<p className="px-2 pb-2 text-xs font-semibold uppercase tracking-wider text-slate-400">
<p className="px-1 pb-2.5 text-[11px] font-semibold uppercase tracking-[0.14em] text-slate-400">
Keşfet
</p>
<div className="grid gap-2 sm:grid-cols-3">
<div className="grid grid-cols-3 gap-2">
<KesfetLinki
href="/bolumler"
ikon={<GraduationCap className="size-5" aria-hidden />}
ikon={GraduationCap}
baslik="Tüm bölümler"
aciklama="Lisans ve önlisans"
kisaBaslik="Bölümler"
aciklama="Lisans · önlisans"
ton="slate"
onSec={onSec}
/>
<KesfetLinki
href="/universiteler"
ikon={<Building2 className="size-5" aria-hidden />}
ikon={Building2}
baslik="Üniversiteler"
aciklama="Devlet ve vakıf"
kisaBaslik="Üniversite"
aciklama="Devlet · vakıf"
ton="sky"
onSec={onSec}
/>
<KesfetLinki
href="/rehber"
ikon={<BookOpenText className="size-5" aria-hidden />}
ikon={BookOpenText}
baslik="Tercih rehberi"
aciklama="Sorularına yanıtlar"
kisaBaslik="Rehber"
aciklama="Sorularına yanıt"
ton="amber"
onSec={onSec}
/>
</div>
<div className="mt-6 rounded-xl border border-orange-100 bg-orange-50/70 p-4">
<p className="text-sm font-medium text-slate-900">
Neyi merak ediyorsun?
</p>
<p className="mt-1 text-sm leading-6 text-slate-600">
Psikoloji veya Boğaziçi gibi bir ad yaz; eşleşen sayfaları ve
temel bilgilerini burada önizle.
</p>
<div className="mt-5 flex gap-3 rounded-2xl bg-slate-50 px-3.5 py-3.5 sm:px-4">
<span className="mt-0.5 flex size-8 shrink-0 items-center justify-center rounded-full bg-white text-slate-400 shadow-[0_0_0_1px_rgba(15,23,42,0.06)]">
<Search className="size-3.5" aria-hidden />
</span>
<div className="min-w-0">
<p className="text-sm font-semibold tracking-tight text-slate-900">
Neyi merak ediyorsun?
</p>
<p className="mt-0.5 text-xs leading-5 text-slate-500 sm:text-sm sm:leading-6">
Psikoloji veya Boğaziçi yaz; eşleşen sayfaları ve temel
bilgilerini burada önizle.
</p>
</div>
</div>
<AramaFunnelKarti onSec={onSec} />
</div>
);
}
function KesfetLinki({
href,
ikon,
ikon: Ikon,
baslik,
kisaBaslik,
aciklama,
ton,
onSec,
}: {
href: string;
ikon: React.ReactNode;
ikon: typeof GraduationCap;
baslik: string;
kisaBaslik: string;
aciklama: string;
ton: keyof typeof KESFET_TONLARI;
onSec: () => void;
}) {
const palet = KESFET_TONLARI[ton];
return (
<Link
href={href}
onClick={onSec}
className="group rounded-xl border border-slate-200 bg-white p-4 transition-[border-color,background-color,transform] duration-150 hover:border-slate-300 hover:bg-slate-50 active:scale-[0.98]"
className="group/kesfet flex cursor-pointer flex-col overflow-hidden rounded-2xl border border-slate-200 bg-white transition-transform duration-150 ease-out active:scale-[0.97]"
>
<span className="flex size-9 items-center justify-center rounded-lg bg-slate-100 text-slate-600 transition-colors group-hover:bg-white">
{ikon}
{/* Mini tarayıcı — sekme içinde tek büyük ikon, alta doğru maske ile solar */}
<div
aria-hidden
className="relative h-18 overflow-hidden bg-slate-50/80 sm:h-22"
>
<div
className={`absolute inset-x-2 -bottom-3 top-2 flex flex-col overflow-hidden rounded-t-xl border border-b-0 sm:inset-x-2.5 sm:top-2.5 ${palet.pencere}`}
>
<div className="flex h-3.5 shrink-0 items-center gap-1 border-b border-slate-100/90 bg-slate-50/90 px-2">
<span className={`size-1.5 rounded-full ${palet.nokta}`} />
<span className={`size-1.5 rounded-full ${palet.nokta} opacity-65`} />
<span className={`size-1.5 rounded-full ${palet.nokta} opacity-35`} />
<span className="ml-1.5 h-1.5 max-w-[55%] flex-1 rounded-full bg-white shadow-[0_0_0_1px_rgba(15,23,42,0.06)]" />
</div>
<div className="relative min-h-0 flex-1 overflow-hidden">
<div className="absolute inset-x-0 top-0 flex flex-col items-center gap-1.5 pt-2 sm:gap-2 sm:pt-2.5">
<span
className={`flex size-9 items-center justify-center rounded-xl border shadow-[0_6px_12px_-6px_rgba(15,23,42,0.18)] transition-transform duration-200 ease-out group-hover/kesfet:-translate-y-0.5 sm:size-10 ${palet.rozet}`}
>
<Ikon className="size-4.5 sm:size-5" strokeWidth={1.75} />
</span>
<span className={`h-1.5 w-2/5 rounded-full ${palet.cizgi}`} />
<span className="h-1.5 w-1/4 rounded-full bg-slate-100" />
</div>
<ProgressiveBlur
position="bottom"
backgroundColor="#ffffff"
height="45%"
blurAmount="2px"
/>
</div>
</div>
<div className="pointer-events-none absolute inset-x-0 bottom-0 h-6 bg-linear-to-t from-white to-transparent" />
</div>
<span className="min-w-0 px-2.5 pb-3 pt-2 text-center sm:px-3.5 sm:pb-3.5 sm:pt-2.5 sm:text-left">
<span className="block text-[12px] font-semibold leading-tight tracking-tight text-slate-900 sm:text-sm">
<span className="sm:hidden">{kisaBaslik}</span>
<span className="hidden sm:inline">{baslik}</span>
</span>
<span className="mt-0.5 block text-[10px] leading-snug text-slate-500 sm:text-xs">
{aciklama}
</span>
</span>
<span className="mt-3 block text-sm font-semibold text-slate-900">
{baslik}
</span>
<span className="mt-0.5 block text-xs text-slate-500">{aciklama}</span>
</Link>
);
}