From 7288c926d03474d4ddfa01da563558c52bdad445 Mon Sep 17 00:00:00 2001 From: bilalgursen Date: Sun, 26 Jul 2026 15:10:57 +0300 Subject: [PATCH] =?UTF-8?q?UX/CRO=20denetimi=20uyguland=C4=B1:=20de=C4=9Fe?= =?UTF-8?q?r-=C3=B6nce=20funnel,=20g=C3=BCven=20sayfalar=C4=B1,=20dev=20pa?= =?UTF-8?q?neli?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Kritik (K1-K6) sonrası kalan tüm maddeler: - Değer önce: girişsiz kullanıcı sihirbaz bitince /giris yerine /sonuc'taki ücretsiz tabloya gider (?hazir=1); AI CTA'sı "5 deneme kredinle listeni kur — giriş yap" paneli + sabit alt karttan verilir, girişte otomatik üretim - Ö1-Ö10: paywall kopyası, aranabilir il seçimi, "Seçtiklerim" adlandırması, gizlilik/koşullar/iletişim sayfaları, danışman özeti + uyarılar, revizyon kutusu, dilim özeti + tek şehir uyarısı, eksik veri rozetleri, magic link yeniden gönder, mobil sohbet kısayolu - G1-G5: binlik ayraçlı input + inline hata, adım göstergesi, route-level loading/error/not-found, dürüst konumlandırma kartı, dürüst buton etiketleri - Seçtiklerim butonu liste boşken gizli; ilk eklemede belirir - Dev süperadmin paneli (yalnızca development): hızlı giriş, kredi/paket/rapor senaryoları, sahte ödeme dönüşü Co-Authored-By: Claude Fable 5 --- UX-DUZELTMELER.md | 101 +++++++ data/app.db | Bin 122880 -> 135168 bytes package.json | 1 + pnpm-lock.yaml | 18 ++ public/yok-logo.svg | 28 ++ src/app/api/soru/route.ts | 6 +- src/app/error.tsx | 45 +++ src/app/giris/giris-form.tsx | 57 +++- src/app/giris/page.tsx | 12 +- src/app/gizlilik/page.tsx | 66 +++++ src/app/iletisim/page.tsx | 30 ++ src/app/kosullar/page.tsx | 74 +++++ src/app/layout.tsx | 3 + src/app/listem/loading.tsx | 19 ++ src/app/listem/page.tsx | 130 +++++++-- src/app/listem/revizyon-kutusu.tsx | 96 ++++++ src/app/loading.tsx | 13 + src/app/not-found.tsx | 24 ++ src/app/odeme/sonuc/page.tsx | 27 +- src/app/page.tsx | 106 +++++-- src/app/paket/actions.ts | 4 +- src/app/paket/page.tsx | 33 ++- src/app/sonuc/actions.ts | 12 +- src/app/sonuc/loading.tsx | 19 ++ src/app/sonuc/page.tsx | 2 + src/app/sonuc/sihirbaz-cagri-karti.tsx | 91 +++++- src/components/core/transition-panel.tsx | 45 +++ src/components/dev/dev-panel.tsx | 273 ++++++++++++++++++ src/components/hero-form.tsx | 95 ++++-- src/components/il-secim-haritasi.tsx | 3 + .../manuel-liste/liste-cekmecesi.tsx | 8 +- src/components/manuel-liste/listem-butonu.tsx | 15 +- src/components/program-tablosu.tsx | 12 +- src/components/rapor-listesi.tsx | 55 +++- src/components/sihirbaz-adimlar.tsx | 165 ++++++++--- src/components/ui/typing-animation.tsx | 2 + src/lib/auth.ts | 5 + src/lib/credits.ts | 3 + src/lib/dev/actions.ts | 127 ++++++++ src/lib/rapor-maske.ts | 3 +- 40 files changed, 1666 insertions(+), 162 deletions(-) create mode 100644 UX-DUZELTMELER.md create mode 100644 public/yok-logo.svg create mode 100644 src/app/error.tsx create mode 100644 src/app/gizlilik/page.tsx create mode 100644 src/app/iletisim/page.tsx create mode 100644 src/app/kosullar/page.tsx create mode 100644 src/app/listem/loading.tsx create mode 100644 src/app/listem/revizyon-kutusu.tsx create mode 100644 src/app/loading.tsx create mode 100644 src/app/not-found.tsx create mode 100644 src/app/sonuc/loading.tsx create mode 100644 src/components/core/transition-panel.tsx create mode 100644 src/components/dev/dev-panel.tsx create mode 100644 src/lib/dev/actions.ts 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 5897576fcfe7a8d64be759d954668af9c8138ada..a72736c2fed11668c51ee574dd28d7feedda885c 100644 GIT binary patch delta 8065 zcmcgxYj7Lab;gn)0aC;+MOyKzwN@ZyiV9ZRd-v{3cBDWQ^`byMNGK9*MPBR!UJ(E( z0HjT8WLS1>&vfe82|Qk>Q^}r5I%$5S5zWMmJ@$+$G95Wi6QwipOK#K3GwnEyRsYC1 zeYj4~F8F{zQh@*X2S5(*x%WHwJLi1o-pfZW1Ri-Y_{{AMQ$C+>-8b27)YRmC-`#vm zbHn$2%zpYD&9?k`i@!P9^rOIU1M!CMPpxi!s$u_Q-Oa&xZ0+Nm&#&{cCJ}|_)C86| zq9_KZsutmNUJy)K)G^leq3nqV#^r2&r+_B(-PXyyJ$y8m5z@B2Z)`$7k}tk{_rBYA zX<03kw^_qZ*=ak?PMCQ!o3h!oy{$;!_4~qyZ=7js3dCb;dg6Y;uw>0JR8Fu=jU%Rt zIo0G5#~XZt$3)N-)2s$MmNTpLflU@87UG%oG2+e zMT`G`UGI`$5CiiGgVQV-NI{nA@&RN@q9!Vs*Hy~| z9?wlBx14RbrI}9pm{s&M^i}2^=7-ENI>`{`3HlYtyFR+Gp*~L4l5owgVJ%70(^u$8`bqj2z(?sqw?F2mswE=C;Lie7 zP2_l)BshZg1XP}`b1Jbc(NIMl=}NT?|53kR%O6jhxtx|XYe}AhTmEN!ZSy|pBC!&x ztA9{$#6Ss7Q%s!`kSru5h_uXGpUa18*;a6i*Dakh^{ZVUz8v@SqR0c3#bG26;30vz z1W$NQ))PdNkji6Gs^-K0WzEr%Z2oxC%$Qj%Z|Yh?z?Q^wqGe%@=%&RfSWvh`LE&XA z83|p{@CozkyQf-RFFIm=Lo*ah<|WWdOL0pcI1v*nXQ^0$r38gYd^Il`@>yF;9Z$MV zVFlaJLIVNxx2K+b@N}TFz2y@heKoY3A@myh73Q6mPeN%H#RQnkEx)9<(KOV@r>nkoc6FhaJZ=~L>p@q`vcFp9 zxrx`N_`e*ds^rSfg=8tqmDRkzBSuw^??QN6Upnir54VT5`;Gx}Yc4gGKEEoL|K zJ~PdHi%EwjLw^xUhPF@L@#3F^3Z3<_s^ZpOi&0xGMyo50)+~3zX)S73I;Z~U2hDZ$ zUB1_gf%AQSY90Ktx;0rybghcVZmy$l_xb!jUQ8IK$_r4y2ApgZ5D^twGG`Ky4}(}p z5w#V{VS%nuZP?7`e}4ChZ|}-O|-`(HA<|bg|`=rvI2~x$sicp;BE`-&dDsc%5_0`*!ElO+Wj% zp}wp6>&3w3K7aFL&0mLv4>s?ujKy-H=&Hr&PA^_Re@|%$)-`VRg+?2<0s?Nl_uASz zVDiy5XWm^C^!p{nO6WpD;}k;!X_S%9X@+ERGQpxQ$fk}=wOX`Jg>!aR3vW@x;y--E zcE@I3yWfplE@cn1cd~=#aAqVma>5QDi-heIocY3A?g{tp?>}0+>DuPv&^0z7^N1%H z7tdTn4e^=pX~WLC84A_GdLLi=V;e#O;zd~vN9J&_^YKM0Q5ya%h@m^EJR;8NzoW2o z^9*&@+(=VtL?VLoZx^Y(P3(s4W7%A4tlNqHgbL5C9)#6pjAiWHJ4+#V`bIz8Bi2rh ze)Mczu#Wkm56*D!0CV4AI5)Vp@Kx7UA8#r?{88t#l;2;+O#7z#FAp_Ov90lj?pi`u z*HIs9D*oy{_PJK7t*)sJ&_4?vd{hZG$NaH-9_@+w{btYD!NWPc^GL6{yC0{b=^U{J zC**;BSv8f<8ODi`KmA%Pd3byOgG5062l0q9kW>fA(2%&}h}~}whzA}#;zZX{Vduha z)G;QdjTz=%4=+uh?Wd9y-NV>iL#NX3WQYm-z|v4Q6bbzK=4QTW_fsspZfcoo|d#k5fN` zy}Z<1c%U&5%fTlGS>WII&@Qq&Y9tRBW^_*?K6s$#pqd;!S7-7m)uXL0YH1E zdrwhU+u3Ts)4QIgUO8|xR1(W-DJ_%Iva^#DDUBUBSw;NL$JeNU0%uIji_WipN(p6U z8+V@m5#_Yc)viPqs$O=^U81@j{sI+Vwv@ez&(GB}7My1GZn-b%yn7UBW6j^?ISmhn{B45M5p*&Z|?`jepfu$8|;iKi9Z%nx|Vt z&gLIeWbILHRGVOj%waFkF>Tm}>;24gvoB=L3|vc`gBPfdd6MsVm3j_>;Nd9PdFEw` ztU!fpdDC1%sEcg?sm{9BAS3*LPpw%YBVeWz&gy4>D+yp8V3~T z$Cs%i1(-cH0U8Vy3Ho1`uE3jKjDrmD>4yre2r_$yow~>!# zN8n&A|E z?)?o#s={8pj^upr*VNYPKn1uli*h9p zMo3k}2vTLZ<3^CA$_Owd1qn(7(hO!SLV@y>ak-5AunWg5C`ez*o_)b~Qzc`Mo7tSh zUZEPRy4mp+6)xForJI?tR4TIgg+Q!$CE}Tu{F_&9m-N6u@4lHQ<9(F@RYU}P^bTx` z0aauHwkoMaf^Y1GRhHnU5b=!5)&|k~8{oNdxurXI@RGoz%B7JAOAA0FK_XC$z($TB z41gHI=*}oxAGBcQ^&xf`EpZ*Y&!tKPoEB9zf&~Jy9Kn#EJYtxnkp)l`{ySBDxOrhOM-$ z*h`61ifdrmz;+(M0I}jI|4zaYMIl&?s3MY~jk@NG zq`P-;2x(_w;SOG@du7pJXZ{Mb>2U9%f!Xtez3|0u@O@Be?EbNZ*v{VS%? z0(puMvTMxdqEm}aGfvg0LjWaA&lb?~AS*&^*LVvE2eTo3){y zUeg|A69hJ~1a`tQrXa$;u&3AnN7w`oirw=tD#CFl6&q?OAM3GePi~fHi54Mv8m1Xx z2XPY1Aja;o4)z|_(1fet0uZeYE8lxJhi{5fY4S~`v_`NH{=f&YvEOVBy_iNFZ(%1q zfHrW$h$t;E-u%(7{hC#>3=TTLO$(KR9-!L5;uSDElsv-DTEA=R%2GDBsUa=z#aG$j zQ0{ZnY9<&`w(GAp%48#DF~VX3wlXcYHJ)Xpm?D@G;5AA`hMH{Nw`CuvlHh5AX~Ey2 zRA}&J^S2we+bZ25$YDkI<R`Fu5!B$c-WO83{d2J7m){u34PuF^n>Cmi)e0xkY_xBFV~_>47kbk}A2IoI~Txw>bBF8lui DZOPKw 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). +

+