UX/CRO denetimi uygulandı: değer-önce funnel, güven sayfaları, dev paneli

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 <noreply@anthropic.com>
This commit is contained in:
bilalgursen
2026-07-26 15:10:57 +03:00
parent d93cf742ff
commit 7288c926d0
40 changed files with 1666 additions and 162 deletions

127
src/lib/dev/actions.ts Normal file
View File

@@ -0,0 +1,127 @@
"use server";
// Dev süperadmin aksiyonları: yalnızca geliştirme ortamında çalışır.
// Panelden (src/components/dev/dev-panel.tsx) çağrılır; kullanıcı durumunu
// (kredi, paket, rapor) kurcalayıp tüm sayfaları gerçek akışta gezmeyi sağlar.
import { eq } from "drizzle-orm";
import { headers } from "next/headers";
import { appDb, schema } from "@/lib/appdb";
import { auth } from "@/lib/auth";
import { getCurrentUser } from "@/lib/session";
import { URUNLER } from "@/lib/credits";
function devKontrol() {
if (process.env.NODE_ENV === "production") {
throw new Error("Dev paneli yalnızca geliştirme ortamında çalışır.");
}
}
export type DevDurum =
| { girisli: false }
| {
girisli: true;
email: string;
kredi: number;
hasPaket: boolean;
raporVar: boolean;
sira: number | null;
tur: string | null;
};
export async function devDurum(): Promise<DevDurum> {
devKontrol();
const user = await getCurrentUser();
if (!user) return { girisli: false };
const rapor = await appDb.query.reports.findFirst({
where: eq(schema.reports.userId, user.id),
columns: { params: true },
});
const params = rapor?.params as { sira?: number; tur?: string } | undefined;
return {
girisli: true,
email: user.email,
kredi: user.creditBalance,
hasPaket: user.hasPaket,
raporVar: Boolean(rapor),
sira: params?.sira ?? null,
tur: params?.tur ?? null,
};
}
/** Magic link üretir ve (dev'de auth.ts'in yakaladığı) URL'i döner —
* panel bu URL'e giderek anında oturum açar; yeni e-posta = yeni kullanıcı. */
export async function devAnindaGiris(
email: string,
): Promise<{ ok: true; url: string } | { ok: false; error: string }> {
devKontrol();
const temiz = email.trim().toLowerCase();
if (!/^\S+@\S+\.\S+$/.test(temiz)) {
return { ok: false, error: "Geçerli bir e-posta gir." };
}
const g = globalThis as { __sonMagicLink?: string };
g.__sonMagicLink = undefined;
await auth.api.signInMagicLink({
body: { email: temiz, callbackURL: "/" },
headers: await headers(),
});
if (!g.__sonMagicLink) {
return { ok: false, error: "Magic link üretilemedi (auth.ts yakalayamadı)." };
}
return { ok: true, url: g.__sonMagicLink };
}
export async function devAyarla(ayar: {
kredi?: number;
hasPaket?: boolean;
}): Promise<{ ok: boolean; error?: string }> {
devKontrol();
const user = await getCurrentUser();
if (!user) return { ok: false, error: "Önce giriş yap." };
await appDb
.update(schema.user)
.set({
...(typeof ayar.kredi === "number"
? { creditBalance: Math.max(0, Math.round(ayar.kredi)) }
: {}),
...(typeof ayar.hasPaket === "boolean"
? { hasPaket: ayar.hasPaket }
: {}),
})
.where(eq(schema.user.id, user.id));
return { ok: true };
}
/** Raporu ve sohbet geçmişini siler — boş durumları test etmek için. */
export async function devRaporSil(): Promise<{ ok: boolean; error?: string }> {
devKontrol();
const user = await getCurrentUser();
if (!user) return { ok: false, error: "Önce giriş yap." };
await appDb
.delete(schema.reports)
.where(eq(schema.reports.userId, user.id));
await appDb
.delete(schema.chatMessages)
.where(eq(schema.chatMessages.userId, user.id));
return { ok: true };
}
/** Ödenmiş sahte sipariş oluşturur — /odeme/sonuc başarı ekranı için. */
export async function devSahteOdeme(): Promise<
{ ok: true; siparis: string } | { ok: false; error: string }
> {
devKontrol();
const user = await getCurrentUser();
if (!user) return { ok: false, error: "Önce giriş yap." };
const id = `dev-${crypto.randomUUID()}`;
await appDb.insert(schema.orders).values({
id,
userId: user.id,
product: "paket",
amountKurus: URUNLER.paket.amountKurus,
credits: URUNLER.paket.credits,
status: "paid",
createdAt: new Date(),
});
return { ok: true, siparis: id };
}