From 8b5aa8c8a6140a0714f789b1532e5e8a98d38207 Mon Sep 17 00:00:00 2001 From: bilalgursen Date: Sat, 8 Aug 2026 01:22:31 +0300 Subject: [PATCH] =?UTF-8?q?refactor:=20kullanici=20feature'=C4=B1=20?= =?UTF-8?q?=E2=80=94=20giri=C5=9F=20kartlar=C4=B1=20feature=20bile=C5=9Fen?= =?UTF-8?q?lerine=20=C3=A7=C4=B1kt=C4=B1=20(Faz=204)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - giris-form, giris-baslik, user-nav, sign-out-button features/kullanici/components/ altına taşındı - /giris'in session+redirect+callback temizliği GirisKarti'ya (async), /giris/dogrula'nın inline doğrulama formu + guvenliYol kalkanı DogrulaKarti'ya çıkarıldı; skeleton'lar aynı dosyada - İki sayfa senkron: searchParams.then() + sayfada Suspense; app/giris/loading.tsx silindi (geometrisi GirisKartiSkeleton'da yaşıyor) - site-header'ın UserNav importu güncellendi; mevcut Suspense sınırı yerinde - Davranış değişikliği yok Co-Authored-By: Claude Fable 5 --- src/app/giris/dogrula/page.tsx | 95 +++----------- src/app/giris/loading.tsx | 39 ------ src/app/giris/page.tsx | 87 ++----------- src/components/site-header.tsx | 2 +- .../kullanici/components/dogrula-karti.tsx | 104 +++++++++++++++ .../kullanici/components}/giris-baslik.tsx | 0 .../kullanici/components}/giris-form.tsx | 0 .../kullanici/components/giris-karti.tsx | 122 ++++++++++++++++++ .../kullanici}/components/sign-out-button.tsx | 0 .../kullanici}/components/user-nav.tsx | 0 10 files changed, 254 insertions(+), 195 deletions(-) delete mode 100644 src/app/giris/loading.tsx create mode 100644 src/features/kullanici/components/dogrula-karti.tsx rename src/{app/giris => features/kullanici/components}/giris-baslik.tsx (100%) rename src/{app/giris => features/kullanici/components}/giris-form.tsx (100%) create mode 100644 src/features/kullanici/components/giris-karti.tsx rename src/{ => features/kullanici}/components/sign-out-button.tsx (100%) rename src/{ => features/kullanici}/components/user-nav.tsx (100%) diff --git a/src/app/giris/dogrula/page.tsx b/src/app/giris/dogrula/page.tsx index 9be3acc..5242a9d 100644 --- a/src/app/giris/dogrula/page.tsx +++ b/src/app/giris/dogrula/page.tsx @@ -1,30 +1,16 @@ +import { Suspense } from "react"; import type { Metadata } from "next"; -import { redirect } from "next/navigation"; -import { ArrowRight } from "lucide-react"; -import { PixelCorner } from "@/components/pixel-decor"; -import { Button } from "@/components/ui/button"; +import { + DogrulaKarti, + DogrulaKartiSkeleton, +} from "@/features/kullanici/components/dogrula-karti"; export const metadata: Metadata = { title: "Girişini onayla", robots: { index: false, follow: false }, }; -/** Yalnızca site içi (göreli) callback'lere izin ver — open redirect kalkanı. */ -function guvenliYol(deger: string | undefined, varsayilan: string): string { - return deger && deger.startsWith("/") && !deger.startsWith("//") - ? deger - : varsayilan; -} - -/** - * Maildeki giriş bağlantısının indiği onay sayfası. Doğrulama ucu - * (/api/auth/magic-link/verify) GET'lendiği anda token'ı tüketir; mail - * güvenlik tarayıcıları ve tarayıcı ön-yüklemeleri linki kullanıcıdan önce - * açıp token'ı yakabildiği için maildeki link token'ı TÜKETMEYEN bu sayfaya - * gelir. Verify ancak buradaki form gönderilince tetiklenir — tarayıcı botları - * sayfayı GET'lese bile form'u göndermez, token yanmaz. - */ -export default async function DogrulaPage({ +export default function DogrulaPage({ searchParams, }: { searchParams: Promise<{ @@ -34,68 +20,19 @@ export default async function DogrulaPage({ newUserCallbackURL?: string; }>; }) { - const { token, callbackURL, errorCallbackURL, newUserCallbackURL } = - await searchParams; - if (!token) redirect("/giris"); - - const callback = guvenliYol(callbackURL, "/"); - const hataCallback = guvenliYol( - errorCallbackURL, - `/giris?hata=gecersiz-baglanti&callback=${encodeURIComponent(callback)}`, - ); - const yeniKullaniciCallback = guvenliYol(newUserCallbackURL, callback); - return (
-
-
-

Girişini onayla

-

- Bu bağlantı senin için üretildi ve{" "} - tek kullanımlık. Aşağıdaki - butona tıkladığında KolayTercih hesabına giriş yapacaksın. -

-
- - - - - -
-

- Bu isteği sen yapmadıysan bu sayfayı kapatabilirsin — butona - tıklanmadığı sürece hesabında hiçbir şey değişmez. -

-
-
- - - - -
-
+ }> + {searchParams.then((sp) => ( + + ))} +
); diff --git a/src/app/giris/loading.tsx b/src/app/giris/loading.tsx deleted file mode 100644 index 0b363f7..0000000 --- a/src/app/giris/loading.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { Skeleton } from "@/components/ui/skeleton"; - -// /giris açılırken spinner yerine sayfanın kendi iskeleti: kart, başlık, -// Google butonu, ayraç, e-posta alanı ve gönder butonu aynı yerde belirir — -// içerik gelince zıplama olmaz. -export default function GirisLoading() { - return ( -
-
-
- {/* Başlık + alt metin */} - - - - - {/* Google ile devam et */} - - - {/* "veya" ayracı */} -
- - - -
- - {/* E-posta alanı + gönder butonu */} - - -
- - {/* Kart altındaki yasal metin */} -
- - -
-
-
- ); -} diff --git a/src/app/giris/page.tsx b/src/app/giris/page.tsx index db2bfe3..c8d2b10 100644 --- a/src/app/giris/page.tsx +++ b/src/app/giris/page.tsx @@ -1,95 +1,30 @@ +import { Suspense } from "react"; import type { Metadata } from "next"; -import Link from "next/link"; -import { redirect } from "next/navigation"; -import { getSession } from "@/lib/session"; -import { PixelCorner } from "@/components/pixel-decor"; -import { GirisForm } from "./giris-form"; -import { GirisBaslik } from "./giris-baslik"; +import { + GirisKarti, + GirisKartiSkeleton, +} from "@/features/kullanici/components/giris-karti"; export const metadata: Metadata = { title: "Giriş", robots: { index: false, follow: true }, }; -export default async function GirisPage({ +export default function GirisPage({ searchParams, }: { searchParams: Promise<{ callback?: string; hata?: string }>; }) { - const { callback, hata } = await searchParams; - const callbackURL = - callback && callback.startsWith("/") ? callback : "/"; - // Magic link doğrulaması başarısız olunca errorCallbackURL buraya düşürür - // (bkz. giris-form.tsx ve /giris/dogrula) — ham INVALID_TOKEN yerine - // dostça bir mesaj gösterip yeni bağlantı istemeye yönlendiriyoruz. - const baglantiHatasi = hata === "gecersiz-baglanti"; - - const session = await getSession(); - if (session) redirect(callbackURL); - - // Sihirbaz funnel'ı: seçimler yapıldı, listeye giriş kaldı - const funnel = callbackURL.startsWith("/sonuc"); - return ( // -mb-28: layout'taki ScrollFlow sarmalayıcısının pb-28'ini nötrler — // kart tam dvh içinde ortalanır, sayfa scroll etmez (sığmazsa scroll açılır).
-
-
- {baglantiHatasi && ( -
-

- Giriş bağlantısı geçersiz ya da süresi dolmuş -

-

- Bağlantılar 15 dakika geçerli ve tek kullanımlık. Aşağıdan - yeni bir bağlantı isteyebilirsin. -

-
- )} - -

- {funnel ? ( - <> - Seçimlerin hazır — giriş yap,{" "} - 5 deneme kredinle listen - hemen üretilsin. - - ) : ( - <> - Kayıt olduğunda{" "} - 5 deneme kredisi{" "} - hesabına tanımlanır — Yapay Zeka danışmana ücretsiz soru sorabilirsin. - - )} -

- -
-
- - - - -
-
-

- Giriş yaparak verilerinin tercih önerileri üretmek amacıyla{" "} - - gizlilik metnindeki - {" "} - gibi işlenmesini ve{" "} - - kullanım koşullarını - {" "} - kabul etmiş olursun. KolayTercih yerleşme garantisi vermez. -

+ }> + {searchParams.then(({ callback, hata }) => ( + + ))} +
); diff --git a/src/components/site-header.tsx b/src/components/site-header.tsx index d48bf18..18c75dd 100644 --- a/src/components/site-header.tsx +++ b/src/components/site-header.tsx @@ -2,7 +2,7 @@ import { Suspense } from "react"; import Image from "next/image"; import Link from "next/link"; import { KatalogArama } from "@/features/katalog/components/katalog-arama"; -import { UserNav, UserNavFallback } from "@/components/user-nav"; +import { UserNav, UserNavFallback } from "@/features/kullanici/components/user-nav"; import { getAllRehberler } from "@/lib/rehber"; export function SiteHeader() { diff --git a/src/features/kullanici/components/dogrula-karti.tsx b/src/features/kullanici/components/dogrula-karti.tsx new file mode 100644 index 0000000..d9f56ee --- /dev/null +++ b/src/features/kullanici/components/dogrula-karti.tsx @@ -0,0 +1,104 @@ +import { redirect } from "next/navigation"; +import { ArrowRight } from "lucide-react"; +import { PixelCorner } from "@/components/pixel-decor"; +import { Button } from "@/components/ui/button"; +import { Skeleton } from "@/components/ui/skeleton"; + +/** Yalnızca site içi (göreli) callback'lere izin ver — open redirect kalkanı. */ +function guvenliYol(deger: string | undefined, varsayilan: string): string { + return deger && deger.startsWith("/") && !deger.startsWith("//") + ? deger + : varsayilan; +} + +/** + * Maildeki giriş bağlantısının indiği onay kartı. Doğrulama ucu + * (/api/auth/magic-link/verify) GET'lendiği anda token'ı tüketir; mail + * güvenlik tarayıcıları ve tarayıcı ön-yüklemeleri linki kullanıcıdan önce + * açıp token'ı yakabildiği için maildeki link token'ı TÜKETMEYEN bu sayfaya + * gelir. Verify ancak buradaki form gönderilince tetiklenir — tarayıcı botları + * sayfayı GET'lese bile form'u göndermez, token yanmaz. + */ +export function DogrulaKarti({ + token, + callbackURL, + errorCallbackURL, + newUserCallbackURL, +}: { + token?: string; + callbackURL?: string; + errorCallbackURL?: string; + newUserCallbackURL?: string; +}) { + if (!token) redirect("/giris"); + + const callback = guvenliYol(callbackURL, "/"); + const hataCallback = guvenliYol( + errorCallbackURL, + `/giris?hata=gecersiz-baglanti&callback=${encodeURIComponent(callback)}`, + ); + const yeniKullaniciCallback = guvenliYol(newUserCallbackURL, callback); + + return ( +
+
+

Girişini onayla

+

+ Bu bağlantı senin için üretildi ve{" "} + tek kullanımlık. Aşağıdaki + butona tıkladığında KolayTercih hesabına giriş yapacaksın. +

+
+ + + + + +
+

+ Bu isteği sen yapmadıysan bu sayfayı kapatabilirsin — butona + tıklanmadığı sürece hesabında hiçbir şey değişmez. +

+
+
+ + + + +
+
+ ); +} + +export function DogrulaKartiSkeleton() { + return ( +
+ + + + + +
+ ); +} diff --git a/src/app/giris/giris-baslik.tsx b/src/features/kullanici/components/giris-baslik.tsx similarity index 100% rename from src/app/giris/giris-baslik.tsx rename to src/features/kullanici/components/giris-baslik.tsx diff --git a/src/app/giris/giris-form.tsx b/src/features/kullanici/components/giris-form.tsx similarity index 100% rename from src/app/giris/giris-form.tsx rename to src/features/kullanici/components/giris-form.tsx diff --git a/src/features/kullanici/components/giris-karti.tsx b/src/features/kullanici/components/giris-karti.tsx new file mode 100644 index 0000000..e93f716 --- /dev/null +++ b/src/features/kullanici/components/giris-karti.tsx @@ -0,0 +1,122 @@ +import Link from "next/link"; +import { redirect } from "next/navigation"; +import { getSession } from "@/lib/session"; +import { PixelCorner } from "@/components/pixel-decor"; +import { Skeleton } from "@/components/ui/skeleton"; +import { GirisForm } from "./giris-form"; +import { GirisBaslik } from "./giris-baslik"; + +export async function GirisKarti({ + callback, + hata, +}: { + callback?: string; + hata?: string; +}) { + const callbackURL = callback && callback.startsWith("/") ? callback : "/"; + // Magic link doğrulaması başarısız olunca errorCallbackURL buraya düşürür + // (bkz. giris-form.tsx ve /giris/dogrula) — ham INVALID_TOKEN yerine + // dostça bir mesaj gösterip yeni bağlantı istemeye yönlendiriyoruz. + const baglantiHatasi = hata === "gecersiz-baglanti"; + + const session = await getSession(); + if (session) redirect(callbackURL); + + // Sihirbaz funnel'ı: seçimler yapıldı, listeye giriş kaldı + const funnel = callbackURL.startsWith("/sonuc"); + + return ( + <> +
+
+ {baglantiHatasi && ( +
+

+ Giriş bağlantısı geçersiz ya da süresi dolmuş +

+

+ Bağlantılar 15 dakika geçerli ve tek kullanımlık. Aşağıdan + yeni bir bağlantı isteyebilirsin. +

+
+ )} + +

+ {funnel ? ( + <> + Seçimlerin hazır — giriş yap,{" "} + 5 deneme kredinle listen + hemen üretilsin. + + ) : ( + <> + Kayıt olduğunda{" "} + 5 deneme kredisi{" "} + hesabına tanımlanır — Yapay Zeka danışmana ücretsiz soru sorabilirsin. + + )} +

+ +
+
+ + + + +
+
+

+ Giriş yaparak verilerinin tercih önerileri üretmek amacıyla{" "} + + gizlilik metnindeki + {" "} + gibi işlenmesini ve{" "} + + kullanım koşullarını + {" "} + kabul etmiş olursun. KolayTercih yerleşme garantisi vermez. +

+ + ); +} + +// Kart, başlık, Google butonu, ayraç, e-posta alanı ve gönder butonu aynı +// yerde belirir — içerik gelince zıplama olmaz. +export function GirisKartiSkeleton() { + return ( + <> +
+ {/* Başlık + alt metin */} + + + + + {/* Google ile devam et */} + + + {/* "veya" ayracı */} +
+ + + +
+ + {/* E-posta alanı + gönder butonu */} + + +
+ + {/* Kart altındaki yasal metin */} +
+ + +
+ + ); +} diff --git a/src/components/sign-out-button.tsx b/src/features/kullanici/components/sign-out-button.tsx similarity index 100% rename from src/components/sign-out-button.tsx rename to src/features/kullanici/components/sign-out-button.tsx diff --git a/src/components/user-nav.tsx b/src/features/kullanici/components/user-nav.tsx similarity index 100% rename from src/components/user-nav.tsx rename to src/features/kullanici/components/user-nav.tsx