fix: demo-banner (#305)

This commit is contained in:
Charles GTE
2026-06-02 18:36:35 +02:00
committed by GitHub
parent 564dee512f
commit 09791ae86e
5 changed files with 77 additions and 12 deletions
+15 -8
View File
@@ -36,8 +36,8 @@ export const env = createEnv({
SMTP_SECURE: z
.enum(["true", "false"])
.transform((val) => val === "true")
.default("true"),
.default("true")
.transform((val) => val === "true"),
AUTH_GOOGLE_ID: z.string().optional(),
AUTH_GOOGLE_SECRET: z.string().optional(),
@@ -101,18 +101,23 @@ export const env = createEnv({
OPENAPI_ENABLED: z
.enum(["true", "false"])
.transform((val) => val === "true")
.default("false"),
.default("false")
.transform((val) => val === "true"),
API_ENABLED: z
.enum(["true", "false"])
.transform((val) => val === "true")
.default("false"),
.default("false")
.transform((val) => val === "true"),
MCP_ENABLED: z
.enum(["true", "false"])
.transform((val) => val === "true")
.default("false"),
.default("false")
.transform((val) => val === "true"),
DEMO_ENABLED: z
.enum(["true", "false"])
.default("false")
.transform((val) => val === "true"),
},
client: {
NEXT_PUBLIC_PROJECT_VERSION: z.string().optional(),
@@ -190,5 +195,7 @@ export const env = createEnv({
API_ENABLED: process.env.API_ENABLED,
MCP_ENABLED: process.env.MCP_ENABLED,
DEMO_ENABLED: process.env.DEMO_ENABLED,
},
});
+54
View File
@@ -0,0 +1,54 @@
'use client';
import { useEffect, useState } from 'react';
import { Timer } from 'lucide-react';
import { cn } from '@/lib/utils';
import { Badge } from '@/components/ui/badge';
function getSecondsUntilNextHour(): number {
const now = new Date();
const s = 3600 - (now.getMinutes() * 60 + now.getSeconds());
return s === 3600 ? 0 : s;
}
function formatTime(seconds: number): string {
const s = Math.max(0, seconds);
const m = Math.floor(s / 60);
const sec = s % 60;
return `${String(m).padStart(2, '0')}:${String(sec).padStart(2, '0')}`;
}
export function DemoResetBanner() {
const [secondsLeft, setSecondsLeft] = useState<number | null>(null);
useEffect(() => {
const tick = () => setSecondsLeft(getSecondsUntilNextHour());
const timeout = setTimeout(tick, 0);
const interval = setInterval(tick, 1000);
return () => {
clearTimeout(timeout);
clearInterval(interval);
};
}, []);
if (secondsLeft === null) return null;
const isResetting = secondsLeft === 0;
const isWarning = secondsLeft > 0 && secondsLeft <= 300;
return (
<Badge
variant="outline"
className={cn(
'hidden md:inline-flex h-7',
isResetting && 'animate-pulse border-red-200 bg-red-50 text-red-700 dark:border-red-800 dark:bg-red-950/30 dark:text-red-400',
isWarning && 'border-amber-200 bg-amber-50 text-amber-700 dark:border-amber-800 dark:bg-amber-950/30 dark:text-amber-400',
)}
>
<Timer aria-hidden="true" />
{isResetting ? 'Resetting…' : `Demo resets in ${formatTime(secondsLeft)}`}
</Badge>
);
}
+4
View File
@@ -5,12 +5,15 @@ import {currentUser} from "@/lib/auth/current-user";
import {BreadCrumbsWrapper} from "@/components/common/bread-crumbs";
import GitHubStarsButtonCustom from "@/components/common/github-button";
import {LoggedInButton} from "@/features/layout/logged-in-button.server";
import { env } from "@/env.mjs";
import { DemoResetBanner } from "@/features/layout/demo-reset-banner";
export const Header = async ({ actions }: { actions?: ReactNode } = {}) => {
const user = await currentUser();
if (!user) {
return notFound();
}
const demoEnabled = env.DEMO_ENABLED;
return (
<header className="flex h-16 shrink-0 items-center justify-between border-b px-4">
<div className="flex items-center justify-between">
@@ -18,6 +21,7 @@ export const Header = async ({ actions }: { actions?: ReactNode } = {}) => {
<BreadCrumbsWrapper/>
</div>
<div className="flex items-center gap-2">
{demoEnabled && <DemoResetBanner />}
<GitHubStarsButtonCustom/>
{actions}
<LoggedInButton/>