mirror of
https://github.com/Portabase/portabase.git
synced 2026-07-14 11:16:13 +02:00
fix: demo-banner (#305)
This commit is contained in:
+15
-8
@@ -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,
|
||||
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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/>
|
||||
|
||||
Reference in New Issue
Block a user