"use client"; import { ArrowRight } from "lucide-react"; import { useRouter } from "next/navigation"; import { useEffect, useRef, useState } from "react"; import { AuthFormCard, FormError } from "@/components/auth/auth-form-card"; import { BrandLockup, BrandMark } from "@/components/brand"; import { FullScreenLoader } from "@/components/page-layout"; import { ThemeToggle } from "@/components/theme-toggle"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { authClient, signIn, useSession } from "@/lib/auth-client"; import { gsap, motionEase, prefersReducedMotion, useGSAP } from "@/lib/motion"; function LoginHero() { const heroRef = useRef(null); useGSAP( () => { const root = heroRef.current; if (!root || prefersReducedMotion()) return; const bg = root.querySelector("[data-hero-bg]"); const brand = root.querySelector("[data-hero-brand]"); const chip = root.querySelector("[data-hero-chip]"); const lines = root.querySelectorAll("[data-hero-line]"); const copy = root.querySelector("[data-hero-copy]"); const foot = root.querySelector("[data-hero-foot]"); const rail = root.querySelector("[data-hero-rail]"); const tl = gsap.timeline({ defaults: { ease: motionEase.out } }); tl.from(bg, { scale: 1.12, duration: 1.2, ease: "power2.out" }, 0) .from(rail, { scaleY: 0, duration: 0.6, transformOrigin: "top" }, 0.05) .from(brand, { y: -12, autoAlpha: 0, duration: 0.45 }, 0.08) .from(chip, { y: 14, autoAlpha: 0, duration: 0.4 }, 0.16) .from(lines, { yPercent: 110, duration: 0.7, stagger: 0.08, ease: motionEase.snap }, 0.2) .from(copy, { y: 14, autoAlpha: 0, duration: 0.45 }, 0.42) .from(foot, { autoAlpha: 0, duration: 0.35 }, 0.55); gsap.to(bg, { scale: 1.06, duration: 18, ease: "none", yoyo: true, repeat: -1, delay: 1.2, }); }, { scope: heroRef } ); return (
Minecraft Control Plane

Play more Operate less

Spin up servers, route players, and run the whole network from one console.

Built for people who actually run servers

); } export default function LoginPage() { const router = useRouter(); const { data: session, isPending } = useSession(); const [loading, setLoading] = useState(false); const [error, setError] = useState(""); useEffect(() => { if (!isPending && session?.user) { router.replace("/dashboard"); } }, [session, isPending, router]); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setLoading(true); setError(""); const formData = new FormData(e.currentTarget); const email = formData.get("email") as string; const password = formData.get("password") as string; try { const result = await signIn.email({ email, password, }); if (result.error) { setError(result.error.message || "Invalid email or password"); } else { const refreshedSession = await authClient.getSession({ query: { disableCookieCache: true }, }); if (!refreshedSession.data?.user) { setError( "Signed in, but the session cookie was not accepted. Check the web and API URLs." ); return; } window.location.assign("/dashboard"); } } catch (_err) { setError("Failed to connect to server"); } finally { setLoading(false); } }; if (isPending || session?.user) return ; return (
} className="border-2 border-foreground shadow-[8px_8px_0_color-mix(in_oklch,var(--foreground)_18%,transparent)]" headerClassName="border-b" >
); }