Seth commited on
Commit ·
edb8309
1
Parent(s): e3165a6
update
Browse files
frontend/src/components/auth/LoginForm.jsx
CHANGED
|
@@ -510,514 +510,3 @@ export default function LoginForm() {
|
|
| 510 |
</div>
|
| 511 |
);
|
| 512 |
}
|
| 513 |
-
import { motion } from "framer-motion";
|
| 514 |
-
import { Button } from "@/components/ui/button";
|
| 515 |
-
import { Input } from "@/components/ui/input";
|
| 516 |
-
import { Separator } from "@/components/ui/separator";
|
| 517 |
-
import {
|
| 518 |
-
Zap,
|
| 519 |
-
Target,
|
| 520 |
-
Upload,
|
| 521 |
-
CheckCircle2,
|
| 522 |
-
ArrowRight,
|
| 523 |
-
Mail,
|
| 524 |
-
Sparkles,
|
| 525 |
-
Shield,
|
| 526 |
-
Globe,
|
| 527 |
-
AlertCircle,
|
| 528 |
-
Loader2,
|
| 529 |
-
} from "lucide-react";
|
| 530 |
-
import { useAuth } from "@/contexts/AuthContext";
|
| 531 |
-
|
| 532 |
-
export default function LoginForm() {
|
| 533 |
-
const { firebaseLogin, requestOTP, verifyOTP } = useAuth();
|
| 534 |
-
const [email, setEmail] = useState("");
|
| 535 |
-
const [showOtp, setShowOtp] = useState(false);
|
| 536 |
-
const [otp, setOtp] = useState(["", "", "", "", "", ""]);
|
| 537 |
-
const [loading, setLoading] = useState(false);
|
| 538 |
-
const [error, setError] = useState("");
|
| 539 |
-
|
| 540 |
-
// Business email validation
|
| 541 |
-
const PERSONAL_EMAIL_DOMAINS = [
|
| 542 |
-
"gmail.com",
|
| 543 |
-
"yahoo.com",
|
| 544 |
-
"hotmail.com",
|
| 545 |
-
"outlook.com",
|
| 546 |
-
"aol.com",
|
| 547 |
-
"icloud.com",
|
| 548 |
-
"mail.com",
|
| 549 |
-
"protonmail.com",
|
| 550 |
-
"yandex.com",
|
| 551 |
-
"zoho.com",
|
| 552 |
-
"gmx.com",
|
| 553 |
-
"live.com",
|
| 554 |
-
"msn.com",
|
| 555 |
-
];
|
| 556 |
-
|
| 557 |
-
const isBusinessEmail = (email) => {
|
| 558 |
-
if (!email || !email.includes("@")) return false;
|
| 559 |
-
const domain = email.split("@")[1].toLowerCase();
|
| 560 |
-
return !PERSONAL_EMAIL_DOMAINS.includes(domain);
|
| 561 |
-
};
|
| 562 |
-
|
| 563 |
-
const handleGoogleLogin = async () => {
|
| 564 |
-
setLoading(true);
|
| 565 |
-
setError("");
|
| 566 |
-
try {
|
| 567 |
-
await firebaseLogin();
|
| 568 |
-
} catch (err) {
|
| 569 |
-
setError(err.message || "Failed to sign in with Google");
|
| 570 |
-
} finally {
|
| 571 |
-
setLoading(false);
|
| 572 |
-
}
|
| 573 |
-
};
|
| 574 |
-
|
| 575 |
-
const handleEmailSubmit = async (e) => {
|
| 576 |
-
e.preventDefault();
|
| 577 |
-
setLoading(true);
|
| 578 |
-
setError("");
|
| 579 |
-
|
| 580 |
-
if (!email) {
|
| 581 |
-
setError("Please enter your email address");
|
| 582 |
-
setLoading(false);
|
| 583 |
-
return;
|
| 584 |
-
}
|
| 585 |
-
|
| 586 |
-
if (!isBusinessEmail(email)) {
|
| 587 |
-
setError("Only business email addresses are allowed. Personal email accounts (Gmail, Yahoo, etc.) are not permitted.");
|
| 588 |
-
setLoading(false);
|
| 589 |
-
return;
|
| 590 |
-
}
|
| 591 |
-
|
| 592 |
-
try {
|
| 593 |
-
await requestOTP(email);
|
| 594 |
-
setShowOtp(true);
|
| 595 |
-
} catch (err) {
|
| 596 |
-
setError(err.message || "Failed to send OTP");
|
| 597 |
-
} finally {
|
| 598 |
-
setLoading(false);
|
| 599 |
-
}
|
| 600 |
-
};
|
| 601 |
-
|
| 602 |
-
const handleOtpChange = (index, value) => {
|
| 603 |
-
if (value.length <= 1 && /^\d*$/.test(value)) {
|
| 604 |
-
const newOtp = [...otp];
|
| 605 |
-
newOtp[index] = value;
|
| 606 |
-
setOtp(newOtp);
|
| 607 |
-
setError("");
|
| 608 |
-
|
| 609 |
-
// Auto-focus next input
|
| 610 |
-
if (value && index < 5) {
|
| 611 |
-
const nextInput = document.getElementById(`otp-${index + 1}`);
|
| 612 |
-
nextInput?.focus();
|
| 613 |
-
}
|
| 614 |
-
}
|
| 615 |
-
};
|
| 616 |
-
|
| 617 |
-
const handleOtpPaste = (e, startIndex = 0) => {
|
| 618 |
-
e.preventDefault();
|
| 619 |
-
const pastedData = e.clipboardData.getData("text");
|
| 620 |
-
// Extract only digits from pasted content
|
| 621 |
-
const digits = pastedData.replace(/\D/g, "").slice(0, 6);
|
| 622 |
-
|
| 623 |
-
if (digits.length > 0) {
|
| 624 |
-
const newOtp = [...otp];
|
| 625 |
-
// Fill the OTP array with pasted digits starting from the current field
|
| 626 |
-
for (let i = 0; i < digits.length && (startIndex + i) < 6; i++) {
|
| 627 |
-
newOtp[startIndex + i] = digits[i];
|
| 628 |
-
}
|
| 629 |
-
setOtp(newOtp);
|
| 630 |
-
setError("");
|
| 631 |
-
|
| 632 |
-
// Focus on the next empty input or the last input if all are filled
|
| 633 |
-
const nextEmptyIndex = Math.min(startIndex + digits.length, 5);
|
| 634 |
-
const nextInput = document.getElementById(`otp-${nextEmptyIndex}`);
|
| 635 |
-
nextInput?.focus();
|
| 636 |
-
}
|
| 637 |
-
};
|
| 638 |
-
|
| 639 |
-
const handleOtpKeyDown = (index, e) => {
|
| 640 |
-
if (e.key === "Backspace" && !otp[index] && index > 0) {
|
| 641 |
-
const prevInput = document.getElementById(`otp-${index - 1}`);
|
| 642 |
-
prevInput?.focus();
|
| 643 |
-
}
|
| 644 |
-
};
|
| 645 |
-
|
| 646 |
-
const handleOtpVerify = async (e) => {
|
| 647 |
-
e.preventDefault();
|
| 648 |
-
setLoading(true);
|
| 649 |
-
setError("");
|
| 650 |
-
|
| 651 |
-
const otpString = otp.join("");
|
| 652 |
-
if (otpString.length !== 6) {
|
| 653 |
-
setError("Please enter a valid 6-digit OTP");
|
| 654 |
-
setLoading(false);
|
| 655 |
-
return;
|
| 656 |
-
}
|
| 657 |
-
|
| 658 |
-
try {
|
| 659 |
-
await verifyOTP(email, otpString);
|
| 660 |
-
// Success - user will be redirected by AuthContext
|
| 661 |
-
} catch (err) {
|
| 662 |
-
setError(err.message || "Invalid OTP. Please try again.");
|
| 663 |
-
setOtp(["", "", "", "", "", ""]);
|
| 664 |
-
} finally {
|
| 665 |
-
setLoading(false);
|
| 666 |
-
}
|
| 667 |
-
};
|
| 668 |
-
|
| 669 |
-
const features = [
|
| 670 |
-
{
|
| 671 |
-
icon: Zap,
|
| 672 |
-
title: "Lightning Fast",
|
| 673 |
-
description: "Process documents in seconds and get outputs for ERP ingestion",
|
| 674 |
-
color: "text-amber-500",
|
| 675 |
-
bg: "bg-amber-50",
|
| 676 |
-
},
|
| 677 |
-
{
|
| 678 |
-
icon: Target,
|
| 679 |
-
title: "100% Accuracy",
|
| 680 |
-
description: "Industry-leading extraction with Visual Reasoning Processor",
|
| 681 |
-
color: "text-emerald-500",
|
| 682 |
-
bg: "bg-emerald-50",
|
| 683 |
-
},
|
| 684 |
-
{
|
| 685 |
-
icon: Globe,
|
| 686 |
-
title: "Any Format, Any Language",
|
| 687 |
-
description: "PDF, images, scanned docs — multi-lingual support included",
|
| 688 |
-
color: "text-blue-500",
|
| 689 |
-
bg: "bg-blue-50",
|
| 690 |
-
},
|
| 691 |
-
];
|
| 692 |
-
|
| 693 |
-
const supportedFormats = [
|
| 694 |
-
{ ext: "PDF", color: "bg-red-500" },
|
| 695 |
-
{ ext: "PNG", color: "bg-blue-500" },
|
| 696 |
-
{ ext: "JPG", color: "bg-green-500" },
|
| 697 |
-
{ ext: "TIFF", color: "bg-purple-500" },
|
| 698 |
-
];
|
| 699 |
-
|
| 700 |
-
return (
|
| 701 |
-
<div className="min-h-screen bg-gradient-to-br from-slate-50 via-white to-blue-50 flex">
|
| 702 |
-
{/* Left Side - Product Showcase */}
|
| 703 |
-
<div className="hidden lg:flex lg:w-[56%] flex-col justify-between p-8 relative overflow-hidden">
|
| 704 |
-
{/* Background Elements */}
|
| 705 |
-
<div className="absolute top-0 right-0 w-96 h-96 bg-blue-100/40 rounded-full blur-3xl -translate-y-1/2 translate-x-1/2" />
|
| 706 |
-
<div className="absolute bottom-0 left-0 w-80 h-80 bg-emerald-100/40 rounded-full blur-3xl translate-y-1/2 -translate-x-1/2" />
|
| 707 |
-
|
| 708 |
-
{/* Logo & Brand */}
|
| 709 |
-
<motion.div
|
| 710 |
-
initial={{ opacity: 0, y: -20 }}
|
| 711 |
-
animate={{ opacity: 1, y: 0 }}
|
| 712 |
-
className="relative z-10 mb-6"
|
| 713 |
-
>
|
| 714 |
-
<div className="flex items-center gap-3">
|
| 715 |
-
<div className="h-12 w-12 flex items-center justify-center flex-shrink-0">
|
| 716 |
-
<img
|
| 717 |
-
src="/logo.png"
|
| 718 |
-
alt="EZOFIS AI Logo"
|
| 719 |
-
className="h-full w-full object-contain"
|
| 720 |
-
onError={(e) => {
|
| 721 |
-
// Fallback: hide image if logo not found
|
| 722 |
-
e.target.style.display = 'none';
|
| 723 |
-
}}
|
| 724 |
-
/>
|
| 725 |
-
</div>
|
| 726 |
-
<div>
|
| 727 |
-
<h1 className="text-2xl font-bold text-slate-900 tracking-tight">EZOFISOCR</h1>
|
| 728 |
-
<p className="text-sm text-slate-500 font-medium">VRP Intelligence</p>
|
| 729 |
-
</div>
|
| 730 |
-
</div>
|
| 731 |
-
</motion.div>
|
| 732 |
-
|
| 733 |
-
{/* Main Content */}
|
| 734 |
-
<motion.div
|
| 735 |
-
initial={{ opacity: 0, y: 20 }}
|
| 736 |
-
animate={{ opacity: 1, y: 0 }}
|
| 737 |
-
transition={{ delay: 0.1 }}
|
| 738 |
-
className="relative z-10 space-y-5 flex-1 flex flex-col justify-center ml-24 xl:ml-36"
|
| 739 |
-
>
|
| 740 |
-
<div className="space-y-3">
|
| 741 |
-
<h2 className="text-3xl xl:text-4xl font-bold text-slate-900 leading-tight">
|
| 742 |
-
Pure Agentic
|
| 743 |
-
<span className="block text-transparent bg-clip-text bg-gradient-to-r from-blue-600 to-indigo-600">
|
| 744 |
-
Document Intelligence
|
| 745 |
-
</span>
|
| 746 |
-
</h2>
|
| 747 |
-
<p className="text-base text-slate-600 max-w-lg leading-relaxed">
|
| 748 |
-
Deterministic, layout-aware extraction (without LLM) using our proprietary{" "}
|
| 749 |
-
<span className="font-semibold text-slate-800">Visual Reasoning Processor (VRP)</span>
|
| 750 |
-
</p>
|
| 751 |
-
</div>
|
| 752 |
-
|
| 753 |
-
{/* Product Preview Card */}
|
| 754 |
-
<motion.div
|
| 755 |
-
initial={{ opacity: 0, scale: 0.95 }}
|
| 756 |
-
animate={{ opacity: 1, scale: 1 }}
|
| 757 |
-
transition={{ delay: 0.3 }}
|
| 758 |
-
className="bg-white rounded-2xl border border-slate-200/80 shadow-xl shadow-slate-200/50 p-4 max-w-lg"
|
| 759 |
-
>
|
| 760 |
-
<div className="border-2 border-dashed border-slate-200 rounded-xl p-5 text-center bg-slate-50/50">
|
| 761 |
-
<div className="w-12 h-12 rounded-full bg-slate-100 flex items-center justify-center mx-auto mb-3">
|
| 762 |
-
<Upload className="w-5 h-5 text-slate-400" />
|
| 763 |
-
</div>
|
| 764 |
-
<p className="text-slate-700 font-medium mb-1 text-sm">Drop a document to extract data</p>
|
| 765 |
-
<p className="text-xs text-slate-400">Invoices, purchase orders, delivery notes, receipts, and operational documents</p>
|
| 766 |
-
|
| 767 |
-
<div className="flex items-center justify-center gap-2 mt-3">
|
| 768 |
-
{supportedFormats.map((format, i) => (
|
| 769 |
-
<span key={i} className={`${format.color} text-white text-xs font-bold px-2 py-1 rounded`}>
|
| 770 |
-
{format.ext}
|
| 771 |
-
</span>
|
| 772 |
-
))}
|
| 773 |
-
</div>
|
| 774 |
-
</div>
|
| 775 |
-
|
| 776 |
-
<div className="flex items-center justify-between mt-3 pt-3 border-t border-slate-100">
|
| 777 |
-
<div className="flex items-center gap-2">
|
| 778 |
-
<div className="w-2 h-2 rounded-full bg-emerald-500 animate-pulse" />
|
| 779 |
-
<span className="text-xs text-slate-600">Ready to extract</span>
|
| 780 |
-
</div>
|
| 781 |
-
<div className="flex items-center gap-1 text-emerald-600">
|
| 782 |
-
<CheckCircle2 className="w-3.5 h-3.5" />
|
| 783 |
-
<span className="text-xs font-semibold">99.8% Accuracy</span>
|
| 784 |
-
</div>
|
| 785 |
-
</div>
|
| 786 |
-
</motion.div>
|
| 787 |
-
|
| 788 |
-
{/* Features */}
|
| 789 |
-
<div className="grid gap-3">
|
| 790 |
-
{features.map((feature, index) => (
|
| 791 |
-
<motion.div
|
| 792 |
-
key={feature.title}
|
| 793 |
-
initial={{ opacity: 0, x: -20 }}
|
| 794 |
-
animate={{ opacity: 1, x: 0 }}
|
| 795 |
-
transition={{ delay: 0.4 + index * 0.1 }}
|
| 796 |
-
className="flex items-start gap-3 group"
|
| 797 |
-
>
|
| 798 |
-
<div
|
| 799 |
-
className={`w-9 h-9 rounded-xl ${feature.bg} flex items-center justify-center flex-shrink-0 group-hover:scale-110 transition-transform`}
|
| 800 |
-
>
|
| 801 |
-
<feature.icon className={`w-4 h-4 ${feature.color}`} />
|
| 802 |
-
</div>
|
| 803 |
-
<div>
|
| 804 |
-
<h3 className="font-semibold text-slate-900 text-sm">{feature.title}</h3>
|
| 805 |
-
<p className="text-xs text-slate-500">{feature.description}</p>
|
| 806 |
-
</div>
|
| 807 |
-
</motion.div>
|
| 808 |
-
))}
|
| 809 |
-
</div>
|
| 810 |
-
</motion.div>
|
| 811 |
-
|
| 812 |
-
{/* Trust Badge */}
|
| 813 |
-
<motion.div
|
| 814 |
-
initial={{ opacity: 0 }}
|
| 815 |
-
animate={{ opacity: 1 }}
|
| 816 |
-
transition={{ delay: 0.6 }}
|
| 817 |
-
className="relative z-10 flex items-center gap-3 text-xs text-slate-500 mt-6"
|
| 818 |
-
>
|
| 819 |
-
<Shield className="w-4 h-4" />
|
| 820 |
-
<span>Enterprise-grade security • SOC 2 Compliant • GDPR Ready</span>
|
| 821 |
-
</motion.div>
|
| 822 |
-
</div>
|
| 823 |
-
|
| 824 |
-
{/* Right Side - Sign In Form */}
|
| 825 |
-
<div className="w-full lg:w-[44%] flex items-center justify-center p-6 sm:p-10">
|
| 826 |
-
<motion.div
|
| 827 |
-
initial={{ opacity: 0, y: 20 }}
|
| 828 |
-
animate={{ opacity: 1, y: 0 }}
|
| 829 |
-
transition={{ delay: 0.2 }}
|
| 830 |
-
className="w-full max-w-md"
|
| 831 |
-
>
|
| 832 |
-
{/* Mobile Logo */}
|
| 833 |
-
<div className="lg:hidden flex items-center justify-center gap-3 mb-8">
|
| 834 |
-
<div className="h-12 w-12 flex items-center justify-center flex-shrink-0">
|
| 835 |
-
<img
|
| 836 |
-
src="/logo.png"
|
| 837 |
-
alt="EZOFIS AI Logo"
|
| 838 |
-
className="h-full w-full object-contain"
|
| 839 |
-
onError={(e) => {
|
| 840 |
-
// Fallback: hide image if logo not found
|
| 841 |
-
e.target.style.display = 'none';
|
| 842 |
-
}}
|
| 843 |
-
/>
|
| 844 |
-
</div>
|
| 845 |
-
<div>
|
| 846 |
-
<h1 className="text-2xl font-bold text-slate-900 tracking-tight">EZOFISOCR</h1>
|
| 847 |
-
<p className="text-sm text-slate-500 font-medium">VRP Intelligence</p>
|
| 848 |
-
</div>
|
| 849 |
-
</div>
|
| 850 |
-
|
| 851 |
-
<div className="bg-white rounded-3xl border border-slate-200/80 shadow-2xl shadow-slate-200/50 p-8 sm:p-10">
|
| 852 |
-
<div className="text-center mb-8">
|
| 853 |
-
<h2 className="text-2xl font-bold text-slate-900 mb-2">
|
| 854 |
-
{showOtp ? "Enter verification code" : "Secure Access"}
|
| 855 |
-
</h2>
|
| 856 |
-
<p className="text-slate-500">
|
| 857 |
-
{showOtp ? `We sent a code to ${email}` : "Access your document intelligence workspace"}
|
| 858 |
-
</p>
|
| 859 |
-
</div>
|
| 860 |
-
|
| 861 |
-
{/* Error Message */}
|
| 862 |
-
{error && (
|
| 863 |
-
<motion.div
|
| 864 |
-
initial={{ opacity: 0, y: -10 }}
|
| 865 |
-
animate={{ opacity: 1, y: 0 }}
|
| 866 |
-
className="mb-6 p-3 bg-red-50 border border-red-200 rounded-xl flex items-start gap-2 text-sm text-red-700"
|
| 867 |
-
>
|
| 868 |
-
<AlertCircle className="h-4 w-4 flex-shrink-0 mt-0.5" />
|
| 869 |
-
<p>{error}</p>
|
| 870 |
-
</motion.div>
|
| 871 |
-
)}
|
| 872 |
-
|
| 873 |
-
{!showOtp ? (
|
| 874 |
-
<>
|
| 875 |
-
{/* Google Sign In */}
|
| 876 |
-
<Button
|
| 877 |
-
onClick={handleGoogleLogin}
|
| 878 |
-
disabled={loading}
|
| 879 |
-
variant="outline"
|
| 880 |
-
className="w-full h-12 text-base font-medium border-slate-200 hover:bg-slate-50 hover:border-slate-300 transition-all group"
|
| 881 |
-
>
|
| 882 |
-
{loading ? (
|
| 883 |
-
<Loader2 className="w-5 h-5 mr-3 animate-spin" />
|
| 884 |
-
) : (
|
| 885 |
-
<svg className="w-5 h-5 mr-3" viewBox="0 0 24 24">
|
| 886 |
-
<path fill="#4285F4" d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z" />
|
| 887 |
-
<path fill="#34A853" d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z" />
|
| 888 |
-
<path fill="#FBBC05" d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z" />
|
| 889 |
-
<path fill="#EA4335" d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z" />
|
| 890 |
-
</svg>
|
| 891 |
-
)}
|
| 892 |
-
Continue with Google
|
| 893 |
-
<ArrowRight className="w-4 h-4 ml-auto opacity-0 -translate-x-2 group-hover:opacity-100 group-hover:translate-x-0 transition-all" />
|
| 894 |
-
</Button>
|
| 895 |
-
|
| 896 |
-
<div className="relative my-8">
|
| 897 |
-
<Separator />
|
| 898 |
-
<span className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 bg-white px-4 text-sm text-slate-400">
|
| 899 |
-
or continue with email
|
| 900 |
-
</span>
|
| 901 |
-
</div>
|
| 902 |
-
|
| 903 |
-
{/* Email Input */}
|
| 904 |
-
<form onSubmit={handleEmailSubmit} className="space-y-4">
|
| 905 |
-
<div className="relative">
|
| 906 |
-
<Mail className="absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5 text-slate-400" />
|
| 907 |
-
<Input
|
| 908 |
-
type="email"
|
| 909 |
-
placeholder="name@company.com"
|
| 910 |
-
value={email}
|
| 911 |
-
onChange={(e) => {
|
| 912 |
-
setEmail(e.target.value);
|
| 913 |
-
setError("");
|
| 914 |
-
}}
|
| 915 |
-
className="h-12 pl-12 text-base border-slate-200 focus:border-blue-500 focus:ring-blue-500"
|
| 916 |
-
/>
|
| 917 |
-
</div>
|
| 918 |
-
<Button
|
| 919 |
-
type="submit"
|
| 920 |
-
disabled={loading}
|
| 921 |
-
className="w-full h-12 text-base font-medium bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-700 hover:to-indigo-700 shadow-lg shadow-blue-500/25 transition-all"
|
| 922 |
-
>
|
| 923 |
-
{loading ? (
|
| 924 |
-
<>
|
| 925 |
-
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
| 926 |
-
Sending...
|
| 927 |
-
</>
|
| 928 |
-
) : (
|
| 929 |
-
<>
|
| 930 |
-
Continue with Email
|
| 931 |
-
<ArrowRight className="w-4 h-4 ml-2" />
|
| 932 |
-
</>
|
| 933 |
-
)}
|
| 934 |
-
</Button>
|
| 935 |
-
</form>
|
| 936 |
-
</>
|
| 937 |
-
) : (
|
| 938 |
-
/* OTP Input */
|
| 939 |
-
<form onSubmit={handleOtpVerify} className="space-y-6">
|
| 940 |
-
<div className="flex justify-center gap-2">
|
| 941 |
-
{otp.map((digit, index) => (
|
| 942 |
-
<Input
|
| 943 |
-
key={index}
|
| 944 |
-
id={`otp-${index}`}
|
| 945 |
-
type="text"
|
| 946 |
-
inputMode="numeric"
|
| 947 |
-
maxLength={1}
|
| 948 |
-
value={digit}
|
| 949 |
-
onChange={(e) => handleOtpChange(index, e.target.value)}
|
| 950 |
-
onKeyDown={(e) => handleOtpKeyDown(index, e)}
|
| 951 |
-
onPaste={(e) => handleOtpPaste(e, index)}
|
| 952 |
-
className="w-12 h-14 text-center text-xl font-semibold border-slate-200 focus:border-blue-500 focus:ring-blue-500"
|
| 953 |
-
/>
|
| 954 |
-
))}
|
| 955 |
-
</div>
|
| 956 |
-
|
| 957 |
-
<Button
|
| 958 |
-
type="submit"
|
| 959 |
-
disabled={loading || otp.join("").length !== 6}
|
| 960 |
-
className="w-full h-12 text-base font-medium bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-700 hover:to-indigo-700 shadow-lg shadow-blue-500/25"
|
| 961 |
-
>
|
| 962 |
-
{loading ? (
|
| 963 |
-
<>
|
| 964 |
-
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
| 965 |
-
Verifying...
|
| 966 |
-
</>
|
| 967 |
-
) : (
|
| 968 |
-
<>
|
| 969 |
-
Verify & Sign In
|
| 970 |
-
<ArrowRight className="w-4 h-4 ml-2" />
|
| 971 |
-
</>
|
| 972 |
-
)}
|
| 973 |
-
</Button>
|
| 974 |
-
|
| 975 |
-
<button
|
| 976 |
-
type="button"
|
| 977 |
-
onClick={() => {
|
| 978 |
-
setShowOtp(false);
|
| 979 |
-
setOtp(["", "", "", "", "", ""]);
|
| 980 |
-
setError("");
|
| 981 |
-
}}
|
| 982 |
-
className="w-full text-sm text-slate-500 hover:text-slate-700 transition-colors"
|
| 983 |
-
>
|
| 984 |
-
← Back to sign in options
|
| 985 |
-
</button>
|
| 986 |
-
</form>
|
| 987 |
-
)}
|
| 988 |
-
|
| 989 |
-
{/* Notice */}
|
| 990 |
-
<div className="mt-8 pt-6 border-t border-slate-100">
|
| 991 |
-
<div className="flex items-start gap-2 text-xs text-slate-400 mb-4">
|
| 992 |
-
<Shield className="w-4 h-4 flex-shrink-0 mt-0.5" />
|
| 993 |
-
<span>Only business email addresses are allowed</span>
|
| 994 |
-
</div>
|
| 995 |
-
<p className="text-xs text-slate-400 text-center leading-relaxed">
|
| 996 |
-
By signing in, you agree to our{" "}
|
| 997 |
-
<a href="#" className="text-blue-600 hover:underline">
|
| 998 |
-
Terms of Service
|
| 999 |
-
</a>{" "}
|
| 1000 |
-
and{" "}
|
| 1001 |
-
<a href="#" className="text-blue-600 hover:underline">
|
| 1002 |
-
Privacy Policy
|
| 1003 |
-
</a>
|
| 1004 |
-
</p>
|
| 1005 |
-
</div>
|
| 1006 |
-
</div>
|
| 1007 |
-
|
| 1008 |
-
{/* Mobile Features */}
|
| 1009 |
-
<div className="lg:hidden mt-8 space-y-4">
|
| 1010 |
-
{features.map((feature) => (
|
| 1011 |
-
<div key={feature.title} className="flex items-center gap-3 text-sm">
|
| 1012 |
-
<div className={`w-8 h-8 rounded-lg ${feature.bg} flex items-center justify-center`}>
|
| 1013 |
-
<feature.icon className={`w-4 h-4 ${feature.color}`} />
|
| 1014 |
-
</div>
|
| 1015 |
-
<span className="text-slate-600">{feature.title}</span>
|
| 1016 |
-
</div>
|
| 1017 |
-
))}
|
| 1018 |
-
</div>
|
| 1019 |
-
</motion.div>
|
| 1020 |
-
</div>
|
| 1021 |
-
</div>
|
| 1022 |
-
);
|
| 1023 |
-
}
|
|
|
|
| 510 |
</div>
|
| 511 |
);
|
| 512 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|