Some checks failed
Deploy / deploy (push) Has been cancelled
Profil çerezinin SSR'da kullanılmasını sağlayarak, sonuç sayfasındaki yönlendirmeleri güncelledik. Artık profil bilgileri URL parametreleri yerine çerezden alınarak işleniyor. Ayrıca, profil çerezi olmayan kullanıcılar için yönlendirme mantığı iyileştirildi. Çeşitli bileşenlerdeki URL oluşturma fonksiyonları güncellenerek, profil bilgileri çerezden okunacak şekilde düzenlendi. Bu değişiklikler, kullanıcı deneyimini artırmayı ve daha tutarlı bir veri akışı sağlamayı hedefliyor.
539 lines
20 KiB
TypeScript
539 lines
20 KiB
TypeScript
"use client";
|
||
|
||
// Sihirbazın 3 adımlık seçim gövdesi. Hem ana sayfadaki hero modalında hem de
|
||
// /sonuc'taki sihirbaz modalında kullanılır; seçim state'ini kendi tutar,
|
||
// tamamlanınca onTamamla(secimler) çağırır.
|
||
//
|
||
// Adımlar kademeli süzülür: 2. adımın illeri seçili kategorilere, 3. adımın
|
||
// üniversite tipi sayıları kategori+il'e göre /api/facetler'den tazelenir.
|
||
|
||
import { useEffect, useMemo, useState } from "react";
|
||
import { ArrowLeft, ArrowRight, Search, X } from "lucide-react";
|
||
import { useReducedMotion } from "motion/react";
|
||
import useMeasure from "react-use-measure";
|
||
import { toast } from "sonner";
|
||
import { cities } from "turkey-map-react/lib/data";
|
||
import { TransitionPanel } from "@/components/core/transition-panel";
|
||
import { Button } from "@/components/ui/button";
|
||
import { IlSecimHaritasi } from "./il-secim-haritasi";
|
||
import { normalizeIlAdi } from "@/lib/harita";
|
||
import { onbellekliJsonGetir } from "@/lib/istek-cache";
|
||
import { olay } from "@/lib/analitik";
|
||
import { trBaslikDuzeni } from "@/lib/slug";
|
||
import type { SihirbazFacetleri } from "@/types/yokatlas";
|
||
import { ONCELIKLER, UNIVERSITE_TIPI_ETIKET, type SihirbazSecimleri, type UniversiteTipi } from "@/features/sihirbaz/sihirbaz-sabitler";
|
||
|
||
export function Cip({
|
||
secili,
|
||
onClick,
|
||
children,
|
||
kucuk = false,
|
||
}: {
|
||
secili: boolean;
|
||
onClick: () => void;
|
||
children: React.ReactNode;
|
||
kucuk?: boolean;
|
||
}) {
|
||
const boyut = kucuk ? "px-2.5 py-1 text-xs" : "px-3.5 py-2 text-sm";
|
||
return (
|
||
<button
|
||
type="button"
|
||
onClick={onClick}
|
||
aria-pressed={secili}
|
||
className={
|
||
secili
|
||
? `cursor-pointer rounded-full border border-primary bg-primary font-medium text-white transition-colors duration-200 ${boyut}`
|
||
: `cursor-pointer rounded-full border border-slate-200 bg-white text-slate-700 transition-colors duration-200 hover:border-slate-300 hover:bg-slate-50 ${boyut}`
|
||
}
|
||
>
|
||
{children}
|
||
</button>
|
||
);
|
||
}
|
||
|
||
// Haritada karşılığı olan iller (KKTC/yurtdışı kampüsler çip olarak kalır)
|
||
const HARITA_ILLERI = new Set(cities.map((c) => normalizeIlAdi(c.name)));
|
||
|
||
export function SihirbazAdimlar({
|
||
sira,
|
||
tur,
|
||
facetler,
|
||
baslangicSecimler,
|
||
sonButonEtiketi,
|
||
onTamamla,
|
||
}: {
|
||
sira: number;
|
||
tur: string;
|
||
facetler: SihirbazFacetleri;
|
||
baslangicSecimler?: SihirbazSecimleri | null;
|
||
sonButonEtiketi: string;
|
||
onTamamla: (secimler: SihirbazSecimleri) => void;
|
||
}) {
|
||
const [adim, setAdim] = useState(0);
|
||
const [yon, setYon] = useState(1);
|
||
const [gecisBasladi, setGecisBasladi] = useState(false);
|
||
const [panelRef, panelOlculeri] = useMeasure();
|
||
const hareketiAzalt = useReducedMotion();
|
||
const [kategoriler, setKategoriler] = useState<string[]>(
|
||
baslangicSecimler?.kategoriler ?? [],
|
||
);
|
||
const [iller, setIller] = useState<string[]>(baslangicSecimler?.iller ?? []);
|
||
const [universiteTipi, setUniversiteTipi] = useState<UniversiteTipi>(
|
||
baslangicSecimler?.universiteTipi ?? "farketmez",
|
||
);
|
||
const [oncelikler, setOncelikler] = useState<string[]>(
|
||
baslangicSecimler?.oncelikler ?? [],
|
||
);
|
||
|
||
// Kademeli facet'ler: kategoriler daima ilk yükten gelir; il ve üniversite
|
||
// tipi sayıları adıma girerken o anki seçimlerle tazelenir. Fetch başarısız
|
||
// olursa eldeki (daha geniş) liste kalır — sihirbaz asla kilitlenmez.
|
||
const [canliFacetler, setCanliFacetler] = useState(facetler);
|
||
// 81 elemanlı listeyi her çip tıklamasında yeniden sıralamamak/filtrelememek
|
||
// için memo; haritaya giden dizinin referansı da sabit kalır (gereksiz
|
||
// IlSecimHaritasi render'ı biter).
|
||
const siraliIller = useMemo(
|
||
() => [...canliFacetler.iller].sort((a, b) => b.adet - a.adet),
|
||
[canliFacetler.iller],
|
||
);
|
||
// Türkiye illeri ana çip grubu; KKTC/yurtdışı kampüsler haritada olmadığı
|
||
// için ayrı alt grupta listelenir.
|
||
const turkiyeIlleri = useMemo(
|
||
() => siraliIller.filter((i) => HARITA_ILLERI.has(i.il)),
|
||
[siraliIller],
|
||
);
|
||
const disIller = useMemo(
|
||
() => siraliIller.filter((i) => !HARITA_ILLERI.has(i.il)),
|
||
[siraliIller],
|
||
);
|
||
// İl araması: Türkçe büyük/küçük harf ve şapkalı harf farklarına duyarsız
|
||
// eşleşme için iki taraf da normalizeIlAdi'dan geçirilir.
|
||
const [ilArama, setIlArama] = useState("");
|
||
const ilAramaNorm = normalizeIlAdi(ilArama.trim());
|
||
// 81 çip ilk bakışta boğucu; varsayılan görünüm ilk 10 il + "+N il daha"
|
||
// rozeti. Arama açıkken kırpma yok (arama zaten görünümü daraltıyor).
|
||
const [tumIllerAcik, setTumIllerAcik] = useState(false);
|
||
const gorunenTurkiyeIlleri = useMemo(
|
||
() =>
|
||
ilAramaNorm
|
||
? turkiyeIlleri.filter((i) =>
|
||
normalizeIlAdi(i.il).includes(ilAramaNorm),
|
||
)
|
||
: turkiyeIlleri,
|
||
[turkiyeIlleri, ilAramaNorm],
|
||
);
|
||
const IL_ON_IZLEME = 10;
|
||
const ilListesiKirpik =
|
||
!ilAramaNorm &&
|
||
!tumIllerAcik &&
|
||
gorunenTurkiyeIlleri.length > IL_ON_IZLEME;
|
||
const listelenenTurkiyeIlleri = ilListesiKirpik
|
||
? gorunenTurkiyeIlleri.slice(0, IL_ON_IZLEME)
|
||
: gorunenTurkiyeIlleri;
|
||
const gorunenDisIller = useMemo(
|
||
() =>
|
||
ilAramaNorm
|
||
? disIller.filter((i) => normalizeIlAdi(i.il).includes(ilAramaNorm))
|
||
: disIller,
|
||
[disIller, ilAramaNorm],
|
||
);
|
||
const haritaIlleri = useMemo(
|
||
() => canliFacetler.iller.filter((i) => HARITA_ILLERI.has(i.il)),
|
||
[canliFacetler.iller],
|
||
);
|
||
const [facetYukleniyor, setFacetYukleniyor] = useState(false);
|
||
useEffect(() => {
|
||
if (adim === 0) return;
|
||
const ctrl = new AbortController();
|
||
const params = new URLSearchParams({ sira: String(sira), tur });
|
||
for (const k of kategoriler) params.append("kategori", k);
|
||
if (adim === 2) for (const i of iller) params.append("il", i);
|
||
// eslint-disable-next-line react-hooks/set-state-in-effect -- fetch başlangıç işareti
|
||
setFacetYukleniyor(true);
|
||
// Cache'li: 2↔3 adımları arasında gidip gelmek aynı URL'i yeniden çekmez
|
||
onbellekliJsonGetir<{ facetler: SihirbazFacetleri }>(
|
||
`/api/facetler?${params}`,
|
||
{ signal: ctrl.signal },
|
||
)
|
||
.then((veri) => {
|
||
setCanliFacetler((mevcut) => ({
|
||
...mevcut,
|
||
iller: veri.facetler.iller,
|
||
uniturler: veri.facetler.uniturler,
|
||
}));
|
||
// Seçili tip daralan filtrede yoksa (ör. seçilen ilde vakıf kalmadı)
|
||
// görünmez bir seçim bırakma
|
||
setUniversiteTipi((tip) =>
|
||
tip !== "farketmez" &&
|
||
!veri.facetler.uniturler.some((u) => u.grup === tip)
|
||
? "farketmez"
|
||
: tip,
|
||
);
|
||
})
|
||
.catch(() => {})
|
||
.finally(() => setFacetYukleniyor(false));
|
||
return () => ctrl.abort();
|
||
// Seçimler yalnızca önceki adımlarda değişebildiği için adım geçişinde
|
||
// okumak yeterli; her çip tıklamasında istek atmayalım.
|
||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||
}, [adim]);
|
||
|
||
function listeDegistir(
|
||
liste: string[],
|
||
setListe: (v: string[]) => void,
|
||
deger: string,
|
||
maks?: number,
|
||
) {
|
||
if (liste.includes(deger)) {
|
||
setListe(liste.filter((x) => x !== deger));
|
||
} else {
|
||
if (maks && liste.length >= maks) {
|
||
toast.info(`En fazla ${maks} seçebilirsin.`);
|
||
return;
|
||
}
|
||
setListe([...liste, deger]);
|
||
}
|
||
}
|
||
|
||
const adimGecerli = adim === 0 ? kategoriler.length > 0 : true;
|
||
|
||
function adimaGit(yeniAdim: number) {
|
||
setYon(yeniAdim > adim ? 1 : -1);
|
||
setGecisBasladi(true);
|
||
setAdim(yeniAdim);
|
||
// Yalnızca ileri geçişler huni adımı sayılır; geri dönüşler tekrar saymasın
|
||
if (yeniAdim > adim) olay("sihirbaz_adim", { adim: yeniAdim + 1 });
|
||
}
|
||
|
||
const panelYuksekligi =
|
||
gecisBasladi && panelOlculeri.height > 0
|
||
? panelOlculeri.height
|
||
: "auto";
|
||
|
||
return (
|
||
<div className="flex flex-col gap-5">
|
||
<div className="flex items-center gap-1.5">
|
||
{[0, 1, 2].map((i) => (
|
||
<span
|
||
key={i}
|
||
className={
|
||
i === adim
|
||
? "h-1.5 w-6 rounded-full bg-primary transition-[width,background-color] duration-200 ease-out"
|
||
: i < adim
|
||
? "h-1.5 w-3 rounded-full bg-primary/50 transition-[width,background-color] duration-200 ease-out"
|
||
: "h-1.5 w-1.5 rounded-full bg-slate-200 transition-[width,background-color] duration-200 ease-out"
|
||
}
|
||
/>
|
||
))}
|
||
<span className="ml-2 text-xs font-medium text-slate-400">
|
||
Adım {adim + 1}/3
|
||
</span>
|
||
</div>
|
||
|
||
<TransitionPanel
|
||
activeIndex={adim}
|
||
custom={yon}
|
||
variants={{
|
||
enter: (gecisYonu: number) => ({
|
||
x: hareketiAzalt ? 0 : gecisYonu > 0 ? "100%" : "-100%",
|
||
opacity: 0,
|
||
height: panelYuksekligi,
|
||
position: "relative",
|
||
}),
|
||
center: {
|
||
zIndex: 1,
|
||
x: 0,
|
||
opacity: 1,
|
||
height: panelYuksekligi,
|
||
},
|
||
exit: (gecisYonu: number) => ({
|
||
zIndex: 0,
|
||
x: hareketiAzalt ? 0 : gecisYonu < 0 ? "100%" : "-100%",
|
||
opacity: 0,
|
||
position: "absolute",
|
||
top: 0,
|
||
left: 0,
|
||
width: "100%",
|
||
}),
|
||
}}
|
||
transition={
|
||
hareketiAzalt
|
||
? // Azaltılmış harekette yalnızca kısa çapraz solma; height/x
|
||
// varsayılan spring'e düşüp oynamasın diye açıkça sıfırlanır.
|
||
{
|
||
opacity: { duration: 0.12 },
|
||
x: { duration: 0 },
|
||
height: { duration: 0 },
|
||
}
|
||
: {
|
||
x: { type: "spring", stiffness: 300, damping: 30 },
|
||
height: { type: "spring", stiffness: 300, damping: 30 },
|
||
opacity: { duration: 0.2 },
|
||
}
|
||
}
|
||
>
|
||
<div ref={panelRef} className="px-px pb-2">
|
||
<h3 className="font-heading text-lg font-bold">
|
||
Hangi alanlara ilgi duyuyorsun?
|
||
</h3>
|
||
<p className="mt-1 text-sm text-slate-500">
|
||
Sıralamanla ulaşabileceğin alanlar. En az bir tane seç.
|
||
</p>
|
||
<div className="mt-4 flex flex-wrap gap-2">
|
||
{facetler.kategoriler.map((k) => (
|
||
<Cip
|
||
key={k.ad}
|
||
secili={kategoriler.includes(k.ad)}
|
||
onClick={() => listeDegistir(kategoriler, setKategoriler, k.ad)}
|
||
>
|
||
{k.ad}
|
||
<span className="ml-1.5 text-xs opacity-70">{k.adet}</span>
|
||
</Cip>
|
||
))}
|
||
</div>
|
||
</div>
|
||
<div ref={panelRef}>
|
||
<h3 className="font-heading text-lg font-bold">
|
||
Nerede okumak istersin?
|
||
</h3>
|
||
<p className="mt-1 text-sm text-slate-500">
|
||
En fazla 5 il seçebilirsin. İstersen boş bırak, fark etmez.
|
||
</p>
|
||
|
||
<div className="mt-4 flex items-center gap-2 rounded-full border border-slate-200 bg-white px-3.5 py-2 transition-colors focus-within:border-slate-300">
|
||
<Search className="size-4 shrink-0 text-slate-400" aria-hidden />
|
||
<input
|
||
type="text"
|
||
value={ilArama}
|
||
onChange={(e) => setIlArama(e.target.value)}
|
||
placeholder="İl ara..."
|
||
aria-label="İl ara"
|
||
autoComplete="off"
|
||
className="min-w-0 flex-1 bg-transparent text-sm text-slate-950 outline-none placeholder:text-slate-400"
|
||
/>
|
||
{ilArama ? (
|
||
<button
|
||
type="button"
|
||
onClick={() => setIlArama("")}
|
||
aria-label="Aramayı temizle"
|
||
className="flex size-5 shrink-0 cursor-pointer items-center justify-center rounded-full text-slate-400 transition-colors hover:bg-slate-100 hover:text-slate-700"
|
||
>
|
||
<X className="size-3.5" aria-hidden />
|
||
</button>
|
||
) : null}
|
||
</div>
|
||
|
||
{/* Çipler birincil seçim yolu (haritadaki küçük iller mobilde
|
||
güvenilir dokunma hedefi değil); iller seçili kategorilere göre
|
||
süzülmüş, program sayısına göre sıralı listelenir. Varsayılan
|
||
görünüm ilk 10 il; kalanı "+N il daha" rozetiyle açılır. Arama
|
||
yalnızca görünümü daraltır, seçimleri etkilemez ve kırpmayı
|
||
devre dışı bırakır. */}
|
||
<div
|
||
className={`mt-3 flex max-h-56 flex-wrap gap-1.5 overflow-y-auto sm:max-h-none sm:overflow-visible ${
|
||
facetYukleniyor ? "animate-pulse opacity-60" : ""
|
||
}`}
|
||
>
|
||
{listelenenTurkiyeIlleri.map((i) => (
|
||
<Cip
|
||
key={i.il}
|
||
kucuk
|
||
secili={iller.includes(i.il)}
|
||
onClick={() => listeDegistir(iller, setIller, i.il, 5)}
|
||
>
|
||
{trBaslikDuzeni(i.il)}
|
||
<span className="ml-1 text-[10px] opacity-70">{i.adet}</span>
|
||
</Cip>
|
||
))}
|
||
{ilListesiKirpik ? (
|
||
<button
|
||
type="button"
|
||
onClick={() => setTumIllerAcik(true)}
|
||
className="cursor-pointer rounded-full border border-dashed border-slate-300 bg-white px-2.5 py-1 text-xs font-medium text-slate-500 transition-colors duration-200 hover:border-slate-400 hover:bg-slate-50 hover:text-slate-700"
|
||
>
|
||
+{gorunenTurkiyeIlleri.length - IL_ON_IZLEME} il daha
|
||
</button>
|
||
) : null}
|
||
{!ilListesiKirpik &&
|
||
tumIllerAcik &&
|
||
!ilAramaNorm &&
|
||
gorunenTurkiyeIlleri.length > IL_ON_IZLEME ? (
|
||
<button
|
||
type="button"
|
||
onClick={() => setTumIllerAcik(false)}
|
||
className="cursor-pointer rounded-full border border-dashed border-slate-300 bg-white px-2.5 py-1 text-xs font-medium text-slate-500 transition-colors duration-200 hover:border-slate-400 hover:bg-slate-50 hover:text-slate-700"
|
||
>
|
||
Daha az göster
|
||
</button>
|
||
) : null}
|
||
</div>
|
||
|
||
{ilAramaNorm &&
|
||
gorunenTurkiyeIlleri.length === 0 &&
|
||
gorunenDisIller.length === 0 ? (
|
||
<p className="mt-3 text-sm text-slate-500">
|
||
“{ilArama.trim()}” ile eşleşen il yok.
|
||
</p>
|
||
) : null}
|
||
|
||
{gorunenDisIller.length > 0 ? (
|
||
<div className="mt-4">
|
||
<p className="mb-2 text-xs font-semibold tracking-wide text-slate-400 uppercase">
|
||
KKTC & Yurt Dışı
|
||
</p>
|
||
<div
|
||
className={`flex flex-wrap gap-1.5 ${
|
||
facetYukleniyor ? "animate-pulse opacity-60" : ""
|
||
}`}
|
||
>
|
||
{gorunenDisIller.map((i) => (
|
||
<Cip
|
||
key={i.il}
|
||
kucuk
|
||
secili={iller.includes(i.il)}
|
||
onClick={() => listeDegistir(iller, setIller, i.il, 5)}
|
||
>
|
||
{trBaslikDuzeni(i.il)}
|
||
<span className="ml-1 text-[10px] opacity-70">{i.adet}</span>
|
||
</Cip>
|
||
))}
|
||
</div>
|
||
</div>
|
||
) : null}
|
||
|
||
{iller.length > 0 ? (
|
||
<div className="mt-3 flex flex-wrap items-center gap-2">
|
||
<span className="text-xs font-semibold text-slate-400">
|
||
Seçtiklerin:
|
||
</span>
|
||
{iller.map((il) => (
|
||
<button
|
||
key={il}
|
||
type="button"
|
||
onClick={() => listeDegistir(iller, setIller, il)}
|
||
className="inline-flex cursor-pointer items-center gap-1 rounded-full bg-orange-500 px-3 py-1.5 text-xs font-medium text-white transition-colors duration-200 hover:bg-orange-600"
|
||
>
|
||
{trBaslikDuzeni(il)}
|
||
<X className="size-3" aria-hidden />
|
||
</button>
|
||
))}
|
||
</div>
|
||
) : null}
|
||
|
||
{/* Harita geniş ekranda görsel destek olarak kalır */}
|
||
<div className="mt-4 hidden sm:block">
|
||
<IlSecimHaritasi
|
||
iller={haritaIlleri}
|
||
secili={iller}
|
||
onToggle={(il) => listeDegistir(iller, setIller, il, 5)}
|
||
/>
|
||
</div>
|
||
</div>
|
||
<div ref={panelRef} className="space-y-5">
|
||
<div>
|
||
<h3 className="font-heading text-lg font-bold">
|
||
Üniversite tipi & öncelikler
|
||
</h3>
|
||
<p className="mt-1 text-sm text-slate-500">
|
||
Bunlar listenin dengesini belirler.
|
||
</p>
|
||
</div>
|
||
<div>
|
||
<p className="mb-2 text-xs font-semibold tracking-wide text-slate-400 uppercase">
|
||
Üniversite tipi
|
||
</p>
|
||
<div
|
||
className={`flex flex-wrap gap-2 ${
|
||
facetYukleniyor ? "animate-pulse opacity-60" : ""
|
||
}`}
|
||
>
|
||
{canliFacetler.uniturler.map((u) => (
|
||
<Cip
|
||
key={u.grup}
|
||
secili={universiteTipi === u.grup}
|
||
onClick={() =>
|
||
setUniversiteTipi(
|
||
universiteTipi === u.grup ? "farketmez" : u.grup,
|
||
)
|
||
}
|
||
>
|
||
{UNIVERSITE_TIPI_ETIKET[u.grup]}
|
||
<span className="ml-1.5 text-xs opacity-70">{u.adet}</span>
|
||
</Cip>
|
||
))}
|
||
<Cip
|
||
secili={universiteTipi === "farketmez"}
|
||
onClick={() => setUniversiteTipi("farketmez")}
|
||
>
|
||
Fark etmez
|
||
</Cip>
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p className="mb-2 text-xs font-semibold tracking-wide text-slate-400 uppercase">
|
||
Senin için önemli olan
|
||
</p>
|
||
<div className="flex flex-wrap gap-2">
|
||
{ONCELIKLER.map((o) => (
|
||
<Cip
|
||
key={o}
|
||
secili={oncelikler.includes(o)}
|
||
onClick={() => listeDegistir(oncelikler, setOncelikler, o)}
|
||
>
|
||
{o}
|
||
</Cip>
|
||
))}
|
||
</div>
|
||
<p className="mt-2 text-xs text-slate-500">
|
||
Öncelikler yapay zekânın gerekçelerine ve program seçimine yön
|
||
verir; listeyi daraltmaz.
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</TransitionPanel>
|
||
|
||
<div className="flex items-center justify-between border-t border-slate-100 pt-4">
|
||
<Button
|
||
type="button"
|
||
variant="ghost"
|
||
onClick={() => adimaGit(adim - 1)}
|
||
disabled={adim === 0}
|
||
className={adim === 0 ? "invisible" : "cursor-pointer"}
|
||
>
|
||
<ArrowLeft className="size-4" aria-hidden />
|
||
Geri
|
||
</Button>
|
||
{adim < 2 ? (
|
||
<Button
|
||
type="button"
|
||
onClick={() => adimaGit(adim + 1)}
|
||
disabled={!adimGecerli}
|
||
className="cursor-pointer bg-orange-500 text-white transition-colors duration-200 hover:bg-orange-600"
|
||
>
|
||
Devam
|
||
<ArrowRight className="size-4" aria-hidden />
|
||
</Button>
|
||
) : (
|
||
<Button
|
||
type="button"
|
||
onClick={() => {
|
||
olay("sihirbaz_tamamlandi", {
|
||
kategori_sayisi: kategoriler.length,
|
||
il_sayisi: iller.length,
|
||
uni_tipi: universiteTipi,
|
||
oncelik_sayisi: oncelikler.length,
|
||
});
|
||
onTamamla({ kategoriler, iller, universiteTipi, oncelikler });
|
||
}}
|
||
disabled={kategoriler.length === 0}
|
||
className="cursor-pointer bg-orange-500 text-white transition-colors duration-200 hover:bg-orange-600"
|
||
>
|
||
{sonButonEtiketi}
|
||
<ArrowRight className="size-4" aria-hidden />
|
||
</Button>
|
||
)}
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|