"use client" import { useState, useEffect, useRef, Suspense } from "react" import { useSearchParams, useRouter } from "next/navigation" import { Eye, EyeOff, Loader2 } from "lucide-react" const waves = [ { a: 16, f: 0.011, s: 0.018, y: 0.38, fill: "rgba(27,176,206,0.18)" }, { a: 20, f: 0.008, s: 0.013, y: 0.52, fill: "rgba(27,176,206,0.25)" }, { a: 12, f: 0.015, s: 0.025, y: 0.28, fill: "rgba(180,192,210,0.06)" }, { a: 26, f: 0.006, s: 0.010, y: 0.65, fill: "rgba(180,192,210,0.15)" }, { a: 10, f: 0.019, s: 0.030, y: 0.20, fill: "rgba(27,176,206,0.10)" }, ] export default function LoginPage() { return (
}>
) } function LoginForm() { const router = useRouter() const searchParams = useSearchParams() const [email, setEmail] = useState("") const [password, setPassword] = useState("") const [showPassword, setShowPassword] = useState(false) const [loading, setLoading] = useState(false) const [remember, setRemember] = useState(false) const [error, setError] = useState("") const [counts, setCounts] = useState({ leads: 0, conversion: 0, users: 0 }) const counterStarted = useRef(false) const [testimonialIndex, setTestimonialIndex] = useState(0) const testimonials = [ { text: "This CRM transformed how we manage our sales pipeline. We've seen a 40% increase in lead conversion.", author: "Marcus Johnson, Sales Lead at Black Cipher", }, { text: "When you're not sure, flip a coin, because when the coin is in the air, you realize which option you're actually hoping for.", author: "Dillen van der Merwe, Madman of Black Cipher", }, ] useEffect(() => { const timer = setInterval(() => { setTestimonialIndex((prev) => (prev + 1) % testimonials.length) }, 5000) return () => clearInterval(timer) }, [testimonials.length]) const canvasRef = useRef(null) const starsCanvasRightRef = useRef(null) useEffect(() => { const canvas = canvasRef.current if (!canvas) return const ctx = canvas.getContext("2d") if (!ctx) return let animId: number let time = 0 const resize = () => { const parent = canvas.parentElement! const rect = parent.getBoundingClientRect() const dpr = window.devicePixelRatio || 1 canvas.width = rect.width * dpr canvas.height = 200 * dpr canvas.style.width = rect.width + "px" canvas.style.height = "200px" ctx!.setTransform(dpr, 0, 0, dpr, 0, 0) } resize() window.addEventListener("resize", resize) const draw = () => { const w = canvas.width / (window.devicePixelRatio || 1) const h = 200 ctx!.clearRect(0, 0, w, h) for (const wave of waves) { ctx!.beginPath() ctx!.moveTo(0, h) for (let x = 0; x <= w; x++) { const y = wave.y * h + Math.sin(x * wave.f + time * wave.s) * wave.a ctx!.lineTo(x, y) } ctx!.lineTo(w, h) ctx!.closePath() ctx!.fillStyle = wave.fill ctx!.fill() } time += 1 animId = requestAnimationFrame(draw) } animId = requestAnimationFrame(draw) return () => { cancelAnimationFrame(animId) window.removeEventListener("resize", resize) } }, []) useEffect(() => { const canvases = [starsCanvasRightRef.current].filter(Boolean) as HTMLCanvasElement[] if (canvases.length === 0) return const stars = Array.from({ length: 312 }, () => ({ x: Math.random(), y: Math.random(), r: 0.2 + Math.random() * 0.6, baseOpacity: 0.12 + Math.random() * 0.43, speed: 0.003 + Math.random() * 0.015, phase: Math.random() * Math.PI * 2, driftX: (Math.random() - 0.5) * 0.00003, driftY: (Math.random() - 0.5) * 0.00003, })) let animId: number let time = 0 const resize = () => { for (const c of canvases) { const dpr = window.devicePixelRatio || 1 const rect = c.getBoundingClientRect() c.width = rect.width * dpr c.height = rect.height * dpr const ctx = c.getContext("2d") if (ctx) ctx.setTransform(dpr, 0, 0, dpr, 0, 0) } } resize() const ros = canvases.map((c) => { const ro = new ResizeObserver(() => resize()) ro.observe(c.parentElement!) return ro }) window.addEventListener("resize", resize) const draw = () => { time += 1 for (const c of canvases) { const dpr = window.devicePixelRatio || 1 const w = c.width / dpr const h = c.height / dpr const ctx = c.getContext("2d") if (!ctx) continue ctx.clearRect(0, 0, w, h) for (const star of stars) { const x = ((star.x + time * star.driftX) % 1) * w const y = ((star.y + time * star.driftY) % 1) * h const opacity = star.baseOpacity * (0.5 + 0.5 * Math.sin(time * star.speed + star.phase)) ctx.beginPath() ctx.arc(x, y, star.r, 0, Math.PI * 2) ctx.fillStyle = `rgba(255,255,255,${opacity})` ctx.fill() if (star.r > 0.5) { ctx.beginPath() ctx.arc(x, y, star.r * 1.8, 0, Math.PI * 2) ctx.fillStyle = `rgba(255,255,255,${opacity * 0.06})` ctx.fill() } } } animId = requestAnimationFrame(draw) } animId = requestAnimationFrame(draw) return () => { cancelAnimationFrame(animId) ros.forEach((ro) => ro.disconnect()) window.removeEventListener("resize", resize) } }, []) useEffect(() => { if (counterStarted.current) return counterStarted.current = true const targets = { leads: 1247, conversion: 40, users: 83 } const steps = 150 const delayTimer = setTimeout(() => { let step = 0 const interval = setInterval(() => { step++ if (step >= steps) { clearInterval(interval) setCounts(targets) return } setCounts({ leads: Math.min(Math.floor((targets.leads / steps) * step), targets.leads), conversion: Math.min(Math.floor((targets.conversion / steps) * step), targets.conversion), users: Math.min(Math.floor((targets.users / steps) * step), targets.users), }) }, 15) }, 400) return () => clearTimeout(delayTimer) }, []) const handleSubmit = async (e: React.FormEvent) => { e.preventDefault() setError("") setLoading(true) try { const res = await fetch("/api/auth/login", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ email, password }), }) if (res.ok) { const redirectTo = searchParams.get("redirect") || "/dashboard" router.push(redirectTo) } else { const data = await res.json().catch(() => ({})) setError(data.error || "Invalid email or password.") } } catch { setError("Connection error. Please try again.") } finally { setLoading(false) } } return (
Black Cipher

Your Agency's{" "} Growth{" "} Starts Here

Manage leads, track conversions, and grow your web development business with our CRM.

{counts.leads.toLocaleString()}
leads tracked
{counts.conversion}%
conversion lift
{counts.users}
active users

“{testimonials[testimonialIndex].text}”

{testimonials[testimonialIndex].author}

{testimonials.map((_, i) => (

Welcome back

Sign in to your account to continue

{error && (
{error}
)}
setEmail(e.target.value)} required autoComplete="email" className="login-input" />
setPassword(e.target.value)} required autoComplete="current-password" className="login-input" />

By signing in, you agree to our{" "} {" "} and{" "}

) }