merge(funnel/listem-girisi): fix(giris): callback yalnız better-auth'un kabul ettiği yollarda takılır
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
52
src/features/kullanici/components/giris-linki.tsx
Normal file
52
src/features/kullanici/components/giris-linki.tsx
Normal file
@@ -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=<yol>`). 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 (
|
||||||
|
<Button
|
||||||
|
asChild
|
||||||
|
className="h-9 cursor-pointer rounded-full bg-slate-900 px-3.5 text-sm font-medium text-white transition-colors duration-200 hover:bg-slate-700 sm:h-12 sm:px-7 sm:text-base"
|
||||||
|
>
|
||||||
|
<Link href={href}>Giriş yap</Link>
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { Coins } from "lucide-react";
|
import { ClipboardList, Coins } from "lucide-react";
|
||||||
import { getCurrentUser } from "@/lib/session";
|
import { getCurrentUser } from "@/lib/session";
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import { ListemButonu } from "@/features/liste/components/listem-butonu";
|
import { ListemButonu } from "@/features/liste/components/listem-butonu";
|
||||||
import { ListeSenkron } from "@/features/liste/components/liste-senkron";
|
import { ListeSenkron } from "@/features/liste/components/liste-senkron";
|
||||||
import { RybbitIdentify } from "@/components/rybbit-identify";
|
import { RybbitIdentify } from "@/components/rybbit-identify";
|
||||||
|
import { GirisLinki } from "./giris-linki";
|
||||||
import { SignOutButton } from "./sign-out-button";
|
import { SignOutButton } from "./sign-out-button";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -32,12 +32,7 @@ export async function UserNav() {
|
|||||||
<RybbitIdentify kullaniciId={null} />
|
<RybbitIdentify kullaniciId={null} />
|
||||||
<ListeSenkron userId={null} />
|
<ListeSenkron userId={null} />
|
||||||
<ListemButonu />
|
<ListemButonu />
|
||||||
<Button
|
<GirisLinki />
|
||||||
asChild
|
|
||||||
className="h-9 cursor-pointer rounded-full bg-slate-900 px-3.5 text-sm font-medium text-white transition-colors duration-200 hover:bg-slate-700 sm:h-12 sm:px-7 sm:text-base"
|
|
||||||
>
|
|
||||||
<Link href="/giris">Giriş yap</Link>
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -47,6 +42,24 @@ export async function UserNav() {
|
|||||||
<RybbitIdentify kullaniciId={u.id} eposta={u.email} isim={u.name} />
|
<RybbitIdentify kullaniciId={u.id} eposta={u.email} isim={u.name} />
|
||||||
<ListeSenkron userId={u.id} />
|
<ListeSenkron userId={u.id} />
|
||||||
<ListemButonu />
|
<ListemButonu />
|
||||||
|
{/* 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). */}
|
||||||
|
<Link
|
||||||
|
href="/listem"
|
||||||
|
aria-label="Yapay Zeka Listem"
|
||||||
|
title="Yapay Zeka'nın kurduğu 24 tercihlik listen"
|
||||||
|
className="inline-flex h-9 items-center gap-1.5 whitespace-nowrap rounded-full border border-slate-200 bg-white px-3 text-xs font-semibold text-slate-700 transition-colors duration-200 hover:border-slate-300 hover:bg-slate-50 sm:h-12 sm:gap-2 sm:px-5 sm:text-sm"
|
||||||
|
>
|
||||||
|
<ClipboardList className="size-3.5 text-primary sm:size-4" aria-hidden />
|
||||||
|
{/* 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. */}
|
||||||
|
<span className="hidden sm:inline lg:hidden xl:inline">
|
||||||
|
Yapay Zeka Listem
|
||||||
|
</span>
|
||||||
|
</Link>
|
||||||
<Link
|
<Link
|
||||||
href="/paket?kaynak=nav"
|
href="/paket?kaynak=nav"
|
||||||
className="inline-flex h-9 items-center gap-1.5 whitespace-nowrap rounded-full border border-slate-200 bg-white px-3 text-xs font-semibold text-slate-700 transition-colors duration-200 hover:border-slate-300 hover:bg-slate-50 sm:h-12 sm:gap-2 sm:px-5 sm:text-sm"
|
className="inline-flex h-9 items-center gap-1.5 whitespace-nowrap rounded-full border border-slate-200 bg-white px-3 text-xs font-semibold text-slate-700 transition-colors duration-200 hover:border-slate-300 hover:bg-slate-50 sm:h-12 sm:gap-2 sm:px-5 sm:text-sm"
|
||||||
|
|||||||
@@ -52,6 +52,10 @@ export type KapiTetik =
|
|||||||
| "arti_toast"
|
| "arti_toast"
|
||||||
| "sonuc"
|
| "sonuc"
|
||||||
| "sihirbaz"
|
| "sihirbaz"
|
||||||
|
// Girişli ama listesiz kullanıcının /listem boş ekranındaki CTA'sı. Ayrı
|
||||||
|
// değer, çünkü bu kapı huninin en sonunda açılıyor: "giriş yaptı ama profili
|
||||||
|
// yok" dilimini arama kartı gibi genel yüzeylerden ayırt edebilmek gerek.
|
||||||
|
| "listem_bos"
|
||||||
| "duzenle"
|
| "duzenle"
|
||||||
| "diger";
|
| "diger";
|
||||||
|
|
||||||
|
|||||||
@@ -4,12 +4,20 @@ import Link from "next/link";
|
|||||||
import { ArrowRight } from "lucide-react";
|
import { ArrowRight } from "lucide-react";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { PagePixelDivider } from "@/components/pixel-decor";
|
import { PagePixelDivider } from "@/components/pixel-decor";
|
||||||
import { useTercihProfili } from "@/features/liste/hooks/use-tercih-profili";
|
import {
|
||||||
|
profilToplamaAc,
|
||||||
|
useTercihProfili,
|
||||||
|
} from "@/features/liste/hooks/use-tercih-profili";
|
||||||
|
|
||||||
|
// Her iki dalın butonu da aynı CTA: sınıf seti tek yerde dursun.
|
||||||
|
const CTA_SINIF = "mt-6 cursor-pointer bg-orange-500 text-white hover:bg-orange-600";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* /listem boş hâlinin metni + CTA'sı: seçimleri zaten hazır olan adayı
|
* /listem boş hâlinin metni + CTA'sı: seçimleri zaten hazır olan adayı
|
||||||
* /sonuc üzerinden dolaştırmadan doğrudan üretime (?uret=1) alır; profil
|
* /sonuc üzerinden dolaştırmadan doğrudan üretime (?uret=1) alır; profil
|
||||||
* yoksa ana sayfadaki sıralama formuna yönlendirir.
|
* yoksa kullanıcıyı ana sayfaya atmadan tercih kapısını **burada** açar.
|
||||||
|
* Kapı bitince `profilTamamlandi` "sihirbaz" hedefini /sonuc?sihirbaz=1'e
|
||||||
|
* çevirir, oradan /listem?uret=1'e dönülür — ayrı bir dal yok.
|
||||||
*/
|
*/
|
||||||
export function ListemBosCta() {
|
export function ListemBosCta() {
|
||||||
const profil = useTercihProfili();
|
const profil = useTercihProfili();
|
||||||
@@ -22,16 +30,24 @@ export function ListemBosCta() {
|
|||||||
: "Sıralamanı girip birkaç adımda 24 tercihlik risk analizli listeni kur; sonra burada görüntüleyip Yapay Zeka danışmanla üzerinde konuşabilirsin."}
|
: "Sıralamanı girip birkaç adımda 24 tercihlik risk analizli listeni kur; sonra burada görüntüleyip Yapay Zeka danışmanla üzerinde konuşabilirsin."}
|
||||||
</p>
|
</p>
|
||||||
<PagePixelDivider seed={67} className="mx-auto mt-6" />
|
<PagePixelDivider seed={67} className="mx-auto mt-6" />
|
||||||
<Button
|
{profil ? (
|
||||||
asChild
|
<Button asChild size="lg" className={CTA_SINIF}>
|
||||||
size="lg"
|
<Link href="/listem?uret=1&kaynak=bos_durum">
|
||||||
className="mt-6 cursor-pointer bg-orange-500 text-white hover:bg-orange-600"
|
Yapay Zeka listeni kur
|
||||||
>
|
<ArrowRight className="size-4" aria-hidden />
|
||||||
<Link href={profil ? "/listem?uret=1" : "/"}>
|
</Link>
|
||||||
{profil ? "Yapay Zeka listeni kur" : "Sıralamanı gir, Yapay Zeka listeni kur"}
|
</Button>
|
||||||
|
) : (
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
size="lg"
|
||||||
|
className={CTA_SINIF}
|
||||||
|
onClick={() => profilToplamaAc("sihirbaz", true, "listem_bos")}
|
||||||
|
>
|
||||||
|
Sıralamanı gir, Yapay Zeka listeni kur
|
||||||
<ArrowRight className="size-4" aria-hidden />
|
<ArrowRight className="size-4" aria-hidden />
|
||||||
</Link>
|
</Button>
|
||||||
</Button>
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user