All checks were successful
Deploy / deploy (push) Successful in 2m38s
- Updated the `page.tsx` component to enhance user experience with new UI elements and improved layout. - Modified the `funnel-banner.tsx` to include dynamic text and improved styling for better engagement. - Adjusted the `liste-paneli.tsx` to provide clearer information on user credits and package details. - Enhanced the `rapor-listesi.tsx` to display additional data points for better insights. - Updated the database schema in `app.db` to reflect recent changes in the application logic. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
141 lines
5.2 KiB
TypeScript
141 lines
5.2 KiB
TypeScript
"use client";
|
||
|
||
import { useState } from "react";
|
||
import Link from "next/link";
|
||
import {
|
||
ArrowRight,
|
||
ChartNoAxesCombined,
|
||
Database,
|
||
ListChecks,
|
||
} from "lucide-react";
|
||
import { Button } from "@/components/ui/button";
|
||
import { TypingAnimation } from "@/components/ui/typing-animation";
|
||
|
||
/** Banner CTA'sının sihirbaz modalını açmak için yayınladığı olay. */
|
||
export const SIHIRBAZ_AC_EVENT = "kolaytercih:sihirbaz-ac";
|
||
|
||
const FUNNEL_METINLERI = [
|
||
"Sıralamana en uygun listeyi birlikte kuralım.",
|
||
"Geleceğin 24 satırlık bir plana sığar.",
|
||
"Gerçek YÖK Atlas verisiyle güvenle seç.",
|
||
"Her tercihinin bir nedeni olsun.",
|
||
"Riskleri biz hesaplayalım, kararı sen ver.",
|
||
"Sana uygun bölümler bir adım uzağında.",
|
||
"Dengeli bir liste, rahat bir tercih dönemi.",
|
||
"Tercihlerini şansa bırakma.",
|
||
"Doğru strateji, doğru yerleşme.",
|
||
"Hayalindeki bölüm için doğru plan.",
|
||
"Verilerle konuşan bir tercih listesi.",
|
||
"Üç adımda sana özel 24 tercih.",
|
||
"Tercih dönemini stressiz geçir.",
|
||
"Geçen yılın verisi, senin avantajın.",
|
||
"Güvenli ve iddialı tercihleri dengele.",
|
||
"Sıralaman ne söylüyor? Birlikte bakalım.",
|
||
"Her satırı senin için hesaplıyoruz.",
|
||
"Akıllı tercih, içi rahat bir bekleyiş.",
|
||
"Yerleşme ihtimalini görerek seç.",
|
||
"Sana özel liste birkaç dakikada hazır.",
|
||
"İlgi alanların ve şehrin, tek listede.",
|
||
"Boş kalan tercih, kaçan fırsattır.",
|
||
"Uzman işi bir liste, senin onayınla.",
|
||
"Kararını veriye dayandır.",
|
||
"Doğru tercih, doğru bilgiyle başlar.",
|
||
"Listeni bugün kur, için rahat olsun.",
|
||
"Yerleşme riskin renklerle önünde.",
|
||
"En önemli 24 kararını birlikte verelim.",
|
||
"Senin sıralaman, sana özel plan.",
|
||
"İyi bir liste, iyi bir başlangıçtır.",
|
||
];
|
||
|
||
/** Fisher-Yates ile kopya üzerinde karıştırır; her ziyarette farklı sıra. */
|
||
function karistir<T>(dizi: readonly T[]): T[] {
|
||
const kopya = [...dizi];
|
||
for (let i = kopya.length - 1; i > 0; i--) {
|
||
const j = Math.floor(Math.random() * (i + 1));
|
||
[kopya[i], kopya[j]] = [kopya[j], kopya[i]];
|
||
}
|
||
return kopya;
|
||
}
|
||
|
||
const KANITLAR = [
|
||
{ ikon: ListChecks, etiket: "24 tercih" },
|
||
{ ikon: ChartNoAxesCombined, etiket: "Risk analizi" },
|
||
{ ikon: Database, etiket: "YÖK Atlas verisi" },
|
||
];
|
||
|
||
/**
|
||
* Puan türü badge'lerinin hemen altında yaşayan satın alım funnel banner'ı.
|
||
* Rastgele sırayla yazılıp silinen samimi ve profesyonel funnel
|
||
* metinleriyle adayı sihirbaza; rapor zaten varsa kilitli listenin
|
||
* durduğu /listem paywall'ına iter.
|
||
* Tasarım: güven veren lacivert yüzey, veri kanıtları ve turuncu CTA.
|
||
*/
|
||
export function FunnelBanner({ hedefListem }: { hedefListem: boolean }) {
|
||
// İlk render'da bir kez karıştırılır; SSR boş metinle başladığı için
|
||
// hydration uyuşmazlığı yaratmaz.
|
||
const [metinler] = useState(() => karistir(FUNNEL_METINLERI));
|
||
|
||
const ctaSinif =
|
||
"h-12 w-full shrink-0 cursor-pointer rounded-xl bg-orange-500 px-5 font-semibold text-white shadow-[0_8px_24px_rgba(249,115,22,0.28)] transition-[background-color,transform,box-shadow] duration-200 hover:bg-orange-400 hover:shadow-[0_10px_28px_rgba(249,115,22,0.36)] active:scale-[0.98] focus-visible:ring-white/70 sm:w-auto";
|
||
|
||
return (
|
||
<section
|
||
aria-label="Tercih listeni kur"
|
||
className="relative mt-6 overflow-hidden rounded-3xl border border-blue-400/20 bg-[#0b1f3a] px-5 py-6 text-white shadow-[0_20px_50px_-28px_rgba(15,71,150,0.65)] sm:px-7 sm:py-7"
|
||
>
|
||
<div
|
||
aria-hidden
|
||
className="pointer-events-none absolute -right-16 -top-24 size-64 rounded-full bg-blue-500/25 blur-3xl"
|
||
/>
|
||
<div
|
||
aria-hidden
|
||
className="pointer-events-none absolute -bottom-20 left-1/3 size-48 rounded-full bg-cyan-400/10 blur-3xl"
|
||
/>
|
||
|
||
<div className="relative grid items-center gap-6 sm:grid-cols-[minmax(0,1fr)_auto] sm:gap-8">
|
||
<div className="min-w-0">
|
||
<TypingAnimation
|
||
as="h2"
|
||
words={metinler}
|
||
loop
|
||
typeSpeed={38}
|
||
deleteSpeed={18}
|
||
pauseDelay={2600}
|
||
className="min-h-[2.5em] max-w-2xl font-heading text-2xl font-bold leading-tight tracking-[-0.02em] sm:min-h-[1.25em] sm:text-3xl"
|
||
/>
|
||
|
||
<div className="mt-4 flex flex-wrap gap-x-4 gap-y-2">
|
||
{KANITLAR.map(({ ikon: Ikon, etiket }) => (
|
||
<span
|
||
key={etiket}
|
||
className="inline-flex items-center gap-1.5 text-xs font-medium text-slate-200"
|
||
>
|
||
<Ikon className="size-3.5 text-cyan-300" aria-hidden />
|
||
{etiket}
|
||
</span>
|
||
))}
|
||
</div>
|
||
</div>
|
||
|
||
{hedefListem ? (
|
||
<Button asChild size="lg" className={ctaSinif}>
|
||
<Link href="/listem">
|
||
Listeni aç
|
||
<ArrowRight className="size-4" aria-hidden />
|
||
</Link>
|
||
</Button>
|
||
) : (
|
||
<Button
|
||
size="lg"
|
||
onClick={() => window.dispatchEvent(new Event(SIHIRBAZ_AC_EVENT))}
|
||
className={ctaSinif}
|
||
>
|
||
Listemi oluştur
|
||
<ArrowRight className="size-4" aria-hidden />
|
||
</Button>
|
||
)}
|
||
</div>
|
||
</section>
|
||
);
|
||
}
|