feat(demo): show sandbox status controls
This commit is contained in:
@@ -3,13 +3,14 @@ import { useNavigate } from "react-router-dom"
|
||||
import { useTranslation } from "../hooks/useTranslation"
|
||||
import { Button } from "./ui/button"
|
||||
import { SettingsMenu } from "./SettingsMenu"
|
||||
import { LogOut, User, Moon, Sun, Globe, Command, Menu } from "lucide-react"
|
||||
import { FlaskConical, LogOut, User, Moon, Sun, Globe, Command, Menu, RefreshCcw } from "lucide-react"
|
||||
import { useTheme } from "./ThemeProvider"
|
||||
import { logoutUser, getUserProfile } from "../api/users"
|
||||
import { WorkspaceSelector } from "./WorkspaceSelector"
|
||||
import { toast } from "sonner"
|
||||
import { NotificationBell } from "./notifications/NotificationBell"
|
||||
import { clearSessionTokens, getAccessToken, getRefreshToken } from "../lib/session"
|
||||
import { clearSessionTokens, getAccessToken, getRefreshToken, setDemoSessionMeta, setSessionTokens } from "../lib/session"
|
||||
import { startDemo } from "../api/demo"
|
||||
|
||||
type NavbarProps = {
|
||||
onOpenSidebar?: () => void
|
||||
@@ -22,6 +23,7 @@ export function Navbar({ onOpenSidebar }: NavbarProps) {
|
||||
|
||||
const [showLogoutModal, setShowLogoutModal] = useState(false)
|
||||
const [isDropdownOpen, setIsDropdownOpen] = useState(false)
|
||||
const [isResettingDemo, setIsResettingDemo] = useState(false)
|
||||
const [user, setUser] = useState<any>(null)
|
||||
|
||||
const dropdownRef = useRef<HTMLDivElement>(null)
|
||||
@@ -30,6 +32,13 @@ export function Navbar({ onOpenSidebar }: NavbarProps) {
|
||||
const isDarkMode =
|
||||
theme === "dark" ||
|
||||
(theme === "system" && document.documentElement.classList.contains("dark"))
|
||||
const isDemoUser = Boolean(user?.is_demo)
|
||||
const demoExpiryLabel = user?.demo_expires_at
|
||||
? new Intl.DateTimeFormat(isFa ? "fa-IR" : "en-US", {
|
||||
dateStyle: "medium",
|
||||
timeStyle: "short",
|
||||
}).format(new Date(user.demo_expires_at))
|
||||
: null
|
||||
|
||||
useEffect(() => {
|
||||
const handleProfileUpdated = ((e: CustomEvent) => {
|
||||
@@ -86,6 +95,23 @@ export function Navbar({ onOpenSidebar }: NavbarProps) {
|
||||
}
|
||||
}
|
||||
|
||||
const handleResetDemo = async () => {
|
||||
if (isResettingDemo) return
|
||||
setIsResettingDemo(true)
|
||||
try {
|
||||
const demo = await startDemo()
|
||||
setSessionTokens(demo.access, demo.refresh)
|
||||
setDemoSessionMeta(demo.expires_at)
|
||||
toast.success(t.demo?.reset || "Fresh demo environment is ready.")
|
||||
window.location.href = "/timesheet"
|
||||
} catch (error) {
|
||||
console.error("Demo reset failed:", error)
|
||||
toast.error(t.demo?.startError || "Could not start the demo environment.")
|
||||
} finally {
|
||||
setIsResettingDemo(false)
|
||||
}
|
||||
}
|
||||
|
||||
const toggleTheme = () => {
|
||||
setTheme(isDarkMode ? "light" : "dark")
|
||||
}
|
||||
@@ -142,6 +168,12 @@ export function Navbar({ onOpenSidebar }: NavbarProps) {
|
||||
{/* Desktop navbar: keep the old controls here */}
|
||||
<div className="hidden items-center gap-4 md:flex">
|
||||
{user && <WorkspaceSelector />}
|
||||
{isDemoUser && (
|
||||
<div className="inline-flex items-center gap-2 rounded-full border border-cyan-200 bg-cyan-50 px-3 py-2 text-xs font-semibold text-cyan-800 dark:border-cyan-500/20 dark:bg-cyan-500/10 dark:text-cyan-100">
|
||||
<FlaskConical className="h-4 w-4" />
|
||||
<span>{t.demo?.badge || "Demo environment"}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{user ? (
|
||||
<>
|
||||
@@ -178,8 +210,24 @@ export function Navbar({ onOpenSidebar }: NavbarProps) {
|
||||
? `${user.first_name || ""} ${user.last_name || ""}`.trim()
|
||||
: user.email}
|
||||
</p>
|
||||
{isDemoUser && demoExpiryLabel && (
|
||||
<p className="mt-1 text-xs text-cyan-700 dark:text-cyan-300">
|
||||
{(t.demo?.expiresAt || "Expires at")}: {demoExpiryLabel}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{isDemoUser && (
|
||||
<button
|
||||
onClick={handleResetDemo}
|
||||
disabled={isResettingDemo}
|
||||
className="flex w-full items-center gap-3 px-4 py-2.5 text-sm text-cyan-700 transition-colors hover:bg-cyan-50 disabled:cursor-not-allowed disabled:opacity-70 dark:text-cyan-300 dark:hover:bg-cyan-950/40"
|
||||
>
|
||||
<RefreshCcw className="h-4 w-4" />
|
||||
<span>{isResettingDemo ? t.demo?.starting || "Preparing demo..." : t.demo?.resetAction || "Reset demo"}</span>
|
||||
</button>
|
||||
)}
|
||||
|
||||
<button
|
||||
onClick={() => {
|
||||
navigate("/profile")
|
||||
@@ -286,4 +334,4 @@ export function Navbar({ onOpenSidebar }: NavbarProps) {
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user