feat(demo): start sandbox from landing

This commit is contained in:
2026-06-07 00:51:35 +03:30
parent ce6cd6cccc
commit c6b1712486
6 changed files with 87 additions and 12 deletions

View File

@@ -1,4 +1,4 @@
import { useMemo } from "react"
import { useMemo, useState } from "react"
import { Link, useNavigate } from "react-router-dom"
import {
ArrowRight,
@@ -15,11 +15,14 @@ import {
TimerReset,
Waypoints,
} from "lucide-react"
import { toast } from "sonner"
import { Button } from "../components/ui/button"
import { useTheme } from "../components/ThemeProvider"
import { useTranslation } from "../hooks/useTranslation"
import { cn } from "../lib/utils"
import { startDemo } from "../api/demo"
import { setDemoSessionMeta, setSessionTokens } from "../lib/session"
const formatNumber = (value: number, lang: "en" | "fa") =>
new Intl.NumberFormat(lang === "fa" ? "fa-IR" : "en-US").format(value)
@@ -28,6 +31,7 @@ export default function Landing() {
const navigate = useNavigate()
const { t, lang, setLanguage } = useTranslation()
const { theme, setTheme } = useTheme()
const [isStartingDemo, setIsStartingDemo] = useState(false)
const isAuthenticated = typeof window !== "undefined" && !!localStorage.getItem("accessToken")
const isDarkMode =
@@ -87,6 +91,23 @@ export default function Landing() {
const ctaTarget = isAuthenticated ? "/timesheet" : "/auth"
const handleStartDemo = async () => {
if (isStartingDemo) return
setIsStartingDemo(true)
try {
const demo = await startDemo()
setSessionTokens(demo.access, demo.refresh)
setDemoSessionMeta(demo.expires_at)
toast.success(t.demo?.started || "Demo environment is ready.")
navigate("/timesheet")
} catch (error) {
console.error(error)
toast.error(t.demo?.startError || "Could not start the demo environment.")
} finally {
setIsStartingDemo(false)
}
}
return (
<div className="scroll-smooth min-h-screen overflow-x-hidden bg-[radial-gradient(circle_at_top,#e0f2fe_0%,#f8fafc_36%,#eef2ff_100%)] text-slate-950 dark:bg-[radial-gradient(circle_at_top,#082f49_0%,#020617_40%,#020617_100%)] dark:text-slate-50">
<div className="landing-aurora pointer-events-none fixed inset-0 opacity-80" />
@@ -164,12 +185,14 @@ export default function Landing() {
{isAuthenticated ? t.landing.actions.openWorkspace : t.landing.actions.startNow}
<ArrowRight className={cn("ms-2 h-4 w-4", lang === "fa" && "rtl:rotate-180")} />
</Button>
<a
href="#demo"
className="inline-flex h-14 items-center justify-center rounded-full border border-slate-200 bg-white/85 px-7 text-base font-medium text-slate-800 shadow-sm backdrop-blur transition hover:bg-white dark:border-slate-800 dark:bg-slate-950/70 dark:text-slate-100 dark:hover:bg-slate-900"
<button
type="button"
onClick={handleStartDemo}
disabled={isStartingDemo}
className="inline-flex h-14 items-center justify-center rounded-full border border-slate-200 bg-white/85 px-7 text-base font-medium text-slate-800 shadow-sm backdrop-blur transition hover:bg-white disabled:cursor-not-allowed disabled:opacity-70 dark:border-slate-800 dark:bg-slate-950/70 dark:text-slate-100 dark:hover:bg-slate-900"
>
{t.landing.actions.watchDemo}
</a>
{isStartingDemo ? t.demo?.starting || "Preparing demo..." : t.landing.actions.watchDemo}
</button>
</div>
<div className="animate-landing-rise grid gap-3 sm:grid-cols-3 [animation-delay:320ms]">