first commit

This commit is contained in:
Beyhan Oğur
2026-04-26 22:14:08 +03:00
commit b2825e1698
41 changed files with 14258 additions and 0 deletions

View File

@@ -0,0 +1,6 @@
import NextAuth from "next-auth";
import { authOptions } from "@/lib/auth-options";
const handler = NextAuth(authOptions);
export { handler as GET, handler as POST };

View File

@@ -0,0 +1,96 @@
import { NextRequest, NextResponse } from "next/server";
import { cookies } from "next/headers";
import {
COOKIE_ACCESS,
COOKIE_REFRESH,
COOKIE_OPTS,
ACCESS_MAX_AGE,
REFRESH_MAX_AGE,
} from "@/lib/auth-cookies";
const BASE_URL =
process.env.BASE_API_URL ??
process.env.NEXT_PUBLIC_BASE_API_URL ??
"http://127.0.0.1:8080";
export async function POST(request: NextRequest) {
try {
const body = await request.json();
const { email, password } = body as { email?: string; password?: string };
if (!email || !password) {
return NextResponse.json(
{ error: "E-posta ve şifre gerekli." },
{ status: 400 }
);
}
let res: Response;
try {
res = await fetch(`${BASE_URL}/api/v1/auth/login`, {
method: "POST",
headers: {
"Content-Type": "application/json",
accept: "application/json",
},
body: JSON.stringify({ email: String(email).trim(), password }),
});
} catch (fetchErr) {
const msg =
process.env.NODE_ENV === "development" && fetchErr instanceof Error
? `Backend erişilemedi: ${fetchErr.message} (URL: ${BASE_URL})`
: "Giriş servisi şu an kullanılamıyor.";
return NextResponse.json({ error: msg }, { status: 502 });
}
let data: unknown;
try {
const text = await res.text();
data = text ? JSON.parse(text) : {};
} catch {
data = {};
}
if (!res.ok) {
const message =
(data as { detail?: string })?.detail ?? "Giriş başarısız";
return NextResponse.json(
{ error: message },
{ status: res.status >= 400 ? res.status : 500 }
);
}
const access_token = (data as { access_token?: string })?.access_token;
const refresh_token = (data as { refresh_token?: string })?.refresh_token;
const user = (data as { user?: unknown })?.user;
if (!access_token || !refresh_token) {
return NextResponse.json(
{
error:
process.env.NODE_ENV === "development"
? "Backend token döndürmedi."
: "Giriş yanıtı geçersiz.",
},
{ status: 502 }
);
}
const cookieStore = await cookies();
cookieStore.set(COOKIE_ACCESS, access_token, {
...COOKIE_OPTS,
maxAge: ACCESS_MAX_AGE,
});
cookieStore.set(COOKIE_REFRESH, refresh_token, {
...COOKIE_OPTS,
maxAge: REFRESH_MAX_AGE,
});
return NextResponse.json({ user });
} catch (e) {
const message =
process.env.NODE_ENV === "development" && e instanceof Error
? e.message
: "Sunucu hatası.";
return NextResponse.json({ error: message }, { status: 500 });
}
}

View File

@@ -0,0 +1,10 @@
import { NextResponse } from "next/server";
import { cookies } from "next/headers";
import { COOKIE_ACCESS, COOKIE_REFRESH, COOKIE_OPTS } from "@/lib/auth-cookies";
export async function POST() {
const cookieStore = await cookies();
cookieStore.set(COOKIE_ACCESS, "", { ...COOKIE_OPTS, maxAge: 0 });
cookieStore.set(COOKIE_REFRESH, "", { ...COOKIE_OPTS, maxAge: 0 });
return NextResponse.json({ ok: true });
}

View File

@@ -0,0 +1,34 @@
import { NextResponse } from "next/server";
import { cookies } from "next/headers";
import { COOKIE_ACCESS } from "@/lib/auth-cookies";
const BASE_URL =
process.env.BASE_API_URL ?? process.env.NEXT_PUBLIC_BASE_API_URL ?? "http://127.0.0.1:8080";
export async function GET() {
const cookieStore = await cookies();
const accessToken = cookieStore.get(COOKIE_ACCESS)?.value;
if (!accessToken) {
return NextResponse.json({ loggedIn: false });
}
try {
const res = await fetch(`${BASE_URL}/api/v1/auth/me`, {
headers: {
accept: "application/json",
Authorization: `Bearer ${accessToken}`,
},
});
if (!res.ok) {
return NextResponse.json({ loggedIn: false });
}
const data = await res.json();
return NextResponse.json({
loggedIn: true,
user: (data as { user?: unknown }).user,
});
} catch {
return NextResponse.json({ loggedIn: false });
}
}

153
app/auth/login/page.tsx Normal file
View File

@@ -0,0 +1,153 @@
"use client";
import React, { useRef, useState } from "react";
import Link from "next/link";
import { useRouter } from "next/navigation";
import { Turnstile, type TurnstileRef } from "nextjs-turnstile";
import { Loader2 } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { loginViaCookie } from "@/lib/auth-api";
import { AuthSocialButtons } from "@/components/auth-social-buttons";
import { cn } from "@/lib/utils";
const TURNSTILE_SITE_KEY =
process.env.NEXT_PUBLIC_TURNSTILE_SITE_KEY ?? process.env.NEXT_PUBLIC_CLOUD_FLARE_SITE_KEY ?? "";
export default function LoginPage() {
const router = useRouter();
const turnstileRef = useRef<TurnstileRef>(null);
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [turnstileToken, setTurnstileToken] = useState<string | null>(null);
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState(false);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setError(null);
if (!email.trim()) {
setError("E-posta gerekli.");
return;
}
if (!password) {
setError("Şifre gerekli.");
return;
}
if (TURNSTILE_SITE_KEY && !turnstileToken) {
setError("Lütfen doğrulamayı tamamlayın.");
return;
}
setLoading(true);
try {
await loginViaCookie(email.trim(), password);
if (typeof window !== "undefined") window.dispatchEvent(new Event("auth-change"));
router.push("/");
router.refresh();
} catch (err) {
setError(err instanceof Error ? err.message : "Giriş yapılamadı.");
turnstileRef.current?.reset();
setTurnstileToken(null);
} finally {
setLoading(false);
}
};
return (
<div className="flex min-h-[calc(100vh-4rem)] items-center justify-center bg-zinc-50 px-4 py-12 dark:bg-neutral-950">
<div className="w-full max-w-md animate-in fade-in duration-300">
<div className="rounded-2xl border border-neutral-200 bg-white p-8 shadow-sm dark:border-neutral-800 dark:bg-neutral-900">
<h1 className="text-2xl font-bold text-neutral-900 dark:text-white">
Giriş yap
</h1>
<p className="mt-1 text-sm text-neutral-500 dark:text-neutral-400">
Hesabınıza giriş yapın
</p>
<form onSubmit={handleSubmit} className="mt-6 space-y-4">
{error && (
<div
className="rounded-lg bg-red-50 px-3 py-2 text-sm text-red-700 dark:bg-red-950/50 dark:text-red-400"
role="alert"
>
{error}
</div>
)}
<div className="space-y-2">
<Label htmlFor="login-email">E-posta</Label>
<Input
id="login-email"
type="email"
autoComplete="email"
placeholder="ornek@email.com"
value={email}
onChange={(e) => setEmail(e.target.value)}
disabled={loading}
className={cn(
"w-full rounded-lg border-neutral-200 dark:border-neutral-700"
)}
/>
</div>
<div className="space-y-2">
<Label htmlFor="login-password">Şifre</Label>
<Input
id="login-password"
type="password"
autoComplete="current-password"
placeholder="••••••••"
value={password}
onChange={(e) => setPassword(e.target.value)}
disabled={loading}
className="w-full rounded-lg border-neutral-200 dark:border-neutral-700"
/>
</div>
{TURNSTILE_SITE_KEY && (
<div className="flex justify-center [&_iframe]:max-w-full">
<Turnstile
ref={turnstileRef}
siteKey={TURNSTILE_SITE_KEY}
theme="auto"
onSuccess={setTurnstileToken}
onExpire={() => setTurnstileToken(null)}
onError={() => setTurnstileToken(null)}
/>
</div>
)}
<Button
type="submit"
className="w-full rounded-lg"
size="lg"
disabled={loading}
>
{loading ? (
<>
<Loader2 className="size-4 animate-spin" />
Giriş yapılıyor...
</>
) : (
"Giriş yap"
)}
</Button>
<AuthSocialButtons callbackUrl="/" disabled={loading} />
</form>
<p className="mt-6 text-center text-sm text-neutral-600 dark:text-neutral-400">
Hesabınız yok mu?{" "}
<Link
href="/auth/register"
className="font-medium text-blue-600 hover:underline dark:text-blue-400"
>
Kayıt olun
</Link>
</p>
</div>
</div>
</div>
);
}

227
app/auth/register/page.tsx Normal file
View File

@@ -0,0 +1,227 @@
"use client";
import React, { useRef, useState } from "react";
import Link from "next/link";
import { Turnstile, type TurnstileRef } from "nextjs-turnstile";
import { Loader2 } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { register } from "@/lib/auth-api";
import { AuthSocialButtons } from "@/components/auth-social-buttons";
import { cn } from "@/lib/utils";
const TURNSTILE_SITE_KEY =
process.env.NEXT_PUBLIC_TURNSTILE_SITE_KEY ?? process.env.NEXT_PUBLIC_CLOUD_FLARE_SITE_KEY ?? "";
export default function RegisterPage() {
const turnstileRef = useRef<TurnstileRef>(null);
const [email, setEmail] = useState("");
const [firstName, setFirstName] = useState("");
const [lastName, setLastName] = useState("");
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const [turnstileToken, setTurnstileToken] = useState<string | null>(null);
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState(false);
const [success, setSuccess] = useState<string | null>(null);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setError(null);
setSuccess(null);
if (!email.trim()) {
setError("E-posta gerekli.");
return;
}
if (!firstName.trim()) {
setError("Ad gerekli.");
return;
}
if (!lastName.trim()) {
setError("Soyad gerekli.");
return;
}
if (!username.trim()) {
setError("Kullanıcı adı gerekli.");
return;
}
if (password.length < 6) {
setError("Şifre en az 6 karakter olmalıdır.");
return;
}
if (TURNSTILE_SITE_KEY && !turnstileToken) {
setError("Lütfen doğrulamayı tamamlayın.");
return;
}
setLoading(true);
try {
const res = await register({
email: email.trim(),
first_name: firstName.trim(),
last_name: lastName.trim(),
username: username.trim(),
password,
});
setSuccess(
res.message ?? "Kayıt başarılı. Giriş yapmadan önce e-posta doğrulaması yapın."
);
turnstileRef.current?.reset();
setTurnstileToken(null);
} catch (err) {
setError(err instanceof Error ? err.message : "Kayıt oluşturulamadı.");
turnstileRef.current?.reset();
setTurnstileToken(null);
} finally {
setLoading(false);
}
};
return (
<div className="flex min-h-[calc(100vh-4rem)] items-center justify-center bg-zinc-50 px-4 py-12 dark:bg-neutral-950">
<div className="w-full max-w-md animate-in fade-in duration-300">
<div className="rounded-2xl border border-neutral-200 bg-white p-8 shadow-sm dark:border-neutral-800 dark:bg-neutral-900">
<h1 className="text-2xl font-bold text-neutral-900 dark:text-white">
Kayıt ol
</h1>
<p className="mt-1 text-sm text-neutral-500 dark:text-neutral-400">
Yeni hesap oluşturun
</p>
<form onSubmit={handleSubmit} className="mt-6 space-y-4">
{error && (
<div
className="rounded-lg bg-red-50 px-3 py-2 text-sm text-red-700 dark:bg-red-950/50 dark:text-red-400"
role="alert"
>
{error}
</div>
)}
{success && (
<div
className="rounded-lg bg-green-50 px-3 py-2 text-sm text-green-800 dark:bg-green-950/50 dark:text-green-300"
role="status"
>
{success}
</div>
)}
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="reg-first">Ad</Label>
<Input
id="reg-first"
type="text"
autoComplete="given-name"
placeholder="Ad"
value={firstName}
onChange={(e) => setFirstName(e.target.value)}
disabled={loading}
className="rounded-lg border-neutral-200 dark:border-neutral-700"
/>
</div>
<div className="space-y-2">
<Label htmlFor="reg-last">Soyad</Label>
<Input
id="reg-last"
type="text"
autoComplete="family-name"
placeholder="Soyad"
value={lastName}
onChange={(e) => setLastName(e.target.value)}
disabled={loading}
className="rounded-lg border-neutral-200 dark:border-neutral-700"
/>
</div>
</div>
<div className="space-y-2">
<Label htmlFor="reg-username">Kullanıcı adı</Label>
<Input
id="reg-username"
type="text"
autoComplete="username"
placeholder="kullanici_adi"
value={username}
onChange={(e) => setUsername(e.target.value)}
disabled={loading}
className={cn(
"w-full rounded-lg border-neutral-200 dark:border-neutral-700"
)}
/>
</div>
<div className="space-y-2">
<Label htmlFor="reg-email">E-posta</Label>
<Input
id="reg-email"
type="email"
autoComplete="email"
placeholder="ornek@email.com"
value={email}
onChange={(e) => setEmail(e.target.value)}
disabled={loading}
className="w-full rounded-lg border-neutral-200 dark:border-neutral-700"
/>
</div>
<div className="space-y-2">
<Label htmlFor="reg-password">Şifre</Label>
<Input
id="reg-password"
type="password"
autoComplete="new-password"
placeholder="En az 6 karakter"
value={password}
onChange={(e) => setPassword(e.target.value)}
disabled={loading}
className="w-full rounded-lg border-neutral-200 dark:border-neutral-700"
/>
</div>
{TURNSTILE_SITE_KEY && (
<div className="flex justify-center [&_iframe]:max-w-full">
<Turnstile
ref={turnstileRef}
siteKey={TURNSTILE_SITE_KEY}
theme="auto"
onSuccess={setTurnstileToken}
onExpire={() => setTurnstileToken(null)}
onError={() => setTurnstileToken(null)}
/>
</div>
)}
<Button
type="submit"
className="w-full rounded-lg"
size="lg"
disabled={loading}
>
{loading ? (
<>
<Loader2 className="size-4 animate-spin" />
Kaydediliyor...
</>
) : (
"Kayıt ol"
)}
</Button>
<AuthSocialButtons callbackUrl="/" disabled={loading} />
</form>
<p className="mt-6 text-center text-sm text-neutral-600 dark:text-neutral-400">
Zaten hesabınız var mı?{" "}
<Link
href="/auth/login"
className="font-medium text-blue-600 hover:underline dark:text-blue-400"
>
Giriş yapın
</Link>
</p>
</div>
</div>
</div>
);
}

BIN
app/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

126
app/globals.css Normal file
View File

@@ -0,0 +1,126 @@
@import "tailwindcss";
@import "tw-animate-css";
@import "shadcn/tailwind.css";
@custom-variant dark (&:is(.dark *));
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--font-sans: var(--font-geist-sans);
--font-mono: var(--font-geist-mono);
--color-sidebar-ring: var(--sidebar-ring);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar: var(--sidebar);
--color-chart-5: var(--chart-5);
--color-chart-4: var(--chart-4);
--color-chart-3: var(--chart-3);
--color-chart-2: var(--chart-2);
--color-chart-1: var(--chart-1);
--color-ring: var(--ring);
--color-input: var(--input);
--color-border: var(--border);
--color-destructive: var(--destructive);
--color-accent-foreground: var(--accent-foreground);
--color-accent: var(--accent);
--color-muted-foreground: var(--muted-foreground);
--color-muted: var(--muted);
--color-secondary-foreground: var(--secondary-foreground);
--color-secondary: var(--secondary);
--color-primary-foreground: var(--primary-foreground);
--color-primary: var(--primary);
--color-popover-foreground: var(--popover-foreground);
--color-popover: var(--popover);
--color-card-foreground: var(--card-foreground);
--color-card: var(--card);
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
--radius-2xl: calc(var(--radius) + 8px);
--radius-3xl: calc(var(--radius) + 12px);
--radius-4xl: calc(var(--radius) + 16px);
}
:root {
--radius: 0.625rem;
--background: oklch(1 0 0);
--foreground: oklch(0.145 0 0);
--card: oklch(1 0 0);
--card-foreground: oklch(0.145 0 0);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.145 0 0);
--primary: oklch(0.205 0 0);
--primary-foreground: oklch(0.985 0 0);
--secondary: oklch(0.97 0 0);
--secondary-foreground: oklch(0.205 0 0);
--muted: oklch(0.97 0 0);
--muted-foreground: oklch(0.556 0 0);
--accent: oklch(0.97 0 0);
--accent-foreground: oklch(0.205 0 0);
--destructive: oklch(0.577 0.245 27.325);
--border: oklch(0.922 0 0);
--input: oklch(0.922 0 0);
--ring: oklch(0.708 0 0);
--chart-1: oklch(0.646 0.222 41.116);
--chart-2: oklch(0.6 0.118 184.704);
--chart-3: oklch(0.398 0.07 227.392);
--chart-4: oklch(0.828 0.189 84.429);
--chart-5: oklch(0.769 0.188 70.08);
--sidebar: oklch(0.985 0 0);
--sidebar-foreground: oklch(0.145 0 0);
--sidebar-primary: oklch(0.205 0 0);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.97 0 0);
--sidebar-accent-foreground: oklch(0.205 0 0);
--sidebar-border: oklch(0.922 0 0);
--sidebar-ring: oklch(0.708 0 0);
}
.dark {
--background: oklch(0.145 0 0);
--foreground: oklch(0.985 0 0);
--card: oklch(0.205 0 0);
--card-foreground: oklch(0.985 0 0);
--popover: oklch(0.205 0 0);
--popover-foreground: oklch(0.985 0 0);
--primary: oklch(0.922 0 0);
--primary-foreground: oklch(0.205 0 0);
--secondary: oklch(0.269 0 0);
--secondary-foreground: oklch(0.985 0 0);
--muted: oklch(0.269 0 0);
--muted-foreground: oklch(0.708 0 0);
--accent: oklch(0.269 0 0);
--accent-foreground: oklch(0.985 0 0);
--destructive: oklch(0.704 0.191 22.216);
--border: oklch(1 0 0 / 10%);
--input: oklch(1 0 0 / 15%);
--ring: oklch(0.556 0 0);
--chart-1: oklch(0.488 0.243 264.376);
--chart-2: oklch(0.696 0.17 162.48);
--chart-3: oklch(0.769 0.188 70.08);
--chart-4: oklch(0.627 0.265 303.9);
--chart-5: oklch(0.645 0.246 16.439);
--sidebar: oklch(0.205 0 0);
--sidebar-foreground: oklch(0.985 0 0);
--sidebar-primary: oklch(0.488 0.243 264.376);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.269 0 0);
--sidebar-accent-foreground: oklch(0.985 0 0);
--sidebar-border: oklch(1 0 0 / 10%);
--sidebar-ring: oklch(0.556 0 0);
}
@layer base {
* {
@apply border-border outline-ring/50;
}
body {
@apply bg-background text-foreground;
}
}

49
app/layout.tsx Normal file
View File

@@ -0,0 +1,49 @@
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";
import Header from "@/components/header";
import { ThemeProvider } from "@/components/theme-provider";
import { SessionProvider } from "@/components/providers/session-provider";
const geistSans = Geist({
variable: "--font-geist-sans",
subsets: ["latin"],
});
const geistMono = Geist_Mono({
variable: "--font-geist-mono",
subsets: ["latin"],
});
export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en" suppressHydrationWarning>
<head>
<script
dangerouslySetInnerHTML={{
__html: `(function(){var s=localStorage.getItem('techwix-theme');var d=s==='dark'||(!s&&typeof window!=='undefined'&&window.matchMedia('(prefers-color-scheme: dark)').matches);document.documentElement.classList.toggle('dark',d);})();`,
}}
/>
</head>
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
>
<ThemeProvider>
<SessionProvider>
<Header />
<main className="min-h-screen">{children}</main>
</SessionProvider>
</ThemeProvider>
</body>
</html>
);
}

11
app/page.tsx Normal file
View File

@@ -0,0 +1,11 @@
import BlogHero from "@/components/blog-hero";
import BlogContent from "@/components/blog-content";
export default function Home() {
return (
<div className="bg-zinc-50 font-sans dark:bg-black">
<BlogHero />
<BlogContent />
</div>
);
}

107
app/profile/page.tsx Normal file
View File

@@ -0,0 +1,107 @@
"use client";
import React, { useEffect, useState } from "react";
import { useSession } from "next-auth/react";
import Link from "next/link";
import { getCookieSession, type AuthUser } from "@/lib/auth-api";
export default function ProfilePage() {
const { data: session, status } = useSession();
const [cookieUser, setCookieUser] = useState<AuthUser | null | undefined>(undefined);
useEffect(() => {
getCookieSession().then((s) =>
setCookieUser(s.loggedIn && s.user ? s.user : null)
);
}, []);
const loading = status === "loading" || cookieUser === undefined;
if (loading) {
return (
<div className="container mx-auto flex min-h-[60vh] items-center justify-center px-4">
<p className="text-neutral-500">Yükleniyor...</p>
</div>
);
}
if (session?.user) {
const u = session.user;
return (
<div className="container mx-auto max-w-lg px-4 py-12">
<h1 className="text-2xl font-bold text-neutral-900 dark:text-white">
Profil
</h1>
<div className="mt-6 rounded-xl border border-neutral-200 bg-white p-6 dark:border-neutral-800 dark:bg-neutral-900">
<p className="text-sm text-neutral-500 dark:text-neutral-400">
{u.email ?? u.name ?? "Oturum açıldı"}
</p>
{u.name && (
<p className="mt-1 font-medium text-neutral-900 dark:text-white">
{u.name}
</p>
)}
</div>
<p className="mt-4 text-sm text-neutral-500 dark:text-neutral-400">
<Link href="/" className="text-blue-600 hover:underline dark:text-blue-400">
Ana sayfaya dön
</Link>
</p>
</div>
);
}
if (cookieUser) {
const u = cookieUser;
return (
<div className="container mx-auto max-w-lg px-4 py-12">
<h1 className="text-2xl font-bold text-neutral-900 dark:text-white">
Profil
</h1>
<div className="mt-6 rounded-xl border border-neutral-200 bg-white p-6 dark:border-neutral-800 dark:bg-neutral-900">
<p className="text-sm text-neutral-500 dark:text-neutral-400">
{u.email}
</p>
<p className="mt-1 font-medium text-neutral-900 dark:text-white">
{u.first_name} {u.last_name}
</p>
{u.username && (
<p className="mt-1 text-sm text-neutral-600 dark:text-neutral-400">
@{u.username}
</p>
)}
</div>
<p className="mt-4 text-sm text-neutral-500 dark:text-neutral-400">
<Link href="/" className="text-blue-600 hover:underline dark:text-blue-400">
Ana sayfaya dön
</Link>
</p>
</div>
);
}
return (
<div className="container mx-auto max-w-lg px-4 py-12">
<h1 className="text-2xl font-bold text-neutral-900 dark:text-white">
Profil
</h1>
<p className="mt-4 text-neutral-600 dark:text-neutral-400">
Bu sayfayı görmek için giriş yapmalısınız.
</p>
<div className="mt-4 flex gap-3">
<Link
href="/auth/login"
className="text-sm font-medium text-blue-600 hover:underline dark:text-blue-400"
>
Giriş yap
</Link>
<Link
href="/auth/register"
className="text-sm font-medium text-blue-600 hover:underline dark:text-blue-400"
>
Kayıt ol
</Link>
</div>
</div>
);
}