Add @number-flow/react dependency and integrate SiteTopBanner component into layout
All checks were successful
Deploy / deploy (push) Successful in 11m11s

This commit is contained in:
bilalgursen
2026-07-30 12:24:37 +03:00
parent e74710a20a
commit 65d630d754
4 changed files with 172 additions and 0 deletions

View File

@@ -14,6 +14,7 @@
"@anthropic-ai/sdk": "^0.112.4",
"@cds/city": "^1.1.0",
"@libsql/client": "^0.17.4",
"@number-flow/react": "^0.6.2",
"@react-pdf/renderer": "^4.5.1",
"better-auth": "^1.6.23",
"better-sqlite3": "^12.11.1",

28
pnpm-lock.yaml generated
View File

@@ -17,6 +17,9 @@ importers:
'@libsql/client':
specifier: ^0.17.4
version: 0.17.4
'@number-flow/react':
specifier: ^0.6.2
version: 0.6.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
'@react-pdf/renderer':
specifier: ^4.5.1
version: 4.5.1(react@19.2.4)
@@ -1240,6 +1243,12 @@ packages:
resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==}
engines: {node: '>=12.4.0'}
'@number-flow/react@0.6.2':
resolution: {integrity: sha512-WjZuV4aA+vhRCgCF+adGLgFVVAJ8vdvq6EchRT2FiBIgElTXtDOA3YgkcMr9UHpvpS9v4H70AB5wZv0D9jc3QA==}
peerDependencies:
react: ^18 || ^19
react-dom: ^18 || ^19
'@opentelemetry/semantic-conventions@1.43.0':
resolution: {integrity: sha512-eSYWTm620tTk45EKSedaUL8MFYI8hW164hIXsgIHyxu3VobUB3fFCu5t0hQby6OoWRPsG1KkKUG2M5UadiLiVg==}
engines: {node: '>=14'}
@@ -3258,6 +3267,9 @@ packages:
jiti:
optional: true
esm-env@1.2.2:
resolution: {integrity: sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==}
espree@10.4.0:
resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -4414,6 +4426,9 @@ packages:
resolution: {integrity: sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==}
engines: {node: '>=18'}
number-flow@0.6.2:
resolution: {integrity: sha512-MCnImG4Q5vPwhSXnov56nOuyyKn6LC+Qd7II1UiKc+ACRtug5iAtn0+CwXNxM38AC5lSowEY+oYEtZX2qMnUyw==}
oauth-sign@0.9.0:
resolution: {integrity: sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==}
@@ -6320,6 +6335,13 @@ snapshots:
'@nolyfill/is-core-module@1.0.39': {}
'@number-flow/react@0.6.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
dependencies:
esm-env: 1.2.2
number-flow: 0.6.2
react: 19.2.4
react-dom: 19.2.4(react@19.2.4)
'@opentelemetry/semantic-conventions@1.43.0': {}
'@postman/form-data@3.1.1':
@@ -8511,6 +8533,8 @@ snapshots:
transitivePeerDependencies:
- supports-color
esm-env@1.2.2: {}
espree@10.4.0:
dependencies:
acorn: 8.17.0
@@ -9837,6 +9861,10 @@ snapshots:
path-key: 4.0.0
unicorn-magic: 0.3.0
number-flow@0.6.2:
dependencies:
esm-env: 1.2.2
oauth-sign@0.9.0: {}
object-assign@4.1.1: {}

View File

@@ -3,6 +3,7 @@ import { Cal_Sans, Geist_Mono, Bricolage_Grotesque } from "next/font/google";
import localFont from "next/font/local";
import { Toaster } from "@/components/ui/sonner";
import { SiteHeader } from "@/components/site-header";
import { SiteTopBanner } from "@/components/site-top-banner";
import { ListeCekmecesi } from "@/components/manuel-liste/liste-cekmecesi";
import { HeaderGate } from "@/components/header-gate";
import { ProgressiveBlur } from "@/components/ui/skiper-ui/skiper41";
@@ -73,6 +74,7 @@ export default function RootLayout({
/>
</div>
<HeaderGate>
<SiteTopBanner />
<SiteHeader />
</HeaderGate>
<ScrollFlow className="flex min-h-0 flex-1 flex-col">

View File

@@ -0,0 +1,141 @@
"use client";
import { useEffect, useState } from "react";
import NumberFlow, { NumberFlowGroup } from "@number-flow/react";
import { CalendarDays, Clock3 } from "lucide-react";
const TERCIH_BASLANGICI = new Date("2026-07-29T00:00:00+03:00").getTime();
const TERCIH_BITISI = new Date("2026-08-10T23:59:59+03:00").getTime();
type GeriSayim = {
asama: "baslamadi" | "devam-ediyor" | "sona-erdi";
gun: number;
saat: number;
dakika: number;
saniye: number;
};
function geriSayimiHesapla(simdi: number): GeriSayim {
const asama =
simdi < TERCIH_BASLANGICI
? "baslamadi"
: simdi <= TERCIH_BITISI
? "devam-ediyor"
: "sona-erdi";
const hedef =
asama === "baslamadi" ? TERCIH_BASLANGICI : TERCIH_BITISI;
const kalan = Math.max(0, hedef - simdi);
return {
asama,
gun: Math.floor(kalan / 86_400_000),
saat: Math.floor((kalan % 86_400_000) / 3_600_000),
dakika: Math.floor((kalan % 3_600_000) / 60_000),
saniye: Math.floor((kalan % 60_000) / 1_000),
};
}
const BIRIMLER = [
{ anahtar: "gun", etiket: "gün" },
{ anahtar: "saat", etiket: "sa" },
{ anahtar: "dakika", etiket: "dk" },
{ anahtar: "saniye", etiket: "sn" },
] as const;
export function SiteTopBanner() {
const [geriSayim, setGeriSayim] = useState<GeriSayim | null>(null);
useEffect(() => {
const guncelle = () => setGeriSayim(geriSayimiHesapla(Date.now()));
guncelle();
const zamanlayici = window.setInterval(guncelle, 1_000);
return () => window.clearInterval(zamanlayici);
}, []);
const asamaMetni =
geriSayim?.asama === "baslamadi"
? "Başlamasına"
: geriSayim?.asama === "sona-erdi"
? "Tercih dönemi sona erdi"
: "Sona ermesine";
return (
<section
aria-label="2026 YKS tercih dönemi"
className="relative z-50 border-b border-primary-foreground/15 bg-primary text-primary-foreground print:hidden"
>
<div className="mx-auto flex min-h-12 max-w-6xl flex-wrap items-center justify-center gap-x-5 gap-y-1.5 px-4 py-2 sm:justify-between">
<div className="flex min-w-0 items-center gap-2.5">
<CalendarDays className="size-4 shrink-0 opacity-80" aria-hidden />
<p className="truncate text-sm font-semibold tracking-[-0.01em]">
<span className="hidden sm:inline">2026 YKS tercih dönemi · </span>
<time dateTime="2026-07-29">29 Temmuz</time>
<span aria-hidden> </span>
<time dateTime="2026-08-10">10 Ağustos 2026</time>
</p>
</div>
<div className="flex items-center gap-2.5" role="timer">
<div className="flex items-center gap-1.5 text-xs font-medium opacity-85">
<Clock3 className="size-3.5" aria-hidden />
<span>{asamaMetni}</span>
</div>
{geriSayim?.asama !== "sona-erdi" ? (
<dl
className="flex items-baseline gap-1 font-mono text-xs tabular-nums"
aria-label={
geriSayim
? `${geriSayim.gun} gün, ${geriSayim.saat} saat, ${geriSayim.dakika} dakika, ${geriSayim.saniye} saniye`
: "Geri sayım yükleniyor"
}
>
<NumberFlowGroup>
{BIRIMLER.map(({ anahtar, etiket }) => (
<div className="flex items-baseline gap-0.5" key={anahtar}>
<dt className="sr-only">{etiket}</dt>
<dd className="font-semibold">
{geriSayim ? (
<NumberFlow
value={geriSayim[anahtar]}
trend={-1}
format={{
minimumIntegerDigits: 2,
useGrouping: false,
}}
transformTiming={{
duration: 280,
easing: "cubic-bezier(0.23, 1, 0.32, 1)",
}}
spinTiming={{
duration: 280,
easing: "cubic-bezier(0.23, 1, 0.32, 1)",
}}
opacityTiming={{
duration: 180,
easing: "ease-out",
}}
willChange={anahtar === "saniye"}
className="inline-block min-w-[2ch] text-right"
aria-hidden
/>
) : (
"--"
)}
<span className="ml-0.5 text-[10px] font-medium opacity-70">
{etiket}
</span>
</dd>
</div>
))}
</NumberFlowGroup>
</dl>
) : null}
</div>
</div>
</section>
);
}