Add @number-flow/react dependency and integrate SiteTopBanner component into layout
All checks were successful
Deploy / deploy (push) Successful in 11m11s
All checks were successful
Deploy / deploy (push) Successful in 11m11s
This commit is contained in:
@@ -14,6 +14,7 @@
|
|||||||
"@anthropic-ai/sdk": "^0.112.4",
|
"@anthropic-ai/sdk": "^0.112.4",
|
||||||
"@cds/city": "^1.1.0",
|
"@cds/city": "^1.1.0",
|
||||||
"@libsql/client": "^0.17.4",
|
"@libsql/client": "^0.17.4",
|
||||||
|
"@number-flow/react": "^0.6.2",
|
||||||
"@react-pdf/renderer": "^4.5.1",
|
"@react-pdf/renderer": "^4.5.1",
|
||||||
"better-auth": "^1.6.23",
|
"better-auth": "^1.6.23",
|
||||||
"better-sqlite3": "^12.11.1",
|
"better-sqlite3": "^12.11.1",
|
||||||
|
|||||||
28
pnpm-lock.yaml
generated
28
pnpm-lock.yaml
generated
@@ -17,6 +17,9 @@ importers:
|
|||||||
'@libsql/client':
|
'@libsql/client':
|
||||||
specifier: ^0.17.4
|
specifier: ^0.17.4
|
||||||
version: 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':
|
'@react-pdf/renderer':
|
||||||
specifier: ^4.5.1
|
specifier: ^4.5.1
|
||||||
version: 4.5.1(react@19.2.4)
|
version: 4.5.1(react@19.2.4)
|
||||||
@@ -1240,6 +1243,12 @@ packages:
|
|||||||
resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==}
|
resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==}
|
||||||
engines: {node: '>=12.4.0'}
|
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':
|
'@opentelemetry/semantic-conventions@1.43.0':
|
||||||
resolution: {integrity: sha512-eSYWTm620tTk45EKSedaUL8MFYI8hW164hIXsgIHyxu3VobUB3fFCu5t0hQby6OoWRPsG1KkKUG2M5UadiLiVg==}
|
resolution: {integrity: sha512-eSYWTm620tTk45EKSedaUL8MFYI8hW164hIXsgIHyxu3VobUB3fFCu5t0hQby6OoWRPsG1KkKUG2M5UadiLiVg==}
|
||||||
engines: {node: '>=14'}
|
engines: {node: '>=14'}
|
||||||
@@ -3258,6 +3267,9 @@ packages:
|
|||||||
jiti:
|
jiti:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
esm-env@1.2.2:
|
||||||
|
resolution: {integrity: sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==}
|
||||||
|
|
||||||
espree@10.4.0:
|
espree@10.4.0:
|
||||||
resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==}
|
resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
@@ -4414,6 +4426,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==}
|
resolution: {integrity: sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
|
number-flow@0.6.2:
|
||||||
|
resolution: {integrity: sha512-MCnImG4Q5vPwhSXnov56nOuyyKn6LC+Qd7II1UiKc+ACRtug5iAtn0+CwXNxM38AC5lSowEY+oYEtZX2qMnUyw==}
|
||||||
|
|
||||||
oauth-sign@0.9.0:
|
oauth-sign@0.9.0:
|
||||||
resolution: {integrity: sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==}
|
resolution: {integrity: sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==}
|
||||||
|
|
||||||
@@ -6320,6 +6335,13 @@ snapshots:
|
|||||||
|
|
||||||
'@nolyfill/is-core-module@1.0.39': {}
|
'@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': {}
|
'@opentelemetry/semantic-conventions@1.43.0': {}
|
||||||
|
|
||||||
'@postman/form-data@3.1.1':
|
'@postman/form-data@3.1.1':
|
||||||
@@ -8511,6 +8533,8 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
|
esm-env@1.2.2: {}
|
||||||
|
|
||||||
espree@10.4.0:
|
espree@10.4.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
acorn: 8.17.0
|
acorn: 8.17.0
|
||||||
@@ -9837,6 +9861,10 @@ snapshots:
|
|||||||
path-key: 4.0.0
|
path-key: 4.0.0
|
||||||
unicorn-magic: 0.3.0
|
unicorn-magic: 0.3.0
|
||||||
|
|
||||||
|
number-flow@0.6.2:
|
||||||
|
dependencies:
|
||||||
|
esm-env: 1.2.2
|
||||||
|
|
||||||
oauth-sign@0.9.0: {}
|
oauth-sign@0.9.0: {}
|
||||||
|
|
||||||
object-assign@4.1.1: {}
|
object-assign@4.1.1: {}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { Cal_Sans, Geist_Mono, Bricolage_Grotesque } from "next/font/google";
|
|||||||
import localFont from "next/font/local";
|
import localFont from "next/font/local";
|
||||||
import { Toaster } from "@/components/ui/sonner";
|
import { Toaster } from "@/components/ui/sonner";
|
||||||
import { SiteHeader } from "@/components/site-header";
|
import { SiteHeader } from "@/components/site-header";
|
||||||
|
import { SiteTopBanner } from "@/components/site-top-banner";
|
||||||
import { ListeCekmecesi } from "@/components/manuel-liste/liste-cekmecesi";
|
import { ListeCekmecesi } from "@/components/manuel-liste/liste-cekmecesi";
|
||||||
import { HeaderGate } from "@/components/header-gate";
|
import { HeaderGate } from "@/components/header-gate";
|
||||||
import { ProgressiveBlur } from "@/components/ui/skiper-ui/skiper41";
|
import { ProgressiveBlur } from "@/components/ui/skiper-ui/skiper41";
|
||||||
@@ -73,6 +74,7 @@ export default function RootLayout({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<HeaderGate>
|
<HeaderGate>
|
||||||
|
<SiteTopBanner />
|
||||||
<SiteHeader />
|
<SiteHeader />
|
||||||
</HeaderGate>
|
</HeaderGate>
|
||||||
<ScrollFlow className="flex min-h-0 flex-1 flex-col">
|
<ScrollFlow className="flex min-h-0 flex-1 flex-col">
|
||||||
|
|||||||
141
src/components/site-top-banner.tsx
Normal file
141
src/components/site-top-banner.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user