refactor: sihirbaz feature'ı — lib/sihirbaz bölündü, bileşenler taşındı (Faz 6)
- lib/sihirbaz.ts ikiye ayrıldı: sihirbaz-sabitler.ts (şema + doğrulayıcılar + event/storage sabitleri + sonucHref/secimOzeti, izomorfik) ve sihirbaz-profil.ts (localStorage oku/yaz/sil); 25 tüketicinin importu sembol bazında bölünerek güncellendi, localStorage anahtar/event değerleri birebir aynı - sihirbaz-adimlar(+lazy), hero-form, cta-sira-form, il-secim-haritasi ve /sonuc'un sihirbaz-modal/sihirbaz-cagri-karti/sira-gerekli bileşenleri features/sihirbaz/components/ altına taşındı - sihirbaz-queries.ts ve async SihirbazBolumu sarmalayıcısı /sonuc sayfa dönüşümüyle birlikte Faz 9'da gelecek (o zamana dek ölü kod olurlardı) - Davranış değişikliği yok Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
157
src/features/sihirbaz/sihirbaz-sabitler.ts
Normal file
157
src/features/sihirbaz/sihirbaz-sabitler.ts
Normal file
@@ -0,0 +1,157 @@
|
||||
// Sihirbazın seçim şeması, doğrulaması ve prompt özeti — izomorfik katman:
|
||||
// hem client bileşenleri hem server action'ları/AI motoru buradan okur.
|
||||
// localStorage'a dokunan profil yardımcıları sihirbaz-profil.ts'te yaşar.
|
||||
// Kategoriler/iller sıralamaya göre dinamik üretilir (bkz. rankWindowFacets);
|
||||
// öncelikler statik kalır (veriye bağlı değil, tercih niyeti).
|
||||
|
||||
import { KATEGORILER } from "@/lib/kategoriler";
|
||||
import type { UniturGrubu } from "@/types/yokatlas";
|
||||
|
||||
export const ONCELIKLER = [
|
||||
"İş garantisi",
|
||||
"Yüksek maaş potansiyeli",
|
||||
"Sevdiğim işi yapmak",
|
||||
"Akademik kariyer",
|
||||
"Yurtdışı imkânı",
|
||||
"Prestijli üniversite",
|
||||
"Kolay yerleşme",
|
||||
] as const;
|
||||
|
||||
export const UNIVERSITE_TIPI_ETIKET: Record<UniturGrubu | "farketmez", string> = {
|
||||
devlet: "Devlet",
|
||||
vakif: "Vakıf (burslu olursa)",
|
||||
farketmez: "Fark etmez",
|
||||
};
|
||||
|
||||
export type UniversiteTipi = UniturGrubu | "farketmez";
|
||||
|
||||
export const PUAN_TURU_ETIKET: Record<string, string> = {
|
||||
say: "Sayısal",
|
||||
ea: "Eşit Ağırlık",
|
||||
soz: "Sözel",
|
||||
dil: "Dil",
|
||||
tyt: "TYT",
|
||||
};
|
||||
|
||||
// Girişsiz kullanıcının modalda yaptığı seçimler, giriş sonrası /sonuc'a
|
||||
// dönene kadar burada bekler.
|
||||
export const SIHIRBAZ_STORAGE_KEY = "kolaytercih.sihirbaz";
|
||||
|
||||
// storage olayı yalnızca diğer sekmelerde tetiklenir; aynı sekmedeki
|
||||
// aboneler (ör. navbar sıralama rozeti) bu olayla haberdar edilir.
|
||||
export const PROFIL_DEGISTI_EVENT = "kolaytercih:profil-degisti";
|
||||
|
||||
// Arama modalındaki funnel CTA'sı /sonuc üzerindeyken sihirbaz
|
||||
// orkestratörünü (SihirbazBolumu) bu olayla açar.
|
||||
export const SIHIRBAZ_AC_EVENT = "kolaytercih:sihirbaz-ac";
|
||||
|
||||
export interface SihirbazSecimleri {
|
||||
kategoriler: string[];
|
||||
iller: string[];
|
||||
universiteTipi: UniversiteTipi;
|
||||
oncelikler: string[];
|
||||
}
|
||||
|
||||
export interface TercihProfili {
|
||||
sira: number;
|
||||
tur: string;
|
||||
secimler: SihirbazSecimleri;
|
||||
}
|
||||
|
||||
const UNI_TIPLERI: UniversiteTipi[] = ["devlet", "vakif", "farketmez"];
|
||||
|
||||
/**
|
||||
* Client'tan gelen seçimleri güvene alır. Kategoriler KATEGORILER allowlist'ine
|
||||
* göre süzülür; iller serbest string (yalnızca sanity — DB IN filtresi zaten
|
||||
* eşleşmeyeni yok sayar), en fazla 5 il; öncelikler ONCELIKLER'e göre süzülür.
|
||||
* En az bir kategori zorunlu.
|
||||
*/
|
||||
export function sihirbazDogrula(girdi: unknown): SihirbazSecimleri | null {
|
||||
if (typeof girdi !== "object" || girdi === null) return null;
|
||||
const p = girdi as Record<string, unknown>;
|
||||
|
||||
const kategoriler = Array.isArray(p.kategoriler)
|
||||
? [
|
||||
...new Set(
|
||||
p.kategoriler.filter(
|
||||
(x): x is string =>
|
||||
typeof x === "string" &&
|
||||
(KATEGORILER as readonly string[]).includes(x),
|
||||
),
|
||||
),
|
||||
]
|
||||
: [];
|
||||
if (kategoriler.length === 0) return null;
|
||||
|
||||
const iller = Array.isArray(p.iller)
|
||||
? [
|
||||
...new Set(
|
||||
p.iller.filter(
|
||||
(x): x is string => typeof x === "string" && x.trim().length > 0,
|
||||
),
|
||||
),
|
||||
].slice(0, 5)
|
||||
: [];
|
||||
|
||||
const universiteTipi =
|
||||
typeof p.universiteTipi === "string" &&
|
||||
UNI_TIPLERI.includes(p.universiteTipi as UniversiteTipi)
|
||||
? (p.universiteTipi as UniversiteTipi)
|
||||
: "farketmez";
|
||||
|
||||
const oncelikler = Array.isArray(p.oncelikler)
|
||||
? [
|
||||
...new Set(
|
||||
p.oncelikler.filter(
|
||||
(x): x is string =>
|
||||
typeof x === "string" &&
|
||||
(ONCELIKLER as readonly string[]).includes(x),
|
||||
),
|
||||
),
|
||||
]
|
||||
: [];
|
||||
|
||||
return { kategoriler, iller, universiteTipi, oncelikler };
|
||||
}
|
||||
|
||||
/** localStorage'taki ortak sıra + sihirbaz profilini güvenle doğrular. */
|
||||
export function tercihProfiliDogrula(girdi: unknown): TercihProfili | null {
|
||||
if (typeof girdi !== "object" || girdi === null) return null;
|
||||
const p = girdi as Record<string, unknown>;
|
||||
const sira = typeof p.sira === "number" ? p.sira : Number(p.sira);
|
||||
if (!Number.isFinite(sira) || sira < 1 || sira > 4_000_000) return null;
|
||||
if (typeof p.tur !== "string" || p.tur.trim().length === 0) return null;
|
||||
const secimler = sihirbazDogrula(p.secimler);
|
||||
if (!secimler) return null;
|
||||
return { sira, tur: p.tur, secimler };
|
||||
}
|
||||
|
||||
/** Profildeki sıra/tur ile sonuç sayfası URL'i — tüm uygulama aynı sorguyu kullanır. */
|
||||
export function sonucHref(
|
||||
profil: Pick<TercihProfili, "sira" | "tur">,
|
||||
ekstra?: Record<string, string>,
|
||||
): string {
|
||||
const q = new URLSearchParams({
|
||||
sira: String(profil.sira),
|
||||
tur: profil.tur,
|
||||
...ekstra,
|
||||
});
|
||||
return `/sonuc?${q}`;
|
||||
}
|
||||
|
||||
/** Prompt'a enjekte edilecek Türkçe özet. */
|
||||
export function secimOzeti(secimler: SihirbazSecimleri): string {
|
||||
const satirlar = [
|
||||
`- İlgi alanları: ${secimler.kategoriler.join(", ")}`,
|
||||
];
|
||||
if (secimler.iller.length > 0) {
|
||||
satirlar.push(`- Tercih ettiği iller: ${secimler.iller.join(", ")}`);
|
||||
}
|
||||
satirlar.push(
|
||||
`- Üniversite tipi tercihi: ${UNIVERSITE_TIPI_ETIKET[secimler.universiteTipi]}`,
|
||||
);
|
||||
if (secimler.oncelikler.length > 0) {
|
||||
satirlar.push(`- Öncelikleri: ${secimler.oncelikler.join(", ")}`);
|
||||
}
|
||||
return satirlar.join("\n");
|
||||
}
|
||||
Reference in New Issue
Block a user