fix: denetim bulguları — çip adı sahte 'tercihlerin değişmiş', çekmece kartı, senkron riski
- tercihFarki eski 'İş garantisi' parametresini yeni ada eşleyerek kıyaslar; ad değişikliği 3 kredilik güncelleme çağrısı üretmesin - çekmece kartı yalnız profil yokken sıra ister, butonu sıra kapısını açar (profilsiz ziyaretçi /listem giriş duvarına gönderilmiyordu → çıkmaz) - sunucuylaBirlestir risksiz gelen kayıtların riskini doldurur - /paket'teki fiyat kıyasına kaynak satırı Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -52,6 +52,9 @@ export default function PaketPage({
|
||||
Tercih danışmanları bu dönemde 1.000–3.000 TL alıyor. KolayTercih
|
||||
aynı işi gerçek YÖK Atlas verisiyle, dakikalar içinde yapar.
|
||||
</p>
|
||||
<p className="mt-2 text-xs text-slate-500">
|
||||
Danışman fiyat aralığı: Armut ortalama fiyatı, Eylül 2026.
|
||||
</p>
|
||||
</Parallax>
|
||||
|
||||
<Suspense fallback={<PaketSatinAlSkeleton />}>
|
||||
|
||||
@@ -46,7 +46,11 @@ import { RISK_ETIKET, type RiskSeviyesi } from "@/lib/risk";
|
||||
import { bolumBazAdi, turkishSlugify } from "@/lib/slug";
|
||||
import { olay } from "@/lib/analitik";
|
||||
import { CihazCifti, IsletimSistemiLogosu } from "./cihaz-ikonlari";
|
||||
import { sonucaGitIste } from "../hooks/use-tercih-profili";
|
||||
import {
|
||||
profilToplamaAc,
|
||||
sonucaGitIste,
|
||||
useTercihProfili,
|
||||
} from "../hooks/use-tercih-profili";
|
||||
import { useOturumDurumu } from "../liste-oturum-sinyali";
|
||||
import { useGirisUrl } from "../liste-giris-url";
|
||||
import {
|
||||
@@ -92,8 +96,10 @@ export function ListeCekmecesi() {
|
||||
|
||||
const doluluk = Math.round((liste.length / MANUEL_LISTE_MAX) * 100);
|
||||
// Profilsiz + ile eklenenlerin riski henüz hesaplanmamıştır; alt kart
|
||||
// olmayan bir analizi "hazır" diye duyurmasın.
|
||||
const riskEksik = liste.some((t) => t.risk == null);
|
||||
// olmayan bir analizi "hazır" diye duyurmasın. Profil varken null risk,
|
||||
// programın taban sırası olmadığındandır — sıra istemek yanlış olur.
|
||||
const profil = useTercihProfili();
|
||||
const riskEksik = !profil && liste.some((t) => t.risk == null);
|
||||
|
||||
function programaEkleGit() {
|
||||
cekmeceKapat();
|
||||
@@ -278,6 +284,19 @@ export function ListeCekmecesi() {
|
||||
: "Her seçimin yerleşme riski hazır — son kararı sen ver."}
|
||||
</p>
|
||||
</div>
|
||||
{riskEksik ? (
|
||||
<Button
|
||||
size="lg"
|
||||
className="shrink-0 cursor-pointer bg-orange-500 text-white transition-colors duration-200 hover:bg-orange-600"
|
||||
onClick={() => {
|
||||
cekmeceKapat();
|
||||
profilToplamaAc(null, true, "arti_toast");
|
||||
}}
|
||||
>
|
||||
Sıralamamı gir
|
||||
<ArrowRight data-icon="inline-end" />
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
asChild
|
||||
size="lg"
|
||||
@@ -288,6 +307,7 @@ export function ListeCekmecesi() {
|
||||
<ArrowRight data-icon="inline-end" />
|
||||
</Link>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</DrawerFooter>
|
||||
</DrawerContent>
|
||||
|
||||
@@ -152,6 +152,9 @@ export function sunucuylaBirlestir(sunucuItems: ManuelTercih[]): ManuelTercih[]
|
||||
liste = [...liste, ...ekler].slice(0, MANUEL_LISTE_MAX);
|
||||
kaydet();
|
||||
yayinla();
|
||||
// Başka cihazda profilsiz eklenmiş kayıtlar risksiz gelir; profil lokalde
|
||||
// zaten varsa PROFIL_DEGISTI_EVENT yayılmaz, riski burada doldur.
|
||||
if (liste.some((t) => t.risk == null)) riskleriYenile();
|
||||
return liste;
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
import {
|
||||
PUAN_TURU_ETIKET,
|
||||
UNIVERSITE_TIPI_ETIKET,
|
||||
oncelikNormalize,
|
||||
type TercihProfili,
|
||||
} from "@/features/sihirbaz/sihirbaz-sabitler";
|
||||
import type { RaporParams } from "./types/rapor";
|
||||
@@ -69,7 +70,9 @@ export function tercihFarki(
|
||||
) {
|
||||
farklar.push("universiteTipi");
|
||||
}
|
||||
if (kumeFarkli(params.oncelikler, s.oncelikler)) farklar.push("oncelikler");
|
||||
if (kumeFarkli(params.oncelikler?.map(oncelikNormalize), s.oncelikler)) {
|
||||
farklar.push("oncelikler");
|
||||
}
|
||||
|
||||
return farklar;
|
||||
}
|
||||
@@ -107,7 +110,8 @@ export function farkSatirlari(
|
||||
yeni: UNIVERSITE_TIPI_ETIKET[s.universiteTipi],
|
||||
};
|
||||
case "oncelikler":
|
||||
return { ...ortak, eski: liste(params.oncelikler), yeni: liste(s.oncelikler) };
|
||||
return { ...ortak, eski: liste(params.oncelikler?.map(oncelikNormalize)),
|
||||
yeni: liste(s.oncelikler), };
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -111,7 +111,7 @@ export function sihirbazDogrula(girdi: unknown): SihirbazSecimleri | null {
|
||||
p.oncelikler
|
||||
// Eski kayıtlı profiller: çip adı "İş garantisi" idi (vaat diliyle
|
||||
// çeliştiği için değişti); seçim kaybolmasın diye yeni ada eşlenir.
|
||||
.map((x) => (x === "İş garantisi" ? "İş bulma kolaylığı" : x))
|
||||
.map(oncelikNormalize)
|
||||
.filter(
|
||||
(x): x is string =>
|
||||
typeof x === "string" &&
|
||||
@@ -124,6 +124,15 @@ export function sihirbazDogrula(girdi: unknown): SihirbazSecimleri | null {
|
||||
return { kategoriler, iller, universiteTipi, oncelikler };
|
||||
}
|
||||
|
||||
/**
|
||||
* Eski çip adı "İş garantisi" → "İş bulma kolaylığı". Kayıtlı profil ve eski
|
||||
* rapor parametreleri eski adı taşıyabilir; karşılaştırma/gösterim öncesi
|
||||
* yeni ada eşlenir ki ad değişikliği "tercihlerin değişmiş" diye okunmasın.
|
||||
*/
|
||||
export function oncelikNormalize<T>(x: T): T | string {
|
||||
return x === "İş garantisi" ? "İş bulma kolaylığı" : x;
|
||||
}
|
||||
|
||||
/** 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;
|
||||
|
||||
Reference in New Issue
Block a user