Login and dashboard added

This commit is contained in:
2026-07-13 16:09:15 +01:00
commit ca40bd4095
36 changed files with 7821 additions and 0 deletions

View File

@@ -0,0 +1,32 @@
"use client";
import { useState } from "react";
import LoginForm from "./loginForm";
import VerifyCode from "./verifyCode";
import ForgotPassword from "./forgetPassword";
import {Step} from "@/constants/types"
export default function AuthCard() {
const [step, setStep] = useState<Step>("login");
return (
<>
{step === "login" && (
<LoginForm
onSuccess={() => setStep("verify")}
onForgotPassword={() => setStep("forgot")}
/>
)}
{step === "verify" && (
<VerifyCode onBack={() => setStep("login")} />
)}
{step === "forgot" && (
<ForgotPassword onBack={() => setStep("login")} />
)}
</>
);
}

View File

@@ -0,0 +1,16 @@
import LeftPanel from "./leftpanel";
interface AuthLayoutProps {
children: React.ReactNode;
}
export default function AuthLayout({ children }: AuthLayoutProps) {
return (
<main className="min-h-screen grid lg:grid-cols-2 bg-[#081321]">
<LeftPanel />
<div className="flex items-center justify-center p-8">
{children}
</div>
</main>
);
}

View File

@@ -0,0 +1,39 @@
import FormInput from "@/utils/formInput";
import Button from "@/utils/button";
import {Props} from "@/constants/types"
export default function ForgotPassword({ onBack }: Props) {
return (
<div className="space-y-4">
<button
onClick={onBack}
className="text-slate-400 hover:text-white"
>
Back to sign in
</button>
<div>
<h2 className="text-2xl font-bold text-white">
Reset your password
</h2>
<p className="mt-3 text-slate-400">
Enter your work email and we'll send a secure
time-limited reset link.
</p>
</div>
{/* Your email input */}
<FormInput
label="Email"
value="dhananjay@kafm.io"
/>
{/* Send reset button */}
<Button className="h-10 w-full rounded-xl bg-gradient-to-r from-teal-400 to-emerald-400 font-semibold text-black">
Send reset link
</Button>
</div>
);
}

View File

@@ -0,0 +1,83 @@
"use client";
import Badge from "@/utils/badge";
import Stat from "@/utils/statCard";
import { ShieldCheck, KeyRound, FileText } from "lucide-react";
export default function LeftPanel() {
return (
<div className="relative overflow-hidden border-r border-slate-800 bg-[#091523] hidden lg:block">
{/* Grid Background overlay */}
<div
className="absolute inset-0 opacity-10 pointer-events-none"
style={{
backgroundImage: `
linear-gradient(rgba(255,255,255,.05) 1px, transparent 1px),
linear-gradient(90deg, rgba(255,255,255,.05) 1px, transparent 1px)
`,
backgroundSize: "44px 44px",
}}
/>
<div className="relative flex h-full flex-col justify-between p-16 z-10">
<div>
{/* Logo Group */}
<div className="mb-20 flex items-center gap-3">
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-gradient-to-br from-teal-300 to-cyan-500 font-bold text-black shadow-md text-lg">
K
</div>
<h2 className="text-2xl font-bold tracking-wider text-white">
KAF<span className="text-teal-400">M</span>
</h2>
</div>
{/* Subtitle */}
<p className="mb-6 text-xs font-semibold uppercase tracking-[4px] text-teal-400/90">
Unified Facility Platform
</p>
{/* Punchy Catchphrase */}
<h1 className="max-w-lg text-4xl font-bold tracking-tight text-white leading-[1]">
One platform.
<br />
Configure it.
<br />
Run it.
</h1>
{/* Paragraph explanation */}
<p className="mt-6 max-w-md text-[16px] leading-relaxed text-slate-400">
Administer tenants, sites and access then operate every
building from the same console. Multi-tenant, scoped to the site
in front of you.
</p>
{/* Badges Stack */}
<div className="mt-10 flex flex-col items-start gap-3">
<Badge icon={<ShieldCheck size={14} className="text-teal-400" />}>
MFA enforced
</Badge>
<Badge icon={<KeyRound size={14} className="text-teal-400" />}>
Encrypted in transit
</Badge>
<Badge icon={<FileText size={14} className="text-teal-400" />}>
Full audit trail
</Badge>
</div>
</div>
{/* Global Statistics Footer */}
<div className=" flex gap-2 mt-10 text-slate-300 ">
<Stat number="4" label="TENANTS" />
<Stat number="8" label="MODULES" />
<Stat number="2" label="WORKSPACES" />
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,103 @@
import { Eye,EyeOff } from "lucide-react";
import FormInput from "@/utils/formInput";
import Button from "@/utils/button";
import { useState } from "react";
import {Props} from "@/constants/types"
export default function LoginForm({ onSuccess,onForgotPassword }: Props) {
const [showPassword, setShowPassword] = useState(false);
return (
<div className="w-full max-w-md space-y-3">
{/* Header text container */}
<div>
<h2 className="text-2xl font-bold tracking-tight text-white">
Sign in to your account
</h2>
<p className="mt-3 text-[15px] text-slate-400">
Use your registered work credentials.
</p>
</div>
{/* Field Elements */}
<div className="space-y-2">
<FormInput
label="Email"
value="dhananjay@kafm.io"
/>
<div className="relative">
<FormInput
label="PASSWORD"
value=""
type={showPassword ? "text" : "password"}
/>
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
className="absolute right-4 top-[40px] text-slate-500 hover:text-slate-300"
>
{showPassword ? <EyeOff size={18} /> : <Eye size={18} />}
</button>
</div>
</div>
{/* Interactive options row */}
<div className="flex items-center justify-between text-sm">
<label className="flex cursor-pointer items-center gap-2.5 text-slate-300 select-none">
<input
type="checkbox"
defaultChecked
className="h-4 w-4 rounded border-slate-700 bg-slate-900 accent-teal-400 focus:ring-0 focus:ring-offset-0"
/>
Keep me signed in
</label>
<Button
onClick={onForgotPassword}
className="font-medium text-teal-400 transition hover:text-teal-300 bg-transparent border-0 p-0 cursor-pointer"
>
Forgot password?
</Button>
</div>
{/* Actions / Submit Stack */}
<div className="space-y-4">
<Button
onClick={onSuccess}
className="h-10 w-full rounded-lg bg-gradient-to-r from-teal-400 to-emerald-400 font-semibold text-slate-950 shadow-md hover:opacity-95 transition"
>
Sign in
</Button>
{/* Custom Visual Divider */}
<div className="flex items-center gap-4 py-2">
<div className="h-px flex-1 bg-slate-800" />
<span className="text-xs uppercase tracking-wider text-slate-600 font-medium">or</span>
<div className="h-px flex-1 bg-slate-800" />
</div>
<Button
className="h-10 w-full rounded-lg border border-slate-800 bg-[#122031] text-sm font-semibold text-white hover:bg-[#17293e] transition"
>
Sign in with SSO
</Button>
</div>
{/* Bottom Legal Info */}
<p className="text-center text-xs tracking-wide text-slate-500 font-mono">
Protected by 2-factor auth every sign-in is
<br/> audit-logged
</p>
</div>
);
}

View File

@@ -0,0 +1,32 @@
"use client";
import { useRef } from "react";
export default function OTPInput() {
const refs = useRef<(HTMLInputElement | null)[]>([]);
const handleChange = (
e: React.ChangeEvent<HTMLInputElement>,
index: number
) => {
if (e.target.value && index < 5) {
refs.current[index + 1]?.focus();
}
};
return (
<div className="flex gap-3">
{[...Array(6)].map((_, index) => (
<input
key={index}
maxLength={1}
ref={(el) => {
refs.current[index] = el;
}}
onChange={(e) => handleChange(e, index)}
className="h-16 w-16 rounded-xl border border-slate-700 bg-[#13233a] text-center text-2xl text-white outline-none focus:border-teal-400"
/>
))}
</div>
);
}

View File

@@ -0,0 +1,86 @@
import Button from "@/utils/button";
import OTPInput from "./otpInput";
import { useEffect, useState, } from "react";
import {Props} from "@/constants/types"
export default function VerifyCode({ onBack }: Props) {
const [timer, setTimer] = useState(30);
useEffect(() => {
if (timer === 0) return;
const interval = setInterval(() => {
setTimer((prev) => prev - 1);
}, 1000);
return () => clearInterval(interval);
}, [timer]);
const handleResend = () => {
if (timer > 0) return;
// Call your resend OTP API here
setTimer(30);
};
return (
<div className="space-y-6">
<button
onClick={onBack}
className="text-slate-400 hover:text-white"
>
Back to sign in
</button>
<div>
<h2 className="text-2xl font-bold text-white">
Two-factor authentication
</h2>
<p className="text-slate-400">
Enter the 6-digit code from your authenticator app, or the<br/>
code we sent to{" "}
<span className="font-semibold text-white">
d@kafm.io
</span>.
</p>
</div>
<OTPInput />
<div className="flex items-center gap-2">
<Button
onClick={handleResend}
className={`font-semibold transition-colors ${
timer > 0
? "text-slate-400 cursor-not-allowed"
: "text-teal-400 hover:text-teal-300 cursor-pointer"
}`}
>
Resend code
</Button>
{timer > 0 && (
<span className="text-sm text-slate-500">
{timer}s
</span>
)}
</div>
<label className="flex items-center gap-2 text-slate-300">
<input type="checkbox" />
Trust this device for 30 days
</label>
<Button className="h-10 w-full rounded-xl bg-gradient-to-r from-teal-400 to-emerald-400 font-semibold text-black">
Verify & Continue
</Button>
</div>
);
}

View File

@@ -0,0 +1,50 @@
"use client";
import Link from "next/link";
import { useState } from "react";
import { ChevronDown } from "lucide-react";
import {Props} from "@/constants/types";
export default function SidebarItem({ item }: Props) {
const [open, setOpen] = useState(item.title === "Overview");
return (
<div className="border-b border-slate-800">
<button
onClick={() => setOpen(!open)}
className="flex w-full items-center justify-between px-5 py-4 hover:bg-slate-900"
>
<div className="flex items-center gap-3">
<item.icon size={20} />
<span className="font-medium">{item.title}</span>
</div>
<ChevronDown
size={18}
className={`transition-transform ${
open ? "rotate-180" : ""
}`}
/>
</button>
{open && item.children.length > 0 && (
<div className="ml-8 border-l border-slate-700 pl-5 pb-3">
{item.children.map((child: any) => (
<Link
key={child.title}
href={child.href}
className="group mt-2 flex items-center gap-3 rounded-lg px-3 py-2 text-slate-300 hover:bg-teal-500/20 hover:text-teal-400"
>
<child.icon size={18} />
<span>{child.title}</span>
</Link>
))}
</div>
)}
</div>
);
}

View File

@@ -0,0 +1,73 @@
import { SIDEBAR_ITEMS } from "@/constants/constant";
import SidebarItem from "./sideBarItem";
import {
ShieldCheck,
Activity,
ChevronDown,
ChevronsLeft,
} from "lucide-react";
export default function Sidebar() {
return (
<aside className="w-80 bg-[#071321] text-white h-screen overflow-y-auto">
<div className="flex items-center gap-2.5 px-4 pt-4 pb-3">
<div className="flex h-9 w-9 items-center justify-center rounded-lg bg-[#3de0c4] font-bold text-[#05111d] text-lg">
K
</div>
<span className="text-xl font-bold tracking-tight text-white">
KA<span className="text-[#3de0c4]">FM</span>
</span>
</div>
<div className="mx-3 my-2 flex rounded-xl bg-[#091624] p-1 border border-[#142538]">
<button className="flex flex-1 items-center justify-center gap-1.5 rounded-lg bg-[#3de0c4] py-1.5 text-xs text-black font-semibold shadow-sm">
<Activity size={14} />
Operations
</button>
<button className="flex flex-1 items-center justify-center gap-1.5 rounded-lg py-1.5 text-xs text-slate-400 font-medium hover:text-white transition-colors">
<ShieldCheck size={14} />
Admin
</button>
</div>
<p className="px-4 pt-2 pb-1 text-xs font-semibold uppercase tracking-wider text-slate-500">
Tenant runtime
</p>
<div className="py-4">
{SIDEBAR_ITEMS.map((item) => (
<SidebarItem
key={item.title}
item={item}
/>
))}
</div>
<div className="px-3 pb-2 pt-1 border-t border-[#0f1f30]">
<button className="flex w-full items-center gap-2 rounded-xl py-2 px-3 text-xs text-slate-400 hover:bg-[#091624] hover:text-white transition-colors">
<ChevronsLeft size={16} />
Minimise menu
</button>
</div>
{/* Profile Card Section */}
<div className="m-3 mt-0 rounded-xl border border-[#142538] bg-[#071321] p-2.5">
<div className="flex items-center justify-between">
<div className="flex gap-2.5 items-center">
<div className="flex h-9 w-9 shrink-0 items-center justify-center rounded-lg border border-cyan-600/50 bg-[#0d2430] text-cyan-400 text-xs font-bold">
DT
</div>
<div className="min-w-0 leading-tight">
<h4 className="font-semibold text-xs text-white truncate">Dhananjay T</h4>
<p className="text-[11px] text-slate-500 truncate">
dhananjay@kafm.io
</p>
</div>
</div>
<ChevronDown className="-rotate-90 text-slate-500 shrink-0" size={14} />
</div>
</div>
</aside>
);
}