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

@@ -3,22 +3,24 @@
import { useEffect, useRef, useState, useSyncExternalStore } from "react";
import Link from "next/link";
import { useRouter } from "next/navigation";
import { Sparkles, X } from "lucide-react";
import { ArrowRight, X } from "lucide-react";
import { animate } from "motion";
import { AnimatePresence, motion, useReducedMotion } from "motion/react";
import { toast } from "sonner";
import { Button } from "@/components/ui/button";
import { ViewportPortal } from "@/components/viewport-portal";
import { PixelField } from "@/components/pixel-decor";
import type { SihirbazFacetleri } from "@/lib/db";
import {
SIHIRBAZ_AC_EVENT,
SIHIRBAZ_STORAGE_KEY,
sihirbazDogrula,
sonucHref,
tercihProfiliSil,
tercihProfiliYaz,
type SihirbazSecimleri,
} from "@/lib/sihirbaz";
import { SihirbazModal } from "./sihirbaz-modal";
import { SIHIRBAZ_AC_EVENT } from "./funnel-banner";
import { ListePaneli, type MevcutRapor, type PanelDurum } from "./liste-paneli";
import { listeOlustur, listeRevize } from "./actions";
@@ -86,6 +88,9 @@ export function SihirbazBolumu({
const [elleKapandi, setElleKapandi] = useState(false);
const [kartGizle, setKartGizle] = useState(false);
const [bekleyen, setBekleyen] = useState<BekleyenSecim | null>(null);
// ?sihirbaz=1 fallback modalı storage okunmadan açılmasın: hero'dan gelen
// kayıtlı seçimler varken modal bir anlığına açılıp kapanıyordu.
const [depoOkundu, setDepoOkundu] = useState(false);
const [rapor, setRapor] = useState<MevcutRapor | null>(mevcutRapor);
const [kredi, setKredi] = useState(baslangicKredi);
@@ -101,6 +106,9 @@ export function SihirbazBolumu({
const requestIdRef = useRef<string | null>(null);
const otomatikBasladi = useRef(false);
const bolumRef = useRef<HTMLDivElement>(null);
// Funnel CTA'ları (banner olayı dahil) tıklama anındaki bekleyen seçimi
// görsün diye ref'te tutulur; olay dinleyicisi bir kez bağlanır.
const bekleyenRef = useRef<BekleyenSecim | null>(null);
// Mount sonrası storage'dan bekleyen seçimleri + kalıcı kart kapatmayı oku
useEffect(() => {
@@ -124,18 +132,20 @@ export function SihirbazBolumu({
}
// eslint-disable-next-line react-hooks/set-state-in-effect
if (kaliciKapali) setKartGizle(true);
if (yeniBekleyen) setBekleyen(yeniBekleyen);
if (yeniBekleyen) {
setBekleyen(yeniBekleyen);
bekleyenRef.current = yeniBekleyen;
}
} catch {}
setDepoOkundu(true);
}, [sira, tur]);
// Üstteki funnel banner'ının CTA'sı sihirbaz modalını bu olayla açar
// Arama modalındaki funnel kartının CTA'sı sihirbazı bu olayla başlatır
useEffect(() => {
const ac = () => {
setElleAcik(true);
setElleKapandi(false);
};
const ac = () => sihirbaziBaslat();
window.addEventListener(SIHIRBAZ_AC_EVENT, ac);
return () => window.removeEventListener(SIHIRBAZ_AC_EVENT, ac);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
/**
@@ -241,7 +251,7 @@ export function SihirbazBolumu({
function secimleriKaydetVeGirise(secimler: SihirbazSecimleri) {
tercihProfiliYaz({ sira, tur, secimler });
const geri = `/sonuc?sira=${sira}&tur=${tur}&sihirbaz=1`;
const geri = sonucHref({ sira, tur }, { sihirbaz: "1" });
router.push(`/giris?callback=${encodeURIComponent(geri)}`);
}
@@ -255,6 +265,22 @@ export function SihirbazBolumu({
await uret(secimler);
}
/**
* Funnel CTA'larının (banner, büyük kart, sabit alt kart) ortak girişi:
* adımlar zaten tamamlanıp kaydedildiyse modal açılmaz — girişli
* kullanıcıda üretim doğrudan başlar, girişsiz kullanıcı girişe gider.
* Kayıtlı seçim yoksa (ya da başka sıra/türe aitse) modal açılır.
*/
function sihirbaziBaslat() {
const hazir = bekleyenRef.current;
if (hazir?.eslesir) {
void ilerle(hazir.secimler);
return;
}
setElleAcik(true);
setElleKapandi(false);
}
// Giriş dönüşü: storage'daki eşleşen seçimlerle otomatik üretime başla
// (bir kez, modal açmadan — üretim sayfa gövdesinde oynar).
const otomatikUret = Boolean(bekleyen?.eslesir && girisliMi && !rapor);
@@ -268,11 +294,15 @@ export function SihirbazBolumu({
}, [otomatikUret]);
// ?sihirbaz=1: otomatik üretim devreye girmediyse (seçim kaybolduysa/
// eşleşmiyorsa) modalı aç ki kullanıcı adımları tamamlayabilsin.
// eşleşmiyorsa) modalı aç ki kullanıcı adımları tamamlayabilsin. Karar
// storage okunmadan verilmez: kayıtlı seçimlerle gelen kullanıcıya modal
// hiç gösterilmez, üretim doğrudan başlar.
const acik =
!elleKapandi &&
(elleAcik ||
Boolean(otomatikAc && !otomatikUret && !rapor && durum === "bos"));
Boolean(
otomatikAc && depoOkundu && !otomatikUret && !rapor && durum === "bos",
));
function acikDegisti(v: boolean) {
if (v) {
@@ -293,7 +323,7 @@ export function SihirbazBolumu({
// mount sonrası değişir ve AnimatePresence geçişi donardı.
const girisBekliyor = Boolean(!girisliMi && secimlerHazir && !rapor);
const girisUrl = `/giris?callback=${encodeURIComponent(
`/sonuc?sira=${sira}&tur=${tur}&sihirbaz=1`,
sonucHref({ sira, tur }, { sihirbaz: "1" }),
)}`;
const panelAnahtari =
@@ -393,11 +423,17 @@ export function SihirbazBolumu({
</Button>
</section>
) : girisBekliyor ? (
<section className="mt-16 min-w-0 rounded-2xl bg-primary px-5 py-8 text-center text-white sm:p-12">
<h2 className="font-heading text-2xl font-bold sm:text-3xl">
<section className="relative isolate mt-16 min-w-0 overflow-hidden rounded-2xl bg-primary px-5 py-8 text-center text-white sm:p-12">
<div
aria-hidden
className="pointer-events-none absolute inset-x-0 -inset-y-10 -z-10 text-white"
>
<PixelField seed={13} />
</div>
<h2 className="relative font-heading text-2xl font-bold sm:text-3xl">
Seçimlerin hazır AI listen bir adım uzakta
</h2>
<p className="mx-auto mt-3 max-w-xl text-white/90">
<p className="relative mx-auto mt-3 max-w-xl text-white/90">
Yukarıdaki tablo her zaman ücretsiz. 5 deneme kredinle 24
tercihlik AI listeni de kurabilirsin: ücretsiz giriş yap,
seçimlerin kayıtlı listen otomatik kurulsun (üretim 3
@@ -406,34 +442,41 @@ export function SihirbazBolumu({
<Button
asChild
size="lg"
className="mt-6 h-auto min-h-12 w-full cursor-pointer whitespace-normal bg-orange-500 px-4 py-3 text-white transition-colors duration-200 hover:bg-orange-600 sm:w-auto sm:px-8"
className="relative mt-6 h-auto min-h-12 w-full cursor-pointer whitespace-normal bg-orange-500 px-4 py-3 text-white transition-colors duration-200 hover:bg-orange-600 sm:w-auto sm:px-8"
>
<Link href={girisUrl}>
<Sparkles className="size-4" aria-hidden />5 deneme kredinle
AI listeni kur giriş yap
5 deneme kredinle AI listeni kur giriş yap
<ArrowRight className="size-4" aria-hidden />
</Link>
</Button>
<p className="mt-3 text-xs text-white/70">
<p className="relative mt-3 text-xs text-white/70">
Kredi kartı gerekmez · Google ya da e-posta ile 30 saniyede
</p>
</section>
) : (
<section className="mt-16 min-w-0 rounded-2xl bg-primary px-5 py-8 text-center text-white sm:p-12">
<h2 className="font-heading text-2xl font-bold sm:text-3xl">
Bu listeyi 24 tercihlik plana çevirelim mi?
<section className="relative isolate mt-16 min-w-0 overflow-hidden rounded-2xl bg-primary px-5 py-8 text-center text-white sm:p-12">
<div
aria-hidden
className="pointer-events-none absolute inset-x-0 -inset-y-10 -z-10 text-white"
>
<PixelField seed={19} />
</div>
<h2 className="relative font-heading text-2xl font-bold sm:text-3xl">
24 tercihi tek tek elemek yerine, yapay zekâ kursun
</h2>
<p className="mx-auto mt-3 max-w-xl text-white/90">
Birkaç adımda ilgi alanların ve şehir tercihine göre
gerekçeli, dengeli bir tercih listesi kuralım her satır için
yerleşme riski analiziyle.
<p className="relative mx-auto mt-3 max-w-xl text-white/90">
Tablodan elle eklemek her zaman serbest. Ama üç adımda ilgi
alanlarını ve şehirlerini söylersen, yapay zekâ hayal, dengeli
ve garanti dilimlerine dağıtılmış 24 tercihi her satırı
yerleşme riski ve gerekçesiyle senin yerine dizer.
</p>
<Button
size="lg"
onClick={() => acikDegisti(true)}
className="mt-6 h-auto min-h-12 w-full cursor-pointer whitespace-normal bg-orange-500 px-4 py-3 text-white transition-colors duration-200 hover:bg-orange-600 sm:w-auto sm:px-8"
onClick={sihirbaziBaslat}
className="relative mt-6 h-auto min-h-12 w-full cursor-pointer whitespace-normal bg-orange-500 px-4 py-3 text-white transition-colors duration-200 hover:bg-orange-600 sm:w-auto sm:px-8"
>
<Sparkles className="size-4" aria-hidden />
Listemi oluştur
AI listemi kur
<ArrowRight className="size-4" aria-hidden />
</Button>
</section>
)}
@@ -450,12 +493,12 @@ export function SihirbazBolumu({
<p className="font-heading text-sm font-bold text-slate-900">
{girisBekliyor
? "Seçimlerin kayıtlı — AI listen hazır bekliyor"
: "3 adımda 24 tercihlik listen"}
: "Tek tek ekleme, yapay zekâ kursun"}
</p>
<p className="text-xs text-slate-500">
{girisBekliyor
? "Ücretsiz giriş yap, 5 deneme kredinle listen otomatik kurulsun."
: "Sıralamana uygun bölümleri seç, yapay zekâ listeni kursun."}
: "Üç adımda seçimlerini söyle; risk analizli, gerekçeli 24 tercih hazır."}
</p>
</div>
{girisBekliyor ? (
@@ -469,7 +512,7 @@ export function SihirbazBolumu({
) : (
<Button
size="sm"
onClick={() => acikDegisti(true)}
onClick={sihirbaziBaslat}
className="cursor-pointer bg-orange-500 text-white transition-colors duration-200 hover:bg-orange-600"
>
Başla
@@ -506,7 +549,7 @@ export function SihirbazBolumu({
sonSecimler ?? (bekleyen?.eslesir ? bekleyen.secimler : null)
}
sonButonEtiketi={
girisliMi ? "Listemi oluştur (3 kredi)" : "Giriş yap ve listeni gör"
girisliMi ? "AI listemi kur (3 kredi)" : "Giriş yap ve listeni gör"
}
onTamamla={ilerle}
/>