From 2278d2a218770f6a1f4163290803c4ddd72ffa46 Mon Sep 17 00:00:00 2001 From: bilalgursen Date: Sun, 2 Aug 2026 21:10:43 +0300 Subject: [PATCH] Update app.db, enhance Home component with social proof feature, and improve error handling in auth module - Updated app.db to reflect recent changes. - Added a Suspense-wrapped SosyalKanitBandi component in the Home page for improved user engagement. - Refactored the sendMagicLinkEmail function in auth.ts to handle errors more effectively, ensuring users receive appropriate feedback on email sending issues. - Removed the Rybbit live visitor widget from the site footer for a cleaner design. --- data/app.db | Bin 155648 -> 155648 bytes src/app/api/sosyal-kanit/route.ts | 15 ++ src/app/page.tsx | 39 +++-- src/components/landing/sosyal-kanit-govde.tsx | 155 ++++++++++++++++++ src/components/landing/sosyal-kanit.tsx | 17 ++ src/components/site-footer.tsx | 11 -- src/lib/auth.ts | 9 +- src/lib/sosyal-kanit-sabitler.ts | 33 ++++ src/lib/sosyal-kanit.ts | 105 ++++++++++++ 9 files changed, 360 insertions(+), 24 deletions(-) create mode 100644 src/app/api/sosyal-kanit/route.ts create mode 100644 src/components/landing/sosyal-kanit-govde.tsx create mode 100644 src/components/landing/sosyal-kanit.tsx create mode 100644 src/lib/sosyal-kanit-sabitler.ts create mode 100644 src/lib/sosyal-kanit.ts diff --git a/data/app.db b/data/app.db index abb867e365c4a46ffb566f553ce2a854438b4801..5cfc41e2430f17a9daf15faf2909d21326ff5da3 100644 GIT binary patch delta 545 zcmZoTz}awsbAmLZ%0wAwMwP~dtqF`v_!(KZvkEYpaxyV#ZMTtRJi#W!%6F52KbLlo=A;o?5srIt}O zB)?^OR(^9e2$wTKP0lh%H?PP`&j_pX_cb+k^-WC8%?K{Ev?$0ZcPYxxaV$>s%S}s4 z&QGfH3C#`-a4gGkiptz6Qn?{yY_CUq(Wq!wgbH$fHck%)Xq2+`Mc8 G;{^at6s)rV delta 592 zcmaixyKCD}6o-9d$Hak3NuWb$2O~G3L)!SQ#MBLum0Ett@*{pEOC!mWb#3{9bXD6e z!EPvYO}&^Fx|RL|p=b?s>DX1!$$v=5P1`*O4jc~Ofgk6aUi+rkzRO3z|LJiG_}`!X zI0N5E;I)_6K+LxU9@;N9!7uNImvCHUmzk9ujYp0%`B4X(h^Djy!wd_&Ot1vaAA z#-cLL6pad3fjNVT30Y;>*P?GS5i5n59igN%d@h0}b*W4r4-B?y>ZQDr z*4T36bqhyQMgwRl<{_dIA$l(sk3>Vkcr+T`q57hQv6RD`|cC+{Sy4X1t|TwyZ`_I diff --git a/src/app/api/sosyal-kanit/route.ts b/src/app/api/sosyal-kanit/route.ts new file mode 100644 index 0000000..5dac81d --- /dev/null +++ b/src/app/api/sosyal-kanit/route.ts @@ -0,0 +1,15 @@ +import { NextResponse } from "next/server"; +import { sosyalKanitVerisi } from "@/lib/sosyal-kanit"; + +// Fiyat bölümündeki şeridin canlı sayıyı tazelemek için yokladığı uç. +// İlk değer sunucudan HTML ile geliyor (bkz. components/landing/sosyal-kanit.tsx); +// burası yalnızca sayfa açık kaldıkça gelen güncellemeler için. +// Sayılar sosyal kanıt, gizli veri değil — kimlik doğrulaması yok. +// Yük: sosyalKanitVerisi kendi içinde TTL önbelleğine sahip, yoklama +// sıklığı DB/Rybbit'e yansımaz. +export async function GET() { + const veri = await sosyalKanitVerisi(); + return NextResponse.json(veri, { + headers: { "Cache-Control": "no-store" }, + }); +} diff --git a/src/app/page.tsx b/src/app/page.tsx index 74af976..802c830 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -16,11 +16,13 @@ import { } from "lucide-react"; import Image from "next/image"; import Link from "next/link"; +import { Suspense } from "react"; import { HeroBaslik } from "@/components/hero-baslik"; import { KapanisCta } from "@/components/hero-focus-button"; import { HeroForm } from "@/components/hero-form"; import { Parallax } from "@/components/parallax"; import { Reveal } from "@/components/landing/reveal"; +import { SosyalKanitBandi } from "@/components/landing/sosyal-kanit"; import { TurkeyPixelMap } from "@/components/turkey-pixel-map"; import { PixelCorner, @@ -528,18 +530,31 @@ export default function Home() { ))} - -

- Temel program arama herkes için ücretsiz · iyzico güvenli ödeme -

+ + {/* Boşluk sarmalayıcıda: sosyal kanıt eşik altında kalıp hiç + çizilmediğinde buton yine listeden mt-8 uzakta durur. */} +
+ {/* Karar butonun başında veriliyor; kanıt da orada dursun. + İstek anında okunuyor, o yüzden Suspense içinde. */} + + + + + + +

+ Temel program arama herkes için ücretsiz · iyzico güvenli + ödeme +

+
) { + // null = animasyon çalışmıyor → güncel hedef doğrudan gösterilir. Böylece + // yoklama sayıyı tazelediğinde sayaç baştan oynamaz, sadece değer değişir. + const [animasyon, setAnimasyon] = useState(null); + const hedefRef = useRef(hedef); + + useEffect(() => { + hedefRef.current = hedef; + }, [hedef]); + + useEffect(() => { + const el = ref.current; + if (!el) return; + if ( + typeof IntersectionObserver === "undefined" || + window.matchMedia("(prefers-reduced-motion: reduce)").matches + ) { + return; + } + + let cerceve = 0; + const io = new IntersectionObserver( + (girisler) => { + if (!girisler.some((g) => g.isIntersecting)) return; + io.disconnect(); + const son = hedefRef.current; + const basla = performance.now(); + const adim = (simdi: number) => { + const t = Math.min(1, (simdi - basla) / SAYAC_MS); + if (t < 1) { + // easeOutCubic: hızlı başlar, son sayıya yumuşak oturur + setAnimasyon(Math.round(son * (1 - Math.pow(1 - t, 3)))); + cerceve = requestAnimationFrame(adim); + } else { + setAnimasyon(null); + } + }; + setAnimasyon(0); + cerceve = requestAnimationFrame(adim); + }, + { rootMargin: "0px 0px -60px 0px" }, + ); + io.observe(el); + return () => { + io.disconnect(); + cancelAnimationFrame(cerceve); + }; + }, [ref]); + + return animasyon ?? hedef; +} + +export function SosyalKanitGovde({ ilk }: { ilk: SosyalKanitVerisi }) { + const [veri, setVeri] = useState(ilk); + const ref = useRef(null); + const gosterilenListe = useSayac(veri.liste, ref); + + useEffect(() => { + // Sekme arkadayken yoklama yok: kimse bakmıyorken sunucuyu meşgul etme. + let iptal = false; + let zamanlayici: ReturnType | undefined; + + async function tazele() { + try { + const cevap = await fetch("/api/sosyal-kanit", { cache: "no-store" }); + if (!cevap.ok) return; + const yeni = (await cevap.json()) as SosyalKanitVerisi; + if (!iptal) setVeri(yeni); + } catch { + // Sessiz: ekrandaki son sayı kalır. + } + } + + function planla() { + clearTimeout(zamanlayici); + if (document.visibilityState !== "visible") return; + zamanlayici = setTimeout(() => { + void tazele().then(planla); + }, YOKLAMA_MS); + } + + function gorunurlukDegisti() { + if (document.visibilityState === "visible") void tazele(); + planla(); + } + + planla(); + document.addEventListener("visibilitychange", gorunurlukDegisti); + return () => { + iptal = true; + clearTimeout(zamanlayici); + document.removeEventListener("visibilitychange", gorunurlukDegisti); + }; + }, []); + + if (!esikGecti(veri.liste, LISTE_ESIGI)) return null; + + const canliVar = veri.canli !== null && esikGecti(veri.canli, CANLI_ESIGI); + + return ( +
+

+ + {sayi(gosterilenListe)} + + + öğrenci listesini KolayTercih ile oluşturdu + +

+ + {canliVar ? ( +

+ + + + + + şu anda{" "} + + {sayi(veri.canli as number)} + {" "} + kişi KolayTercih ile tercihini planlıyor + +

+ ) : null} +
+ ); +} diff --git a/src/components/landing/sosyal-kanit.tsx b/src/components/landing/sosyal-kanit.tsx new file mode 100644 index 0000000..f31d382 --- /dev/null +++ b/src/components/landing/sosyal-kanit.tsx @@ -0,0 +1,17 @@ +import { SosyalKanitGovde } from "@/components/landing/sosyal-kanit-govde"; +import { LISTE_ESIGI, esikGecti } from "@/lib/sosyal-kanit-sabitler"; +import { sosyalKanitVerisi } from "@/lib/sosyal-kanit"; + +/** + * Fiyat bölümünün üstündeki sosyal kanıt şeridi: kaç öğrenci listesini + * oluşturdu + şu anda kaç kişi sitede. + * + * Dinamik (istek anında okunur) olduğu için çağıran tarafta bir `Suspense` + * sınırının içinde durmalı. Fallback bilerek boş: eşik altındayken şerit hiç + * çizilmiyor, yer ayıran bir iskelet o durumda boşluk bırakırdı. + */ +export async function SosyalKanitBandi() { + const veri = await sosyalKanitVerisi(); + if (!esikGecti(veri.liste, LISTE_ESIGI)) return null; + return ; +} diff --git a/src/components/site-footer.tsx b/src/components/site-footer.tsx index a975287..756e46f 100644 --- a/src/components/site-footer.tsx +++ b/src/components/site-footer.tsx @@ -155,17 +155,6 @@ export function SiteFooter() { İletişim - - {/* Rybbit canlı ziyaretçi rozeti — sola yaslı */} -
-