feat: kimlik doğrulama, ödeme, kredi ve AI rapor sihirbazı ekle
- better-auth ile giriş/oturum yönetimi (src/lib/auth, giris sayfası) - iyzico ödeme entegrasyonu ve paket satın alma akışı - kredi sistemi ve uygulama veritabanı şeması (drizzle) - AI destekli rapor üretimi ve tercih sihirbazı - rapor yazdırma sayfası ve site header/kullanıcı menüsü Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
15
src/components/header-gate.tsx
Normal file
15
src/components/header-gate.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
"use client";
|
||||
|
||||
import { usePathname } from "next/navigation";
|
||||
|
||||
// Header'ın gizleneceği rotalar (tam eşleşme veya alt yol)
|
||||
const HIDDEN_PREFIXES = ["/giris"];
|
||||
|
||||
export function HeaderGate({ children }: { children: React.ReactNode }) {
|
||||
const pathname = usePathname();
|
||||
const gizli = HIDDEN_PREFIXES.some(
|
||||
(p) => pathname === p || pathname.startsWith(`${p}/`),
|
||||
);
|
||||
if (gizli) return null;
|
||||
return <>{children}</>;
|
||||
}
|
||||
83
src/components/rapor-listesi.tsx
Normal file
83
src/components/rapor-listesi.tsx
Normal file
@@ -0,0 +1,83 @@
|
||||
import { Rocket, Scale, ShieldCheck } from "lucide-react";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import type { RaporSonuc } from "@/lib/ai/rapor";
|
||||
|
||||
export const DILIM_STIL: Record<
|
||||
string,
|
||||
{ label: string; cls: string; icon: typeof Rocket }
|
||||
> = {
|
||||
hayal: { label: "Hayal", cls: "bg-orange-100 text-orange-700", icon: Rocket },
|
||||
dengeli: { label: "Dengeli", cls: "bg-blue-100 text-blue-700", icon: Scale },
|
||||
garanti: {
|
||||
label: "Garanti",
|
||||
cls: "bg-emerald-100 text-emerald-700",
|
||||
icon: ShieldCheck,
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* 24'lük listenin gövdesi (genel değerlendirme + tercih kartları).
|
||||
* Hem sonuç modalında hem PDF/yazdır sayfasında kullanılır.
|
||||
*/
|
||||
export function RaporListesi({ rapor }: { rapor: RaporSonuc }) {
|
||||
return (
|
||||
<div>
|
||||
<div className="rounded-2xl border border-slate-200 bg-white p-6">
|
||||
<h2 className="font-heading text-lg font-bold">Genel değerlendirme</h2>
|
||||
<p className="mt-2 whitespace-pre-line text-sm leading-relaxed text-slate-700">
|
||||
{rapor.genelDegerlendirme}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<ol className="mt-6 space-y-3">
|
||||
{rapor.tercihler.map((t) => {
|
||||
const p = rapor.programlar[t.programId];
|
||||
const stil = DILIM_STIL[t.dilim];
|
||||
return (
|
||||
<li
|
||||
key={t.sira}
|
||||
className="rounded-2xl border border-slate-200 bg-white p-5"
|
||||
>
|
||||
<div className="flex flex-wrap items-center gap-3">
|
||||
<span className="flex size-8 items-center justify-center rounded-full bg-slate-100 font-heading text-sm font-bold">
|
||||
{t.sira}
|
||||
</span>
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="font-semibold">{p?.isim ?? t.programId}</p>
|
||||
<p className="text-sm text-slate-500">
|
||||
{p?.universite} · {p?.il ?? "—"}
|
||||
{p?.unitur ? ` · ${p.unitur}` : ""}
|
||||
</p>
|
||||
</div>
|
||||
<Badge variant="secondary" className={stil.cls}>
|
||||
<stil.icon className="size-3" aria-hidden />
|
||||
{stil.label}
|
||||
</Badge>
|
||||
</div>
|
||||
<dl className="mt-3 grid gap-2 text-sm sm:grid-cols-3">
|
||||
<div className="rounded-lg bg-slate-50 p-3">
|
||||
<dt className="text-xs font-semibold text-slate-500">
|
||||
Neden listede?
|
||||
</dt>
|
||||
<dd className="mt-1 text-slate-700">{t.gerekce}</dd>
|
||||
</div>
|
||||
<div className="rounded-lg bg-slate-50 p-3">
|
||||
<dt className="text-xs font-semibold text-slate-500">
|
||||
Risk notu
|
||||
</dt>
|
||||
<dd className="mt-1 text-slate-700">{t.riskNotu}</dd>
|
||||
</div>
|
||||
<div className="rounded-lg bg-slate-50 p-3">
|
||||
<dt className="text-xs font-semibold text-slate-500">
|
||||
4 yıllık trend
|
||||
</dt>
|
||||
<dd className="mt-1 text-slate-700">{t.trendOzeti}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ol>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
24
src/components/sign-out-button.tsx
Normal file
24
src/components/sign-out-button.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
"use client";
|
||||
|
||||
import { useRouter } from "next/navigation";
|
||||
import { LogOut } from "lucide-react";
|
||||
import { authClient } from "@/lib/auth-client";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
export function SignOutButton() {
|
||||
const router = useRouter();
|
||||
return (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
className="size-12 cursor-pointer rounded-full border border-slate-200 bg-white text-slate-600 transition-colors duration-200 hover:border-slate-300 hover:bg-slate-50 hover:text-slate-900"
|
||||
aria-label="Çıkış yap"
|
||||
onClick={async () => {
|
||||
await authClient.signOut();
|
||||
router.refresh();
|
||||
}}
|
||||
>
|
||||
<LogOut className="size-5" aria-hidden />
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
39
src/components/site-header.tsx
Normal file
39
src/components/site-header.tsx
Normal file
@@ -0,0 +1,39 @@
|
||||
import Link from "next/link";
|
||||
import { GraduationCap } from "lucide-react";
|
||||
import { UserNav } from "@/components/user-nav";
|
||||
|
||||
const navLinks = [
|
||||
{ href: "/#nasil-calisir", label: "Nasıl çalışır?" },
|
||||
{ href: "/#karsilastirma", label: "Karşılaştır" },
|
||||
{ href: "/#sss", label: "SSS" },
|
||||
];
|
||||
|
||||
export function SiteHeader() {
|
||||
return (
|
||||
<header className="sticky top-6 z-50 mt-6 print:hidden">
|
||||
<div className="mx-auto flex h-16 max-w-6xl items-center justify-between gap-3 px-4">
|
||||
<Link
|
||||
href="/"
|
||||
className="group flex items-center gap-3 font-heading text-xl font-bold"
|
||||
>
|
||||
<span className="flex size-11 items-center justify-center rounded-full bg-primary text-white transition-transform duration-200 group-hover:-rotate-6">
|
||||
<GraduationCap className="size-6" aria-hidden />
|
||||
</span>
|
||||
KolayTercih
|
||||
</Link>
|
||||
<nav className="hidden items-center gap-1 rounded-full border border-slate-200 bg-white p-1.5 text-base font-medium text-slate-600 sm:flex">
|
||||
{navLinks.map((link) => (
|
||||
<Link
|
||||
key={link.href}
|
||||
href={link.href}
|
||||
className="rounded-full px-5 py-2 transition-colors duration-200 hover:bg-slate-100 hover:text-slate-900"
|
||||
>
|
||||
{link.label}
|
||||
</Link>
|
||||
))}
|
||||
</nav>
|
||||
<UserNav />
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
104
src/components/ui/skiper-ui/skiper41.tsx
Normal file
104
src/components/ui/skiper-ui/skiper41.tsx
Normal file
@@ -0,0 +1,104 @@
|
||||
import React from "react";
|
||||
|
||||
type ProgressiveBlurProps = {
|
||||
className?: string;
|
||||
backgroundColor?: string;
|
||||
position?: "top" | "bottom";
|
||||
height?: string;
|
||||
blurAmount?: string;
|
||||
};
|
||||
|
||||
const ProgressiveBlur = ({
|
||||
className = "",
|
||||
backgroundColor = "#f5f4f3",
|
||||
position = "top",
|
||||
height = "150px",
|
||||
blurAmount = "4px",
|
||||
}: ProgressiveBlurProps) => {
|
||||
const isTop = position === "top";
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`pointer-events-none absolute left-0 w-full select-none ${className}`}
|
||||
style={{
|
||||
[isTop ? "top" : "bottom"]: 0,
|
||||
height,
|
||||
background: isTop
|
||||
? `linear-gradient(to top, transparent, ${backgroundColor})`
|
||||
: `linear-gradient(to bottom, transparent, ${backgroundColor})`,
|
||||
maskImage: isTop
|
||||
? `linear-gradient(to bottom, ${backgroundColor} 50%, transparent)`
|
||||
: `linear-gradient(to top, ${backgroundColor} 50%, transparent)`,
|
||||
WebkitBackdropFilter: `blur(${blurAmount})`,
|
||||
backdropFilter: `blur(${blurAmount})`,
|
||||
WebkitUserSelect: "none",
|
||||
userSelect: "none",
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const Skiper41 = () => {
|
||||
return (
|
||||
<div className="relative flex h-full w-full flex-col items-center justify-center bg-[#f5f4f3] text-black/40">
|
||||
<ProgressiveBlur position="top" backgroundColor="#f5f4f3" />
|
||||
<ProgressiveBlur position="bottom" backgroundColor="#f5f4f3" />
|
||||
|
||||
<div className="flex h-[calc(100vh-1rem)] w-full flex-col items-center overflow-scroll">
|
||||
<div className="mt-42 grid content-start justify-items-center gap-6 text-center text-black">
|
||||
<span className="relative max-w-[12ch] text-xs uppercase leading-tight opacity-40 after:absolute after:left-1/2 after:top-full after:h-16 after:w-px after:bg-gradient-to-b after:from-white after:to-black after:content-['']">
|
||||
Scroll down to see the effect
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="mt-24 w-full max-w-lg space-y-20 px-5 text-justify">
|
||||
{Array.from({ length: 10 }).map((_, index) => (
|
||||
<div key={index}>
|
||||
Lorem ipsum dolor sit amet consectetur adipisicing elit.
|
||||
Obcaecati, reiciendis eum vitae nostrum, temporibus repudiandae
|
||||
voluptatibus, natus iure ipsa velit odit quibusdam illum. Quaerat
|
||||
cumque laudantium libero reprehenderit perferendis quo nulla
|
||||
voluptate? Repellat tenetur labore exercitationem dicta libero
|
||||
voluptate suscipit, iusto ea assumenda. Ipsa enim, quidem atque
|
||||
modi error eaque, debitis perferendis, hic iste libero dignissimos
|
||||
ea! Quod inventore beatae aspernatur nulla rem perferendis aperiam
|
||||
at debitis delectus odit quia animi ex mollitia vero molestias
|
||||
itaque deleniti, quos exercitationem consequatur assumenda dolor?
|
||||
Quod reiciendis in similique reprehenderit commodi quo blanditiis
|
||||
nobis hic ea optio illum placeat officia alias quasi autem earum
|
||||
quos obcaecati, voluptatum corporis quisquam. Quisquam iste, quas
|
||||
explicabo omnis harum aut quam adipisci, voluptatem saepe
|
||||
accusantium doloribus repellendus amet culpa magnam ex et dolores
|
||||
accusamus commodi facere aliquam voluptatum alias? Officia
|
||||
expedita ut vel? Beatae deserunt sequi id eos libero suscipit
|
||||
totam cum, sed architecto atque quisquam et incidunt quod fuga
|
||||
ullam repellat assumenda quos ab, voluptatum sint nesciunt? Ad
|
||||
sapiente est laborum quam sint eius sequi. Eum, veniam
|
||||
dignissimos.
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export { ProgressiveBlur, Skiper41 };
|
||||
|
||||
/**
|
||||
* Skiper 41 Canvas_Landing_004 — React + framer motion
|
||||
* Inspired by and adapted from https://devouringdetails.com/
|
||||
* We respect the original creators. This is an inspired rebuild with our own taste and does not claim any ownership.
|
||||
* These animations aren’t associated with the devouringdetails.com . They’re independent recreations meant to study interaction design
|
||||
*
|
||||
* License & Usage:
|
||||
* - Free to use and modify in both personal and commercial projects.
|
||||
* - Attribution to Skiper UI is required when using the free version.
|
||||
* - No attribution required with Skiper UI Pro.
|
||||
*
|
||||
* Feedback and contributions are welcome.
|
||||
*
|
||||
* Author: @gurvinder-singh02
|
||||
* Website: https://gxuri.me
|
||||
* Twitter: https://x.com/Gur__vi
|
||||
*/
|
||||
34
src/components/user-nav.tsx
Normal file
34
src/components/user-nav.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import Link from "next/link";
|
||||
import { Coins } from "lucide-react";
|
||||
import { getCurrentUser } from "@/lib/session";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { SignOutButton } from "./sign-out-button";
|
||||
|
||||
export async function UserNav() {
|
||||
const u = await getCurrentUser();
|
||||
|
||||
if (!u) {
|
||||
return (
|
||||
<Button
|
||||
asChild
|
||||
className="h-12 cursor-pointer rounded-full bg-slate-900 px-7 text-base font-medium text-white transition-colors duration-200 hover:bg-slate-700"
|
||||
>
|
||||
<Link href="/giris">Giriş yap</Link>
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
<Link
|
||||
href="/paket"
|
||||
className="inline-flex h-12 items-center gap-2 whitespace-nowrap rounded-full border border-slate-200 bg-white px-5 text-sm font-semibold text-slate-700 transition-colors duration-200 hover:border-slate-300 hover:bg-slate-50"
|
||||
title="Kredilerin — yüklemek için tıkla"
|
||||
>
|
||||
<Coins className="size-4 text-amber-500" aria-hidden />
|
||||
{u.creditBalance} kredi
|
||||
</Link>
|
||||
<SignOutButton />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user