Seth commited on
Commit
edb8309
·
1 Parent(s): e3165a6
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
  }