diff --git a/UX-DUZELTMELER.md b/UX-DUZELTMELER.md new file mode 100644 index 0000000..88bbcbb --- /dev/null +++ b/UX-DUZELTMELER.md @@ -0,0 +1,101 @@ +# UX/CRO Kritik Düzeltmeler — Uygulama Takibi + +UX denetiminden çıkan 🔴 kritik bulguların uygulama listesi. +Kaynak: 26 Temmuz 2026 tarihli UX/CRO denetimi. + +## K1 — Ödeme sonrası teslimat + +- [x] `/odeme/sonuc` başarı CTA'sı doğrudan `/listem`'e gitsin (ölü `/sonuc?acildi=1` yerine) + (sahte "paid" siparişle canlı doğrulandı: CTA → `/listem?acildi=1`) +- [x] `/listem` ödeme dönüşünde tek seferlik "kilit açıldı" deneyimi (banner + unblur) +- [x] Ücretli `/listem` başlık kopyası düzelsin (rozet "Paketin aktif", alt metin + "24 tercihinin tamamını gerekçe, risk analizi ve trendleriyle…") + +## K2 — Buton dürüstlüğü (giriş duvarı sürprizi) + +- [x] Hero sihirbazı son butonu girişsiz kullanıcıda girişi açıkça söylesin + ("Giriş yap ve listeni gör"; girişliyse "Listemi oluştur (3 kredi)") +- [x] Hero akışı oturumu açık kullanıcıyı `/giris`'ten sektirmeyip doğrudan `/sonuc`'a götürsün +- [x] `/sonuc` sihirbazında girişsiz son buton "Listemi gör" yerine dürüst etiket taşısın +- [x] Değer önce: hero sihirbazı biten girişsiz kullanıcı `/giris`'e değil doğrudan + `/sonuc`'taki ücretsiz tabloya gitsin; AI CTA'sı oradan "5 deneme kredinle + AI listeni kur — giriş yap" olarak verilsin (panel + sabit alt kart); + girişten dönüşte kayıtlı seçimlerle otomatik üretim başlasın + +## K3 — Fiyat şeffaflığı + +- [x] Landing hero mikro-kopyasına paket fiyatı (299 TL) eklensin +- [x] FAQ "Fiyatlandırma nasıl çalışıyor?" cevabına rakamlar yazılsın + +## K4 — Puan türü seçici + +- [x] Hero formuna SAY/EA/SÖZ/DİL/TYT segment kontrolü (varsayılan SAY) + +## K5 — Ücretsiz kullanıcıya AI sohbeti + +- [x] `/listem`'de paketi olmayan kullanıcıya kalan kredileriyle sohbet açılsın + (fiyat kartının altında; mesaj gönderimi + kredi düşümü canlı doğrulandı) +- [x] Kredi bitince composer yerinde paket CTA'sı görünsün (0 krediyle canlı doğrulandı) + +## K6 — Ödeme/AI hata mesajları + +- [x] iyzico/Anthropic yapılandırma hataları kullanıcıya ham sızmasın (log'a detay, kullanıcıya sade mesaj) + (iyzico yolu canlı doğrulandı: "Ödeme şu anda başlatılamıyor; kartından çekim yapılmadı…") +- [x] `/paket` sayfası `?hata=token|siparis` parametresini okuyup banner göstersin + +## Bonus + +- [x] Hero rozetindeki "açıkk" yazım hatası düzeltilsin + +--- + +# 🟠 Önemli + +- [x] Ö1: `/listem` paywall ara kartı kopyası netleşsin (kilit ikonu + "aşağıdaki tüm + satırlar açık" çelişkisi giderilsin; CTA fiyatı söylesin) + (yeni kopya: "İlk 3 tercihte AI analizini gördün… Analizleri aç — 299 TL") +- [x] Ö2: İl seçimi mobilde aranabilir liste ile yapılabilsin; harita mobilde geri + çekilsin; harita illerine erişilebilir isim (aria-label) eklensin + (arama + çip birincil; harita ≥sm'de görsel destek; path'lerde aria-label/aria-pressed) +- [x] Ö3: "Listem" isim çakışması giderilsin (manuel liste "Seçtiklerim" olsun, + AI raporu /listem "Listem" kalsın) +- [x] Ö3b: Navbar'daki "Seçtiklerim" butonu liste boşken görünmesin — tabloya + ulaşmamış ziyaretçiye "0/24" gürültü; ilk program eklenince belirir, + liste boşalınca kaybolur (canlı doğrulandı) +- [x] Ö4: Güven sayfaları eklensin (gizlilik/KVKK, kullanım & iade koşulları, + iletişim) ve footer + giriş + paket sayfalarından bağlansın + (NOT: metinler MVP taslağı — yayın öncesi hukuki gözden geçirme gerekir) +- [x] Ö5: AI'ın ürettiği genel değerlendirme ve uyarılar /listem'de görünsün + (paketliye tam, paketsize kilitli önizleme — "Danışman özeti" kartı) +- [x] Ö6: Paketlinin gerçek revizyon akışı /listem'de çalışsın (geri bildirim + kutusu; başlıktaki "Revize et" oraya götürsün) + (`revizyon-kutusu.tsx` → `listeRevize` aksiyonu; kalan hak sayacıyla) +- [x] Ö7: Liste tepesine dilim özeti (hayal/dengeli/garanti dağılımı) ve tek + şehir uyarısı eklensin +- [x] Ö8: 2024/2025 verisi eksik satırda çelişkili trend rozeti gösterilmesin + (rozet iki yıl da doluysa çıkar; yoksa "Veri eksik") +- [x] Ö9: Magic link "gönderildi" ekranına yeniden gönder + e-postayı düzelt eklensin + (30 sn bekleme sayaçlı yeniden gönder + "E-postayı düzelt") +- [x] Ö10: Mobilde sohbete hızlı erişim (yüzen "Danışmana sor" kısayolu) + +# 🛠 Dev araçları + +- [x] Dev süperadmin paneli: yalnızca geliştirme ortamında sol altta "DEV" + butonu — anında giriş (magic link atlanır), kredi/paket/rapor senaryoları, + sahte "paid" siparişle ödeme başarı ekranı, tüm sayfalara hızlı bağlantılar + (`src/components/dev/dev-panel.tsx` + `src/lib/dev/actions.ts`; + layout NODE_ENV kontrolüyle monte eder, aksiyonlar sunucuda ayrıca korunur) + +# 🟢 Güzel olur + +- [x] G1: Sıralama girişinde canlı binlik ayracı + toast yerine input altı hata +- [x] G2: Sihirbaz adım göstergesi tamamlanan adımı işaretlesin; 1. adımda + "Geri" görünmesin +- [x] G3: Route-level loading / error / not-found boundary'leri eklensin + (kök loading/error/not-found + /sonuc ve /listem'e Skeleton'lı loading) +- [x] G4: Landing istatistik kartlarının yanında "yerleşme garantisi vermeyen + dürüst araç" konumlandırması cesur işlensin (koyu kart: "Yerleşme + garantisi vermiyoruz. Çünkü kimse veremez.") +- [x] G5: `sonButonEtiketi` şeffaflığı hero varyantına taşınsın (girişli: + "Listemi oluştur (3 kredi)"; girişsiz artık ücretsiz tabloya gittiği + için "Ücretsiz tablonu gör") diff --git a/data/app.db b/data/app.db index 5897576..a72736c 100644 Binary files a/data/app.db and b/data/app.db differ diff --git a/package.json b/package.json index bb77677..2ad916d 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "react": "19.2.4", "react-dom": "19.2.4", "react-markdown": "^10.1.0", + "react-use-measure": "^2.1.7", "remark-breaks": "^4.0.0", "remark-gfm": "^4.0.1", "resend": "^6.18.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 305413e..26ed21b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -62,6 +62,9 @@ importers: react-markdown: specifier: ^10.1.0 version: 10.1.0(@types/react@19.2.17)(react@19.2.4) + react-use-measure: + specifier: ^2.1.7 + version: 2.1.7(react-dom@19.2.4(react@19.2.4))(react@19.2.4) remark-breaks: specifier: ^4.0.0 version: 4.0.0 @@ -4720,6 +4723,15 @@ packages: '@types/react': optional: true + react-use-measure@2.1.7: + resolution: {integrity: sha512-KrvcAo13I/60HpwGO5jpW7E9DfusKyLPLvuHlUyP5zqnmAPhNc6qTRjUQrdTADl0lpPpDVU2/Gg51UlOGHXbdg==} + peerDependencies: + react: '>=16.13' + react-dom: '>=16.13' + peerDependenciesMeta: + react-dom: + optional: true + react@19.2.4: resolution: {integrity: sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==} engines: {node: '>=0.10.0'} @@ -10237,6 +10249,12 @@ snapshots: optionalDependencies: '@types/react': 19.2.17 + react-use-measure@2.1.7(react-dom@19.2.4(react@19.2.4))(react@19.2.4): + dependencies: + react: 19.2.4 + optionalDependencies: + react-dom: 19.2.4(react@19.2.4) + react@19.2.4: {} readable-stream@3.6.2: diff --git a/public/yok-logo.svg b/public/yok-logo.svg new file mode 100644 index 0000000..f89226f --- /dev/null +++ b/public/yok-logo.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/app/api/soru/route.ts b/src/app/api/soru/route.ts index 3d38b36..14105e5 100644 --- a/src/app/api/soru/route.ts +++ b/src/app/api/soru/route.ts @@ -229,8 +229,12 @@ export async function POST(request: NextRequest) { reason: "refund", refId: kullaniciMesajId, }); + console.error("[soru] AI istemcisi kurulamadı (anahtar eksik olabilir)"); return Response.json( - { error: "AI altyapısı yapılandırılmadı." }, + { + error: + "Danışman şu anda yanıt veremiyor; kredin iade edildi. Birazdan tekrar dener misin?", + }, { status: 503 }, ); } diff --git a/src/app/error.tsx b/src/app/error.tsx new file mode 100644 index 0000000..a5dc992 --- /dev/null +++ b/src/app/error.tsx @@ -0,0 +1,45 @@ +"use client"; + +// Kök hata sınırı: beklenmedik sunucu/istemci hatalarında Next'in çıplak +// hata ekranı yerine markalı, kurtarılabilir bir ekran gösterir. + +import { useEffect } from "react"; +import Link from "next/link"; +import { TriangleAlert } from "lucide-react"; +import { Button } from "@/components/ui/button"; + +export default function Error({ + error, + reset, +}: { + error: Error & { digest?: string }; + reset: () => void; +}) { + useEffect(() => { + console.error("[hata-siniri]", error); + }, [error]); + + return ( +
+ +

+ Bir şeyler ters gitti +

+

+ Beklenmedik bir hata oluştu. Tekrar dener misin? Sorun devam ederse + birkaç dakika sonra yeniden gel — verilerin güvende. +

+
+ + +
+
+ ); +} diff --git a/src/app/giris/giris-form.tsx b/src/app/giris/giris-form.tsx index e2f9966..5ee6371 100644 --- a/src/app/giris/giris-form.tsx +++ b/src/app/giris/giris-form.tsx @@ -34,6 +34,21 @@ export function GirisForm({ callbackURL }: { callbackURL: string }) { const [email, setEmail] = useState(""); const [busy, setBusy] = useState<"google" | "email" | null>(null); const [sent, setSent] = useState(false); + // Yeniden gönderim arası bekleme (sn) — istemeden spam'lenmesin + const [bekleme, setBekleme] = useState(0); + + function geriSayimBaslat() { + setBekleme(30); + const sayac = setInterval(() => { + setBekleme((s) => { + if (s <= 1) { + clearInterval(sayac); + return 0; + } + return s - 1; + }); + }, 1000); + } async function googleIleGiris() { setBusy("google"); @@ -47,8 +62,8 @@ export function GirisForm({ callbackURL }: { callbackURL: string }) { } } - async function linkGonder(e: React.FormEvent) { - e.preventDefault(); + async function linkGonder(e?: React.FormEvent) { + e?.preventDefault(); if (!/^\S+@\S+\.\S+$/.test(email)) { toast.error("Geçerli bir e-posta adresi gir."); return; @@ -64,17 +79,41 @@ export function GirisForm({ callbackURL }: { callbackURL: string }) { return; } setSent(true); + geriSayimBaslat(); } if (sent) { return ( -
-

Bağlantı gönderildi 📬

-

- {email} adresine bir giriş - bağlantısı gönderdik. Gelen kutunu (ve spam klasörünü) kontrol et — - bağlantı 5 dakika geçerli. -

+
+
+

Bağlantı gönderildi 📬

+

+ {email} adresine bir giriş + bağlantısı gönderdik. Gelen kutunu (ve spam klasörünü) kontrol et — + bağlantı 5 dakika geçerli. +

+
+
+ + +
); } diff --git a/src/app/giris/page.tsx b/src/app/giris/page.tsx index ad159f3..bac56af 100644 --- a/src/app/giris/page.tsx +++ b/src/app/giris/page.tsx @@ -1,4 +1,5 @@ import type { Metadata } from "next"; +import Link from "next/link"; import { redirect } from "next/navigation"; import { getSession } from "@/lib/session"; import { GirisForm } from "./giris-form"; @@ -46,8 +47,15 @@ export default async function GirisPage({

- Giriş yaparak verilerinin tercih önerileri üretmek amacıyla - işlenmesini kabul etmiş olursun. KolayTercih yerleşme garantisi vermez. + 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.

diff --git a/src/app/gizlilik/page.tsx b/src/app/gizlilik/page.tsx new file mode 100644 index 0000000..e23dbc9 --- /dev/null +++ b/src/app/gizlilik/page.tsx @@ -0,0 +1,66 @@ +import type { Metadata } from "next"; +import Link from "next/link"; + +export const metadata: Metadata = { + title: "Gizlilik ve KVKK — KolayTercih", +}; + +// NOT: MVP taslağıdır; yayına almadan önce hukuki gözden geçirme gerekir. +export default function GizlilikPage() { + return ( +
+

+ Gizlilik ve Kişisel Verilerin Korunması +

+

Son güncelleme: 26 Temmuz 2026

+ +
+
+

+ Hangi verileri işliyoruz? +

+

+ KolayTercih; e-posta adresini (hesap oluşturma ve giriş için), YKS + başarı sıralamanı ve sihirbazda yaptığın seçimleri (tercih listeni + üretmek için), danışman sohbetine yazdığın mesajları (sorularını + yanıtlamak için) ve ödeme yaptığında sipariş kayıtlarını işler. + Kart bilgilerin bize hiç ulaşmaz; ödeme iyzico altyapısında + gerçekleşir. +

+
+
+

+ Ne için kullanıyoruz? +

+

+ Verilerin yalnızca tercih önerileri üretmek, hesabını ve kredi + bakiyeni yönetmek ve sana hizmetle ilgili e-posta (giriş bağlantısı, + ödeme bilgilendirmesi) göndermek için kullanılır. Verilerini + üçüncü taraflara satmayız, reklam amacıyla paylaşmayız. +

+
+
+

+ Saklama ve silme +

+

+ Hesabını sildirmek veya verilerinin bir kopyasını istemek için{" "} + + iletişim sayfasındaki + {" "} + adresten bize yazabilirsin; talepler 30 gün içinde sonuçlandırılır. +

+
+
+

+ Çerezler +

+

+ Yalnızca oturumunu açık tutmak için zorunlu çerezler kullanılır; + reklam veya izleme çerezi kullanılmaz. +

+
+
+
+ ); +} diff --git a/src/app/iletisim/page.tsx b/src/app/iletisim/page.tsx new file mode 100644 index 0000000..e4bb320 --- /dev/null +++ b/src/app/iletisim/page.tsx @@ -0,0 +1,30 @@ +import type { Metadata } from "next"; +import { Mail } from "lucide-react"; + +export const metadata: Metadata = { + title: "İletişim — KolayTercih", +}; + +export default function IletisimPage() { + return ( +
+

İletişim

+

+ Ödeme, iade, hesap silme ya da başka bir konuda yardıma mı ihtiyacın + var? Tercih döneminde mesajlara aynı gün dönmeye çalışıyoruz. +

+ + + destek@kolaytercih.com + +

+ Ödemenle ilgili yazıyorsan e-postana sipariş tarihini ve kullandığın + hesap adresini eklersen daha hızlı yardımcı olabiliriz. Kart bilgini + asla e-postayla paylaşma. +

+
+ ); +} diff --git a/src/app/kosullar/page.tsx b/src/app/kosullar/page.tsx new file mode 100644 index 0000000..9cd3cfa --- /dev/null +++ b/src/app/kosullar/page.tsx @@ -0,0 +1,74 @@ +import type { Metadata } from "next"; +import Link from "next/link"; + +export const metadata: Metadata = { + title: "Kullanım ve Satış Koşulları — KolayTercih", +}; + +// NOT: MVP taslağıdır; yayına almadan önce hukuki gözden geçirme gerekir. +export default function KosullarPage() { + return ( +
+

+ Kullanım ve Satış Koşulları +

+

Son güncelleme: 26 Temmuz 2026

+ +
+
+

+ Hizmetin kapsamı +

+

+ KolayTercih, resmî YÖK Atlas verilerine dayanan bir karar destek + aracıdır. Öneriler bilgilendirme amaçlıdır; KolayTercih yerleşme + garantisi vermez ve ÖSYM ya da YÖK ile bağlantılı değildir. Tercih + listenin son hali ve başvuru sorumluluğu sana aittir. +

+
+
+

+ Ücretlendirme +

+

+ Temel program arama ücretsizdir. Tercih Dönemi Paketi tek seferlik + bir satın almadır; abonelik değildir, otomatik yenilenmez. Paket + içeriği ve güncel fiyat{" "} + + paket sayfasında + {" "} + yazar. +

+
+
+

+ İade ve cayma +

+

+ Paket, satın alır almaz ifasına başlanan dijital bir içeriktir. + Buna rağmen kişisel listen henüz üretilmemişse (paket hiç + kullanılmamışsa) satın almadan itibaren 14 gün içinde koşulsuz + iade talep edebilirsin. Teknik bir sorun nedeniyle hizmeti + alamadıysan, kullanımdan bağımsız olarak ücretin iade edilir. + İade talepleri için{" "} + + bize yaz + + ; talepler 14 gün içinde sonuçlandırılır ve iade, ödeme yaptığın + karta yapılır. +

+
+
+

+ Hesap ve adil kullanım +

+

+ Krediler hesabına tanımlıdır, kişiseldir ve üçüncü kişilere + devredilemez. Hizmeti kötüye kullanan (otomatik istek, içerik + kazıma vb.) hesaplar kapatılabilir. +

+
+
+
+ ); +} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 31cba8f..be37141 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -5,6 +5,7 @@ import { SiteHeader } from "@/components/site-header"; import { ListeCekmecesi } from "@/components/manuel-liste/liste-cekmecesi"; import { HeaderGate } from "@/components/header-gate"; import { ProgressiveBlur } from "@/components/ui/skiper-ui/skiper41"; +import { DevPanel } from "@/components/dev/dev-panel"; import "./globals.css"; const outfit = Outfit({ @@ -63,6 +64,8 @@ export default function RootLayout({ {/* Manuel 24'lük liste çekmecesi + "+" uçuş katmanı (navbar'dan açılır) */} + {/* Dev süperadmin paneli — prod build'de hiç render edilmez */} + {process.env.NODE_ENV !== "production" ? : null} ); diff --git a/src/app/listem/loading.tsx b/src/app/listem/loading.tsx new file mode 100644 index 0000000..74218fb --- /dev/null +++ b/src/app/listem/loading.tsx @@ -0,0 +1,19 @@ +import { Skeleton } from "@/components/ui/skeleton"; + +export default function ListemLoading() { + return ( +
+ + + +
+
+ {Array.from({ length: 10 }).map((_, i) => ( + + ))} +
+ +
+
+ ); +} diff --git a/src/app/listem/page.tsx b/src/app/listem/page.tsx index 38947e3..23bb26c 100644 --- a/src/app/listem/page.tsx +++ b/src/app/listem/page.tsx @@ -3,12 +3,14 @@ import Link from "next/link"; import { eq } from "drizzle-orm"; import { redirect } from "next/navigation"; import { + AlertTriangle, ArrowRight, Bot, Check, Database, FileText, Lock, + MessageCircleQuestion, PencilLine, ShieldCheck, Sparkles, @@ -16,7 +18,7 @@ import { import { verifySession, getCurrentUser } from "@/lib/session"; import { appDb, schema } from "@/lib/appdb"; import { PUAN_TURLERI } from "@/lib/db"; -import { URUNLER } from "@/lib/credits"; +import { URUNLER, MAX_REVIZYON } from "@/lib/credits"; import type { RaporSonuc } from "@/lib/ai/rapor"; import type { RaporParams } from "@/lib/rapor-havuzu"; import { Button } from "@/components/ui/button"; @@ -24,6 +26,7 @@ import { Badge } from "@/components/ui/badge"; import { RaporListesi } from "@/components/rapor-listesi"; import { raporMaskele, ACIK_SATIR } from "@/lib/rapor-maske"; import { SohbetClient } from "./sohbet-client"; +import { RevizyonKutusu } from "./revizyon-kutusu"; export const metadata: Metadata = { title: "Listem — KolayTercih", @@ -33,11 +36,19 @@ const PAKET_FIYATI = (URUNLER.paket.amountKurus / 100).toLocaleString("tr-TR", { maximumFractionDigits: 0, }); -export default async function ListemPage() { +export default async function ListemPage({ + searchParams, +}: { + searchParams: Promise<{ acildi?: string }>; +}) { await verifySession("/listem"); const user = await getCurrentUser(); if (!user) redirect("/giris?callback=/listem"); + // ?acildi=1: ödeme dönüşü — kilidin kalktığı tek seferlik açılış deneyimi + const { acildi } = await searchParams; + const kilitAcilisi = Boolean(user.hasPaket && acildi === "1"); + const satir = await appDb.query.reports.findFirst({ where: eq(schema.reports.userId, user.id), }); @@ -73,12 +84,29 @@ export default async function ListemPage() { return (
+ {kilitAcilisi ? ( +
+ + Paketin aktif — 24 tercihinin tamamı, gerekçeler, riskler ve PDF + artık açık. 🎉 +
+ ) : null}
- - - YÖK Atlas verileri ücretsiz - + {user.hasPaket ? ( + + + Paketin aktif + + ) : ( + + + YÖK Atlas verileri ücretsiz + + )}

{user.hasPaket ? "Tercih karar merkezin" @@ -86,8 +114,10 @@ export default async function ListemPage() {

{params.sira.toLocaleString("tr-TR")}. sıra ·{" "} - {PUAN_TURLERI[params.tur]} · 24 programın 2024–2025 taban - sıralamalarını ve ham trendini aşağıda ücretsiz inceleyebilirsin. + {PUAN_TURLERI[params.tur]} ·{" "} + {user.hasPaket + ? "24 tercihinin tamamını gerekçe, risk analizi ve trendleriyle aşağıda inceleyebilirsin." + : "24 programın 2024–2025 taban sıralamalarını ve ham trendini aşağıda ücretsiz inceleyebilirsin."}

{user.hasPaket ? ( @@ -98,9 +128,7 @@ export default async function ListemPage() { size="sm" className="cursor-pointer" > - + Revize et @@ -120,8 +148,49 @@ export default async function ListemPage() { ) : null}
+ {/* AI'ın genel değerlendirmesi ve uyarıları: paketliye tam, + paketsize sunucuda kırpılmış önizleme + kilit notu */} + {rapor.genelDegerlendirme ? ( +
+

+ + Danışman özeti +

+

+ {rapor.genelDegerlendirme} +

+ {user.hasPaket && rapor.uyarilar?.length ? ( +
    + {rapor.uyarilar.map((uyari) => ( +
  • + + {uyari} +
  • + ))} +
+ ) : null} + {!user.hasPaket ? ( +

+ + Değerlendirmenin tamamı ve sıralamana özel uyarılar pakette. +

+ ) : null} +
+ ) : null} +
-
+
+

- Ham veri burada bitmiyor — aşağıdaki tüm satırlar açık + İlk {ACIK_SATIR} tercihte AI analizini gördün

- Paket; kalan {24 - ACIK_SATIR} tercih için kişisel risk, - gerekçe ve AI yorumunu açar. + Kalan {24 - ACIK_SATIR} tercihin kişisel risk, gerekçe ve + AI yorumu paketle açılır. Program adları ve taban verileri + her zaman ücretsiz.

@@ -147,7 +217,7 @@ export default async function ListemPage() { className="h-11 w-full shrink-0 cursor-pointer bg-orange-500 text-white transition-[background-color,transform] duration-200 active:scale-[0.97] hover:bg-orange-600 sm:w-auto" > - Analizleri aç + Analizleri aç — {PAKET_FIYATI} TL @@ -156,10 +226,18 @@ export default async function ListemPage() { />
-
+ {user.hasPaket ? ( + + ) : null} +
+ +
{user.hasPaket ? ( ) : ( +
@@ -230,9 +308,27 @@ export default async function ListemPage() {
+ + {/* Deneme kredileri sohbette geçerli: AI'ı satın almadan önce + tattırır (kalan kredi biterse composer paket CTA'sına döner). */} + +
)}
+ + {/* Mobilde sohbet sağ rayla birlikte en alta düşer; yüzen kısayol + kullanıcıyı 24 satırı kaydırmadan danışmana ulaştırır. */} + + + Danışmana sor +
); } diff --git a/src/app/listem/revizyon-kutusu.tsx b/src/app/listem/revizyon-kutusu.tsx new file mode 100644 index 0000000..e76f789 --- /dev/null +++ b/src/app/listem/revizyon-kutusu.tsx @@ -0,0 +1,96 @@ +"use client"; + +// Paketlinin geri bildirime dayalı liste revizyonu (/listem üzerinde). +// listeRevize sunucu aksiyonunu çağırır; başarıda sayfayı yeniler ki +// sunucuda güncellenen rapor ve kredi bakiyesi ekrana yansısın. + +import { useState } from "react"; +import { useRouter } from "next/navigation"; +import { Loader2, RefreshCcw } from "lucide-react"; +import { toast } from "sonner"; +import { Button } from "@/components/ui/button"; +import { listeRevize } from "@/app/sonuc/actions"; + +export function RevizyonKutusu({ kalanHak }: { kalanHak: number }) { + const [feedback, setFeedback] = useState(""); + const [pending, setPending] = useState(false); + const router = useRouter(); + + if (kalanHak <= 0) { + return ( +
+

+ Revizyon hakların doldu. Danışman sohbetinden listen hakkında soru + sormaya devam edebilirsin. +

+
+ ); + } + + async function revizeEt() { + setPending(true); + try { + const sonuc = await listeRevize({ + feedback: feedback.trim(), + requestId: crypto.randomUUID(), + }); + if (sonuc.ok) { + toast.success("Listen yeniden kuruldu."); + setFeedback(""); + router.refresh(); + } else { + toast.error(sonuc.error); + } + } catch { + toast.error("Beklenmeyen bir hata oluştu, tekrar dener misin?"); + } finally { + setPending(false); + } + } + + return ( +
+

+ Listede değişiklik mi istiyorsun? +

+

+ Ne değişsin istediğini yaz; liste aynı gerçek veriyle yeniden kurulsun + (3 kredi). +

+