first commit
Some checks failed
Test examples / Test Examples (20) (push) Has been cancelled
Test examples / Test Examples (22) (push) Has been cancelled
Lock Threads / action (push) Has been cancelled
Trigger Release / start (push) Has been cancelled
Stale issue handler / stale (push) Has been cancelled
Update Font Data / create-pull-request (push) Has been cancelled
build-and-deploy / deploy-target (push) Has been cancelled
build-and-deploy / build (push) Has been cancelled
build-and-deploy / stable - aarch64-unknown-linux-musl - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-unknown-linux-musl - node@16 (push) Has been cancelled
build-and-deploy / stable - aarch64-unknown-linux-gnu - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-unknown-linux-gnu - node@16 (push) Has been cancelled
build-and-deploy / stable - aarch64-pc-windows-msvc - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-pc-windows-msvc - node@16 (push) Has been cancelled
build-and-deploy / stable - aarch64-apple-darwin - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-apple-darwin - node@16 (push) Has been cancelled
build-and-deploy / build-wasm (nodejs) (push) Has been cancelled
build-and-deploy / build-wasm (web) (push) Has been cancelled
build-and-deploy / Deploy preview tarball (push) Has been cancelled
build-and-deploy / Potentially publish release (push) Has been cancelled
build-and-deploy / publish-turbopack-npm-packages (push) Has been cancelled
build-and-deploy / Deploy examples (push) Has been cancelled
build-and-deploy / thank you, build (push) Has been cancelled
build-and-deploy / Upload Turbopack Bytesize metrics to Datadog (push) Has been cancelled
Rspack Next.js development integration tests / Rspack integration tests (push) Has been cancelled
Rspack Next.js production integration tests / Rspack integration tests (push) Has been cancelled
Turbopack Next.js development integration tests / Next.js integration tests (push) Has been cancelled
Turbopack Next.js production integration tests / Next.js integration tests (push) Has been cancelled
Update Rspack test manifest / Update and upload Rspack development test manifest (push) Has been cancelled
Update Rspack test manifest / Update and upload Rspack production test manifest (push) Has been cancelled
Upload bundler test manifests to areweturboyet.com / Upload test results (push) Has been cancelled
Update React / create-pull-request (push) Has been cancelled
test-e2e-project-reset-cron / reset-test-project (push) Has been cancelled
Notify about the top 15 issues/PRs/feature requests (most reacted) in the last 90 days / run (push) Has been cancelled

This commit is contained in:
Arian Tron
2026-03-10 19:37:31 +03:30
commit 61f56f997c
27684 changed files with 2784175 additions and 0 deletions

View File

@@ -0,0 +1,68 @@
import { notFound } from "next/navigation";
import { CacheStateWatcher } from "../cache-state-watcher";
import { Suspense } from "react";
import { RevalidateFrom } from "../revalidate-from";
import Link from "next/link";
type TimeData = {
unixtime: number;
datetime: string;
timezone: string;
};
const timeZones = ["cet", "gmt"];
export const revalidate = 500;
export async function generateStaticParams() {
return timeZones.map((timezone) => ({ timezone }));
}
export default async function Page({ params: { timezone } }) {
const data = await fetch(
`https://worldtimeapi.org/api/timezone/${timezone}`,
{
next: { tags: ["time-data"] },
},
);
if (!data.ok) {
notFound();
}
const timeData: TimeData = await data.json();
return (
<>
<header className="header">
{timeZones.map((timeZone) => (
<Link key={timeZone} className="link" href={`/${timeZone}`}>
{timeZone.toUpperCase()} Time
</Link>
))}
</header>
<main className="widget">
<div className="pre-rendered-at">
{timeData.timezone} Time {timeData.datetime}
</div>
<Suspense fallback={null}>
<CacheStateWatcher
revalidateAfter={revalidate * 1000}
time={timeData.unixtime * 1000}
/>
</Suspense>
<RevalidateFrom />
</main>
<footer className="footer">
<Link
href={process.env.NEXT_PUBLIC_REDIS_INSIGHT_URL}
className="link"
target="_blank"
rel="noopener noreferrer"
>
View RedisInsight &#x21AA;
</Link>
</footer>
</>
);
}

View File

@@ -0,0 +1,50 @@
"use client";
import { ReactNode, useEffect, useState } from "react";
type CacheStateWatcherProps = { time: number; revalidateAfter: number };
export function CacheStateWatcher({
time,
revalidateAfter,
}: CacheStateWatcherProps): ReactNode {
const [cacheState, setCacheState] = useState("");
const [countDown, setCountDown] = useState("");
useEffect(() => {
let id = -1;
function check(): void {
const now = Date.now();
setCountDown(
Math.max(0, (time + revalidateAfter - now) / 1000).toFixed(3),
);
if (now > time + revalidateAfter) {
setCacheState("stale");
return;
}
setCacheState("fresh");
id = requestAnimationFrame(check);
}
id = requestAnimationFrame(check);
return () => {
cancelAnimationFrame(id);
};
}, [revalidateAfter, time]);
return (
<>
<div className={`cache-state ${cacheState}`}>
Cache state: {cacheState}
</div>
<div className="stale-after">Stale in: {countDown}</div>
</>
);
}

View File

@@ -0,0 +1,102 @@
*,
*:before,
*:after {
box-sizing: border-box;
}
body,
html {
margin: 0;
padding: 0;
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
color: #333;
line-height: 1.6;
background-color: #f4f4f4;
}
.widget {
background-color: #ffffff;
border-radius: 8px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
margin: 20px auto;
padding: 20px;
max-width: 600px;
text-align: center;
}
.pre-rendered-at,
.cache-state,
.stale-after {
font-size: 0.9em;
color: #666;
margin: 5px 0;
}
.cache-state.fresh {
color: #4caf50;
}
.cache-state.stale {
color: #f44336;
}
.revalidate-from {
margin-top: 20px;
}
.revalidate-from-button {
background-color: #008cba;
color: white;
border: none;
border-radius: 4px;
padding: 10px 20px;
cursor: pointer;
transition: background-color 0.3s ease;
}
.revalidate-from-button:hover {
background-color: #005f73;
}
.revalidate-from-button:active {
transform: translateY(2px);
}
.revalidate-from-button[aria-disabled="true"] {
background-color: #ccc;
cursor: not-allowed;
}
.footer,
.header {
padding: 10px;
position: relative;
place-items: center;
grid-auto-flow: column;
bottom: 0;
grid-gap: 20px;
width: 100%;
display: grid;
justify-content: center;
}
.link {
color: #09f;
text-decoration: none;
transition: color 0.3s ease;
}
.link:hover {
color: #07c;
}
@media (max-width: 768px) {
.widget {
width: 90%;
margin: 20px auto;
}
.footer {
padding: 20px;
}
}

View File

@@ -0,0 +1,13 @@
import "./global.css";
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>{children}</body>
</html>
);
}

View File

@@ -0,0 +1,27 @@
"use client";
import { useFormStatus } from "react-dom";
import revalidate from "./server-actions";
function RevalidateButton() {
const { pending } = useFormStatus();
return (
<button
className="revalidate-from-button"
type="submit"
disabled={pending}
aria-disabled={pending}
>
Revalidate
</button>
);
}
export function RevalidateFrom() {
return (
<form className="revalidate-from" action={revalidate}>
<RevalidateButton />
</form>
);
}

View File

@@ -0,0 +1,7 @@
"use server";
import { revalidateTag } from "next/cache";
export default async function revalidate() {
revalidateTag("time-data");
}