diff --git a/src/features/kullanici/components/giris-linki.tsx b/src/features/kullanici/components/giris-linki.tsx new file mode 100644 index 0000000..ccdbc75 --- /dev/null +++ b/src/features/kullanici/components/giris-linki.tsx @@ -0,0 +1,52 @@ +"use client"; + +import Link from "next/link"; +import { usePathname } from "next/navigation"; +import { Button } from "@/components/ui/button"; + +/** + * Navbar'ın "Giriş yap" butonu: giriş bitince kullanıcıyı **bulunduğu + * sayfaya** geri döndürür (`/giris?callback=`). Callback verilmezse + * `giris-karti.tsx` "/"'a düşürüyordu — rehber okuyan ziyaretçi girişten + * sonra ana sayfada uyanıyordu. + * + * Callback neden sabit bir hedef (ör. `/listem`) değil: buton her sayfada + * duruyor, sabit hedef bağlam kaybı olurdu; `/listem?uret=1` yazılsaydı + * girişten dönüşte üretim kendiliğinden başlar ve kullanıcının ayrı onayı + * olmadan 3 kredi yakardı. + * + * Callback yalnız pathname taşır: sorgu dizesini okuyan kanca tüm navbar'ı + * Suspense zorunluluğuna sokardı. `usePathname` bu ağaçta zaten var + * (`header-gate`), `UserNav` da site-header'da Suspense içinde render + * ediliyor — cacheComponents açıkken dinamik parametreli rotalarda gereken + * sınır hazır (next/dist/docs/.../use-pathname.md). + */ +/** + * better-auth (1.6.23) callback'i kendi göreli-yol kalkanından geçiriyor: + * `^\/(?!\/|\\|%2f|%5c)[\w\-.+/@]*(?:\?…)?$`. `\w` ASCII olduğu için + * `/rehber/ösym` ya da yüzde-kodlanmış `/bolum/t%C4%B1p` gibi yollar + * **403 INVALID_CALLBACK_URL** ile reddediliyor ve kullanıcı o sayfadan hiç + * giriş yapamıyor (main'de callback hep "/" olduğu için bu yol yoktu). + * Bu yüzden callback'i ancak auth katmanının da kabul edeceği yollarda + * takıyoruz; gerisinde sade /giris'e düşüyoruz — bağlam kaybı, kırık giriş + * akışına yeğdir. Kalkan daraltılırsa (tek yer) buradaki de gevşetilebilir. + */ +const AUTH_GUVENLI_YOL = /^\/(?!\/|\\)[\w\-./+@]*$/; + +export function GirisLinki() { + const pathname = usePathname(); + // /giris kendine callback veremez (döngü); pathname yoksa sade hedef. + const href = + pathname && !pathname.startsWith("/giris") && AUTH_GUVENLI_YOL.test(pathname) + ? `/giris?callback=${encodeURIComponent(pathname)}` + : "/giris"; + + return ( + + ); +} diff --git a/src/features/kullanici/components/user-nav.tsx b/src/features/kullanici/components/user-nav.tsx index 9a45188..b58f107 100644 --- a/src/features/kullanici/components/user-nav.tsx +++ b/src/features/kullanici/components/user-nav.tsx @@ -1,10 +1,10 @@ import Link from "next/link"; -import { Coins } from "lucide-react"; +import { ClipboardList, Coins } from "lucide-react"; import { getCurrentUser } from "@/lib/session"; -import { Button } from "@/components/ui/button"; import { ListemButonu } from "@/features/liste/components/listem-butonu"; import { ListeSenkron } from "@/features/liste/components/liste-senkron"; import { RybbitIdentify } from "@/components/rybbit-identify"; +import { GirisLinki } from "./giris-linki"; import { SignOutButton } from "./sign-out-button"; /** @@ -32,12 +32,7 @@ export async function UserNav() { - + ); } @@ -47,6 +42,24 @@ export async function UserNav() { + {/* Yapay Zeka raporunun kalıcı kapısı: girişli kullanıcı /listem'i + navbar'dan bulamıyordu (çekmece butonu manuel liste boşken hiç + görünmüyor). Rapor var mı diye DB'ye sorulmuyor — navbar'ın TTFB'si + bozulmasın; liste boşsa sayfadaki ListemBosCta karşılıyor. + Sınıf seti kredi pill'iyle birebir aynı (tek kaynak). */} + + + {/* ListemButonu:39 ile aynı görünürlük deseni: 375 px'te ve lg–xl + arasında metin gizli, ikon+aria-label anlamı taşıyor. */} + + Yapay Zeka Listem + + - + ) : ( + + + )} ); }