222 lines
8.5 KiB
TypeScript
222 lines
8.5 KiB
TypeScript
"use client"
|
|
|
|
import { useState, useEffect, useRef } from "react"
|
|
import { useRouter } from "next/navigation"
|
|
import { motion } from "framer-motion"
|
|
import { Button } from "@/components/ui/button"
|
|
import { Input } from "@/components/ui/input"
|
|
import { Label } from "@/components/ui/label"
|
|
import { Checkbox } from "@/components/ui/checkbox"
|
|
import { COMPANY_NAME } from "@/lib/constants"
|
|
import { Eye, EyeOff, Loader2 } from "lucide-react"
|
|
|
|
function LoginForm() {
|
|
const router = useRouter()
|
|
const [email, setEmail] = useState("")
|
|
const [password, setPassword] = useState("")
|
|
const [showPassword, setShowPassword] = useState(false)
|
|
const [loading, setLoading] = useState(false)
|
|
const [remember, setRemember] = useState(false)
|
|
|
|
const handleSubmit = async (e: React.FormEvent) => {
|
|
e.preventDefault()
|
|
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) {
|
|
router.push("/dashboard")
|
|
} 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 (
|
|
<div className="flex min-h-screen bg-[#0a0a0f]">
|
|
<div className="left-panel flex flex-1 flex-col p-12">
|
|
<div className="relative z-10">
|
|
<img
|
|
src="/logo/CompanyLogo.png"
|
|
alt={COMPANY_NAME}
|
|
className="h-44 w-auto object-contain"
|
|
/>
|
|
</div>
|
|
|
|
<div className="relative z-10 flex-1 flex items-center justify-center">
|
|
<div className="text-center">
|
|
<h1 className="text-[34px] font-extrabold text-[#e8e8ef] leading-tight tracking-[-0.6px]">
|
|
Your Agency's{" "}
|
|
<span className="growth-word">Growth</span>{" "}
|
|
Starts Here
|
|
</h1>
|
|
<p className="mt-4 text-[13px] leading-[1.7] max-w-[360px] mx-auto body-text">
|
|
Manage leads, track conversions, and grow your web development business with our CRM.
|
|
</p>
|
|
<div className="stats-row">
|
|
<div className="stat">
|
|
<div className="stat-number">{counts.leads.toLocaleString()}</div>
|
|
<div className="stat-label">leads tracked</div>
|
|
</div>
|
|
<div className="stat">
|
|
<div className="stat-number">{counts.conversion}%</div>
|
|
<div className="stat-label">conversion lift</div>
|
|
</div>
|
|
<div className="stat">
|
|
<div className="stat-number">{counts.users}</div>
|
|
<div className="stat-label">active users</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="relative z-10 testimonial" style={{ background: "rgba(255,255,255,0.7)", padding: "16px 20px 16px 16px", clipPath: "url(#testimonialClip)" }}>
|
|
<svg width="0" height="0" style={{ position: "absolute" }}>
|
|
<defs>
|
|
<clipPath id="testimonialClip" clipPathUnits="objectBoundingBox">
|
|
<path d="M0,0 L0.92,0 C0.96,0 1,0.03 1,0.08 C1,0.14 0.97,0.22 0.9,0.3 C0.85,0.36 0.83,0.42 0.83,0.5 C0.83,0.58 0.85,0.64 0.9,0.7 C0.97,0.78 1,0.86 1,0.92 C1,0.97 0.96,1 0.92,1 L0,1 Z" />
|
|
</clipPath>
|
|
</defs>
|
|
</svg>
|
|
<div className="transition-opacity duration-500" key={testimonialIndex}>
|
|
<p className="text-sm font-semibold leading-relaxed" style={{ color: "#0a0a0f" }}>
|
|
“{testimonials[testimonialIndex].text}”
|
|
</p>
|
|
<p className="text-xs font-bold mt-2" style={{ color: "#0a0a0f" }}>
|
|
{testimonials[testimonialIndex].author}
|
|
</p>
|
|
</div>
|
|
<div className="flex items-center justify-center gap-1.5 mt-3">
|
|
{testimonials.map((_, i) => (
|
|
<button
|
|
key={i}
|
|
type="button"
|
|
onClick={() => setTestimonialIndex(i)}
|
|
className={`h-1.5 rounded-full transition-all duration-300 ${i === testimonialIndex ? "w-5 bg-[#1BB0CE]" : "w-1.5 bg-[#1BB0CE]/30"}`}
|
|
/>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
<canvas ref={canvasRef} className="wave-canvas" />
|
|
</div>
|
|
|
|
<div className="panel-divider" />
|
|
|
|
<div className="right-panel flex items-center justify-center p-10">
|
|
<canvas ref={starsCanvasRightRef} className="stars-canvas" />
|
|
<div className="accent-line" />
|
|
|
|
<div className="w-full" style={{ maxWidth: 340 }}>
|
|
<h2 className="text-[24px] font-bold text-[#e8e8ef] tracking-[-0.3px] mb-[5px]">
|
|
Welcome back
|
|
</h2>
|
|
<p className="text-[13px] mb-[26px] subheading-text">
|
|
Sign in to your account to continue
|
|
</p>
|
|
|
|
{error && (
|
|
<div className="mb-4 rounded bg-red-500/10 px-4 py-2 text-sm text-red-400">
|
|
{error}
|
|
</div>
|
|
)}
|
|
|
|
<form onSubmit={handleSubmit} className="space-y-5">
|
|
<div>
|
|
<label htmlFor="email" className="login-label">
|
|
Email
|
|
</label>
|
|
<div className="input-sheen">
|
|
<input
|
|
id="email"
|
|
type="email"
|
|
placeholder="name@company.com"
|
|
value={email}
|
|
onChange={(e) => setEmail(e.target.value)}
|
|
required
|
|
autoComplete="email"
|
|
className="login-input"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<div className="flex items-center justify-between mb-[6px]">
|
|
<label htmlFor="password" className="login-label" style={{ marginBottom: 0 }}>
|
|
Password
|
|
</label>
|
|
<button type="button" className="text-[12px] text-[#1BB0CE] hover:underline">
|
|
Forgot password?
|
|
</button>
|
|
</div>
|
|
<div className="input-sheen input-sheen-delayed">
|
|
<div className="relative">
|
|
<input
|
|
id="password"
|
|
type={showPassword ? "text" : "password"}
|
|
placeholder="Enter your password"
|
|
value={password}
|
|
onChange={(e) => setPassword(e.target.value)}
|
|
required
|
|
autoComplete="current-password"
|
|
className="login-input"
|
|
/>
|
|
<button
|
|
type="button"
|
|
onClick={() => setShowPassword(!showPassword)}
|
|
className="password-toggle"
|
|
>
|
|
{showPassword ? (
|
|
<EyeOff className="h-4 w-4" />
|
|
) : (
|
|
<Eye className="h-4 w-4" />
|
|
)}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<label className="flex items-center gap-2 cursor-pointer">
|
|
<input
|
|
type="checkbox"
|
|
checked={remember}
|
|
onChange={(e) => setRemember(e.target.checked)}
|
|
className="login-checkbox"
|
|
/>
|
|
<span className="text-[13px] checkbox-text">
|
|
Remember me
|
|
</span>
|
|
</label>
|
|
|
|
<button type="submit" className="auth-btn" disabled={loading}>
|
|
{loading && <Loader2 className="h-4 w-4 animate-spin" />}
|
|
{loading ? "Signing in..." : "Sign in"}
|
|
</button>
|
|
</form>
|
|
|
|
<p className="text-center text-[11px] mt-4 footer-text">
|
|
By signing in, you agree to our{" "}
|
|
<button type="button" className="text-[#1BB0CE] hover:underline">
|
|
Terms of Service
|
|
</button>{" "}
|
|
and{" "}
|
|
<button type="button" className="text-[#1BB0CE] hover:underline">
|
|
Privacy Policy
|
|
</button>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|