vedanshmadan21 commited on
Commit
2fd574a
·
1 Parent(s): 26159c8

feat: rename to Blue-Fin, add entity onboarding 2-step form with progress indicator

Browse files
frontend/index.html CHANGED
@@ -3,7 +3,7 @@
3
  <head>
4
  <meta charset="UTF-8" />
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
- <title>INTELLI-CREDIT — AI Credit Decisioning</title>
7
  </head>
8
  <body>
9
  <div id="root"></div>
 
3
  <head>
4
  <meta charset="UTF-8" />
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Blue-Fin — AI Credit Decisioning</title>
7
  </head>
8
  <body>
9
  <div id="root"></div>
frontend/src/App.jsx CHANGED
@@ -8,6 +8,7 @@ import AnalysisPage from "./pages/AnalysisPage.jsx";
8
  import CAMPage from "./pages/CAMPage.jsx";
9
  import HistoryPage from "./pages/HistoryPage.jsx";
10
  import AuthPage from "./pages/AuthPage.jsx";
 
11
  import ProtectedRoute from "./components/ProtectedRoute.jsx";
12
 
13
  function AppShell() {
@@ -20,10 +21,46 @@ function AppShell() {
20
  <Routes>
21
  <Route path="/" element={<LandingPage />} />
22
  <Route path="/auth" element={<AuthPage />} />
23
- <Route path="/upload" element={<ProtectedRoute><UploadPage /></ProtectedRoute>} />
24
- <Route path="/analysis/:jobId" element={<ProtectedRoute><AnalysisPage /></ProtectedRoute>} />
25
- <Route path="/cam/:jobId" element={<ProtectedRoute><CAMPage /></ProtectedRoute>} />
26
- <Route path="/history" element={<ProtectedRoute><HistoryPage /></ProtectedRoute>} />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
  </Routes>
28
  </div>
29
  </div>
 
8
  import CAMPage from "./pages/CAMPage.jsx";
9
  import HistoryPage from "./pages/HistoryPage.jsx";
10
  import AuthPage from "./pages/AuthPage.jsx";
11
+ import EntityOnboardingPage from "./pages/EntityOnboardingPage.jsx";
12
  import ProtectedRoute from "./components/ProtectedRoute.jsx";
13
 
14
  function AppShell() {
 
21
  <Routes>
22
  <Route path="/" element={<LandingPage />} />
23
  <Route path="/auth" element={<AuthPage />} />
24
+ <Route
25
+ path="/onboard"
26
+ element={
27
+ <ProtectedRoute>
28
+ <EntityOnboardingPage />
29
+ </ProtectedRoute>
30
+ }
31
+ />
32
+ <Route
33
+ path="/upload"
34
+ element={
35
+ <ProtectedRoute>
36
+ <UploadPage />
37
+ </ProtectedRoute>
38
+ }
39
+ />
40
+ <Route
41
+ path="/analysis/:jobId"
42
+ element={
43
+ <ProtectedRoute>
44
+ <AnalysisPage />
45
+ </ProtectedRoute>
46
+ }
47
+ />
48
+ <Route
49
+ path="/cam/:jobId"
50
+ element={
51
+ <ProtectedRoute>
52
+ <CAMPage />
53
+ </ProtectedRoute>
54
+ }
55
+ />
56
+ <Route
57
+ path="/history"
58
+ element={
59
+ <ProtectedRoute>
60
+ <HistoryPage />
61
+ </ProtectedRoute>
62
+ }
63
+ />
64
  </Routes>
65
  </div>
66
  </div>
frontend/src/components/Navbar.jsx CHANGED
@@ -5,7 +5,7 @@ import { useTheme } from "../context/ThemeContext.jsx";
5
 
6
  const NAV_LINKS = [
7
  { to: "/", label: "Dashboard", icon: LayoutDashboard },
8
- { to: "/upload", label: "Upload", icon: Upload },
9
  { to: "/history", label: "History", icon: History },
10
  ];
11
 
@@ -17,8 +17,11 @@ export default function Navbar() {
17
  return (
18
  <nav className="navbar">
19
  {/* Brand */}
20
- <Link to="/" className="navbar-brand" style={{ textDecoration: 'none' }}>
21
- <span style={{ fontStyle: 'italic', color: 'var(--accent)' }}>Intelli</span>Credit
 
 
 
22
  </Link>
23
 
24
  {/* Center links */}
@@ -41,15 +44,31 @@ export default function Navbar() {
41
  <div className="flex items-center gap-sm">
42
  {user ? (
43
  <>
44
- <div style={{ display: 'flex', alignItems: 'center', gap: '8px', padding: '6px 14px', background: 'var(--bg-elevated)', borderRadius: 'var(--radius-full)', border: '1px solid var(--border)' }}>
45
- <span style={{ fontSize: '13px', fontWeight: 500, color: 'var(--text-secondary)' }}>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
  {user.email}
47
  </span>
48
  </div>
49
  <button
50
  onClick={logout}
51
  className="btn btn-secondary btn-sm"
52
- style={{ borderRadius: 'var(--radius-full)' }}
53
  >
54
  Sign Out
55
  </button>
@@ -58,7 +77,7 @@ export default function Navbar() {
58
  <Link
59
  to="/auth"
60
  className="btn btn-primary btn-sm"
61
- style={{ borderRadius: 'var(--radius-full)' }}
62
  >
63
  Sign In
64
  </Link>
@@ -66,12 +85,12 @@ export default function Navbar() {
66
  <button
67
  onClick={toggleTheme}
68
  className="btn-icon btn-ghost"
69
- style={{ borderRadius: 'var(--radius-sm)' }}
70
  title={`Switch to ${theme === "dark" ? "light" : "dark"} mode`}
71
  >
72
  {theme === "dark" ? <Sun size={16} /> : <Moon size={16} />}
73
  </button>
74
  </div>
75
- </nav>
76
  );
77
  }
 
5
 
6
  const NAV_LINKS = [
7
  { to: "/", label: "Dashboard", icon: LayoutDashboard },
8
+ { to: "/onboard", label: "New Analysis", icon: Upload },
9
  { to: "/history", label: "History", icon: History },
10
  ];
11
 
 
17
  return (
18
  <nav className="navbar">
19
  {/* Brand */}
20
+ <Link to="/" className="navbar-brand" style={{ textDecoration: "none" }}>
21
+ <span style={{ fontStyle: "italic", color: "var(--accent)" }}>
22
+ Blue
23
+ </span>
24
+ -Fin
25
  </Link>
26
 
27
  {/* Center links */}
 
44
  <div className="flex items-center gap-sm">
45
  {user ? (
46
  <>
47
+ <div
48
+ style={{
49
+ display: "flex",
50
+ alignItems: "center",
51
+ gap: "8px",
52
+ padding: "6px 14px",
53
+ background: "var(--bg-elevated)",
54
+ borderRadius: "var(--radius-full)",
55
+ border: "1px solid var(--border)",
56
+ }}
57
+ >
58
+ <span
59
+ style={{
60
+ fontSize: "13px",
61
+ fontWeight: 500,
62
+ color: "var(--text-secondary)",
63
+ }}
64
+ >
65
  {user.email}
66
  </span>
67
  </div>
68
  <button
69
  onClick={logout}
70
  className="btn btn-secondary btn-sm"
71
+ style={{ borderRadius: "var(--radius-full)" }}
72
  >
73
  Sign Out
74
  </button>
 
77
  <Link
78
  to="/auth"
79
  className="btn btn-primary btn-sm"
80
+ style={{ borderRadius: "var(--radius-full)" }}
81
  >
82
  Sign In
83
  </Link>
 
85
  <button
86
  onClick={toggleTheme}
87
  className="btn-icon btn-ghost"
88
+ style={{ borderRadius: "var(--radius-sm)" }}
89
  title={`Switch to ${theme === "dark" ? "light" : "dark"} mode`}
90
  >
91
  {theme === "dark" ? <Sun size={16} /> : <Moon size={16} />}
92
  </button>
93
  </div>
94
+ </nav>
95
  );
96
  }
frontend/src/index.css CHANGED
@@ -1,16 +1,17 @@
1
  /* ═══════════════════════════════════════════════════════════════════════════
2
- INTELLI-CREDIT — Global Design System (Vanilla CSS)
3
  Dark editorial theme · Playfair Display + Inter · Glassmorphism
4
  ═══════════════════════════════════════════════════════════════════════════ */
5
 
6
- @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Playfair+Display:ital,wght@0,400;0,500;0,600;0,700;1,400;1,500;1,600;1,700&display=swap');
7
 
8
  /* ─── CSS Variables ─────────────────────────────────────────────────────── */
9
  :root {
10
  /* Typography */
11
- --font-heading: 'Playfair Display', Georgia, 'Times New Roman', serif;
12
- --font-body: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
13
- --font-mono: 'SF Mono', 'Fira Code', 'Cascadia Code', Consolas, monospace;
 
14
 
15
  /* Spacing */
16
  --space-xs: 4px;
@@ -37,7 +38,8 @@
37
  }
38
 
39
  /* Base colors: Dark Theme (Default) */
40
- :root, [data-theme='dark'] {
 
41
  --bg-primary: #1a1a1f;
42
  --bg-secondary: #212126;
43
  --bg-surface: #242429;
@@ -78,7 +80,7 @@
78
  }
79
 
80
  /* Light Theme Overrides */
81
- [data-theme='light'] {
82
  --bg-primary: #f8fafc;
83
  --bg-secondary: #f1f5f9;
84
  --bg-surface: #ffffff;
@@ -150,28 +152,60 @@ body {
150
  }
151
 
152
  /* ─── Typography ────────────────────────────────────────────────────────── */
153
- h1, h2, h3, h4, h5, h6 {
 
 
 
 
 
154
  font-family: var(--font-heading);
155
  font-weight: 600;
156
  line-height: 1.2;
157
  color: var(--text-primary);
158
  }
159
 
160
- h1 { font-size: clamp(2rem, 5vw, 3.5rem); }
161
- h2 { font-size: clamp(1.5rem, 3.5vw, 2.2rem); }
162
- h3 { font-size: clamp(1.1rem, 2vw, 1.4rem); }
 
 
 
 
 
 
163
 
164
- .serif { font-family: var(--font-heading); }
165
- .serif-italic { font-family: var(--font-heading); font-style: italic; }
166
- .mono { font-family: var(--font-mono); }
 
 
 
 
 
 
 
167
 
168
- .text-primary { color: var(--text-primary); }
169
- .text-secondary { color: var(--text-secondary); }
170
- .text-muted { color: var(--text-muted); }
171
- .text-accent { color: var(--accent); }
172
- .text-success { color: var(--success); }
173
- .text-warning { color: var(--warning); }
174
- .text-danger { color: var(--danger); }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
175
 
176
  .label {
177
  font-family: var(--font-body);
@@ -194,7 +228,7 @@ h3 { font-size: clamp(1.1rem, 2vw, 1.4rem); }
194
  }
195
 
196
  .eyebrow::before {
197
- content: '';
198
  width: 6px;
199
  height: 6px;
200
  border-radius: 50%;
@@ -203,8 +237,13 @@ h3 { font-size: clamp(1.1rem, 2vw, 1.4rem); }
203
  }
204
 
205
  @keyframes pulse-dot {
206
- 0%, 100% { opacity: 1; }
207
- 50% { opacity: 0.4; }
 
 
 
 
 
208
  }
209
 
210
  /* ─── Links ─────────────────────────────────────────────────────────────── */
@@ -496,53 +535,124 @@ tr:hover td {
496
  padding: 0 var(--space-lg);
497
  }
498
 
499
- .flex { display: flex; }
500
- .flex-col { flex-direction: column; }
501
- .flex-wrap { flex-wrap: wrap; }
502
- .items-center { align-items: center; }
503
- .items-start { align-items: flex-start; }
504
- .justify-center { justify-content: center; }
505
- .justify-between { justify-content: space-between; }
506
- .gap-xs { gap: var(--space-xs); }
507
- .gap-sm { gap: var(--space-sm); }
508
- .gap-md { gap: var(--space-md); }
509
- .gap-lg { gap: var(--space-lg); }
510
- .gap-xl { gap: var(--space-xl); }
511
- .flex-1 { flex: 1; }
512
- .shrink-0 { flex-shrink: 0; }
513
-
514
- .grid { display: grid; }
515
- .grid-2 { grid-template-columns: repeat(2, 1fr); }
516
- .grid-3 { grid-template-columns: repeat(3, 1fr); }
517
- .grid-4 { grid-template-columns: repeat(4, 1fr); }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
518
 
519
  @media (max-width: 768px) {
520
- .grid-2, .grid-3, .grid-4 {
 
 
521
  grid-template-columns: 1fr;
522
  }
523
  }
524
 
525
  @media (min-width: 769px) and (max-width: 1024px) {
526
- .grid-3, .grid-4 {
 
527
  grid-template-columns: repeat(2, 1fr);
528
  }
529
  }
530
 
531
- .w-full { width: 100%; }
532
- .text-center { text-align: center; }
533
- .text-right { text-align: right; }
534
- .truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
535
- .whitespace-pre-wrap { white-space: pre-wrap; }
536
- .relative { position: relative; }
537
- .absolute { position: absolute; }
538
- .sticky { position: sticky; }
539
- .hidden { display: none; }
540
- .overflow-hidden { overflow: hidden; }
541
- .overflow-auto { overflow: auto; }
542
- .pointer-events-none { pointer-events: none; }
543
- .select-none { user-select: none; }
544
- .cursor-pointer { cursor: pointer; }
545
- .line-through { text-decoration: line-through; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
546
 
547
  /* ─── Animations ────────────────────────────────────────────────────────── */
548
  .page-enter {
@@ -565,45 +675,79 @@ tr:hover td {
565
  }
566
 
567
  @keyframes slideInRight {
568
- from { opacity: 0; transform: translateX(40px); }
569
- to { opacity: 1; transform: translateX(0); }
 
 
 
 
 
 
570
  }
571
 
572
  @keyframes spin {
573
- to { transform: rotate(360deg); }
 
 
 
 
 
574
  }
575
- .animate-spin { animation: spin 1s linear infinite; }
576
 
577
  @keyframes pulse {
578
- 0%, 100% { opacity: 1; }
579
- 50% { opacity: 0.5; }
 
 
 
 
 
 
 
 
580
  }
581
- .animate-pulse { animation: pulse 2s ease-in-out infinite; }
582
 
583
  /* Marquee */
584
- .marquee-outer { overflow: hidden; }
 
 
585
  .marquee-inner {
586
  display: flex;
587
  width: max-content;
588
  animation: marqueeScroll 32s linear infinite;
589
  }
590
  @keyframes marqueeScroll {
591
- from { transform: translateX(0); }
592
- to { transform: translateX(-50%); }
 
 
 
 
593
  }
594
 
595
  /* Carousel progress */
596
  @keyframes carouselProgress {
597
- from { width: 0%; }
598
- to { width: 100%; }
 
 
 
 
599
  }
600
 
601
  /* Float animation */
602
  @keyframes float {
603
- 0%, 100% { transform: translateY(0); }
604
- 50% { transform: translateY(-8px); }
 
 
 
 
 
 
 
 
605
  }
606
- .animate-float { animation: float 3s ease-in-out infinite; }
607
 
608
  /* ─── Navbar ────────────────────────────────────────────────────────────── */
609
  .navbar {
@@ -768,11 +912,15 @@ tr:hover td {
768
 
769
  /* ─── Visible only on specific screen widths ────────────────────────────── */
770
  @media (max-width: 768px) {
771
- .hide-mobile { display: none !important; }
 
 
772
  }
773
 
774
  @media (min-width: 769px) {
775
- .show-mobile-only { display: none !important; }
 
 
776
  }
777
 
778
  /* ─── Chart & Score Enhancements ────────────────────────────────────────── */
@@ -781,10 +929,18 @@ tr:hover td {
781
  .score-tab-enter > * {
782
  animation: chartSlideUp 0.5s ease-out both;
783
  }
784
- .score-tab-enter > *:nth-child(1) { animation-delay: 0s; }
785
- .score-tab-enter > *:nth-child(2) { animation-delay: 0.1s; }
786
- .score-tab-enter > *:nth-child(3) { animation-delay: 0.2s; }
787
- .score-tab-enter > *:nth-child(4) { animation-delay: 0.3s; }
 
 
 
 
 
 
 
 
788
 
789
  @keyframes chartSlideUp {
790
  from {
@@ -804,7 +960,9 @@ tr:hover td {
804
  }
805
  .score-card-hover:hover {
806
  transform: translateY(-4px);
807
- box-shadow: 0 12px 40px rgba(0, 0, 0, 0.3), 0 0 0 1px rgba(255, 255, 255, 0.08);
 
 
808
  border-color: rgba(255, 255, 255, 0.15);
809
  }
810
 
@@ -832,13 +990,22 @@ tr:hover td {
832
  .stress-flipped-bg {
833
  position: absolute;
834
  inset: 0;
835
- background: linear-gradient(135deg, rgba(239, 68, 68, 0.08) 0%, transparent 60%);
 
 
 
 
836
  animation: stressFlash 3s ease-in-out infinite;
837
  pointer-events: none;
838
  }
839
  @keyframes stressFlash {
840
- 0%, 100% { opacity: 0.3; }
841
- 50% { opacity: 0.8; }
 
 
 
 
 
842
  }
843
 
844
  /* Stress bar fill animation */
@@ -848,13 +1015,19 @@ tr:hover td {
848
  transform-origin: left;
849
  }
850
  @keyframes stressBarGrow {
851
- from { transform: scaleX(0); }
852
- to { transform: scaleX(1); }
 
 
 
 
853
  }
854
 
855
  /* Recharts hover enhancements */
856
  .recharts-bar-rectangle {
857
- transition: filter 0.25s ease, opacity 0.25s ease;
 
 
858
  }
859
 
860
  .recharts-radar-polygon {
@@ -863,7 +1036,9 @@ tr:hover td {
863
 
864
  /* Recharts tooltip animation */
865
  .recharts-tooltip-wrapper {
866
- transition: transform 0.15s ease, opacity 0.15s ease !important;
 
 
867
  }
868
 
869
  /* Recharts active dot pulse */
@@ -878,8 +1053,14 @@ tr:hover td {
878
 
879
  /* Radar chart label animation */
880
  @keyframes radarFadeIn {
881
- from { opacity: 0; transform: scale(0.9); }
882
- to { opacity: 1; transform: scale(1); }
 
 
 
 
 
 
883
  }
884
 
885
  .recharts-polar-angle-axis-tick text {
 
1
  /* ═══════════════════════════════════════════════════════════════════════════
2
+ BLUE-FIN — Global Design System (Vanilla CSS)
3
  Dark editorial theme · Playfair Display + Inter · Glassmorphism
4
  ═══════════════════════════════════════════════════════════════════════════ */
5
 
6
+ @import url("https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Playfair+Display:ital,wght@0,400;0,500;0,600;0,700;1,400;1,500;1,600;1,700&display=swap");
7
 
8
  /* ─── CSS Variables ─────────────────────────────────────────────────────── */
9
  :root {
10
  /* Typography */
11
+ --font-heading: "Playfair Display", Georgia, "Times New Roman", serif;
12
+ --font-body:
13
+ "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
14
+ --font-mono: "SF Mono", "Fira Code", "Cascadia Code", Consolas, monospace;
15
 
16
  /* Spacing */
17
  --space-xs: 4px;
 
38
  }
39
 
40
  /* Base colors: Dark Theme (Default) */
41
+ :root,
42
+ [data-theme="dark"] {
43
  --bg-primary: #1a1a1f;
44
  --bg-secondary: #212126;
45
  --bg-surface: #242429;
 
80
  }
81
 
82
  /* Light Theme Overrides */
83
+ [data-theme="light"] {
84
  --bg-primary: #f8fafc;
85
  --bg-secondary: #f1f5f9;
86
  --bg-surface: #ffffff;
 
152
  }
153
 
154
  /* ─── Typography ────────────────────────────────────────────────────────── */
155
+ h1,
156
+ h2,
157
+ h3,
158
+ h4,
159
+ h5,
160
+ h6 {
161
  font-family: var(--font-heading);
162
  font-weight: 600;
163
  line-height: 1.2;
164
  color: var(--text-primary);
165
  }
166
 
167
+ h1 {
168
+ font-size: clamp(2rem, 5vw, 3.5rem);
169
+ }
170
+ h2 {
171
+ font-size: clamp(1.5rem, 3.5vw, 2.2rem);
172
+ }
173
+ h3 {
174
+ font-size: clamp(1.1rem, 2vw, 1.4rem);
175
+ }
176
 
177
+ .serif {
178
+ font-family: var(--font-heading);
179
+ }
180
+ .serif-italic {
181
+ font-family: var(--font-heading);
182
+ font-style: italic;
183
+ }
184
+ .mono {
185
+ font-family: var(--font-mono);
186
+ }
187
 
188
+ .text-primary {
189
+ color: var(--text-primary);
190
+ }
191
+ .text-secondary {
192
+ color: var(--text-secondary);
193
+ }
194
+ .text-muted {
195
+ color: var(--text-muted);
196
+ }
197
+ .text-accent {
198
+ color: var(--accent);
199
+ }
200
+ .text-success {
201
+ color: var(--success);
202
+ }
203
+ .text-warning {
204
+ color: var(--warning);
205
+ }
206
+ .text-danger {
207
+ color: var(--danger);
208
+ }
209
 
210
  .label {
211
  font-family: var(--font-body);
 
228
  }
229
 
230
  .eyebrow::before {
231
+ content: "";
232
  width: 6px;
233
  height: 6px;
234
  border-radius: 50%;
 
237
  }
238
 
239
  @keyframes pulse-dot {
240
+ 0%,
241
+ 100% {
242
+ opacity: 1;
243
+ }
244
+ 50% {
245
+ opacity: 0.4;
246
+ }
247
  }
248
 
249
  /* ─── Links ─────────────────────────────────────────────────────────────── */
 
535
  padding: 0 var(--space-lg);
536
  }
537
 
538
+ .flex {
539
+ display: flex;
540
+ }
541
+ .flex-col {
542
+ flex-direction: column;
543
+ }
544
+ .flex-wrap {
545
+ flex-wrap: wrap;
546
+ }
547
+ .items-center {
548
+ align-items: center;
549
+ }
550
+ .items-start {
551
+ align-items: flex-start;
552
+ }
553
+ .justify-center {
554
+ justify-content: center;
555
+ }
556
+ .justify-between {
557
+ justify-content: space-between;
558
+ }
559
+ .gap-xs {
560
+ gap: var(--space-xs);
561
+ }
562
+ .gap-sm {
563
+ gap: var(--space-sm);
564
+ }
565
+ .gap-md {
566
+ gap: var(--space-md);
567
+ }
568
+ .gap-lg {
569
+ gap: var(--space-lg);
570
+ }
571
+ .gap-xl {
572
+ gap: var(--space-xl);
573
+ }
574
+ .flex-1 {
575
+ flex: 1;
576
+ }
577
+ .shrink-0 {
578
+ flex-shrink: 0;
579
+ }
580
+
581
+ .grid {
582
+ display: grid;
583
+ }
584
+ .grid-2 {
585
+ grid-template-columns: repeat(2, 1fr);
586
+ }
587
+ .grid-3 {
588
+ grid-template-columns: repeat(3, 1fr);
589
+ }
590
+ .grid-4 {
591
+ grid-template-columns: repeat(4, 1fr);
592
+ }
593
 
594
  @media (max-width: 768px) {
595
+ .grid-2,
596
+ .grid-3,
597
+ .grid-4 {
598
  grid-template-columns: 1fr;
599
  }
600
  }
601
 
602
  @media (min-width: 769px) and (max-width: 1024px) {
603
+ .grid-3,
604
+ .grid-4 {
605
  grid-template-columns: repeat(2, 1fr);
606
  }
607
  }
608
 
609
+ .w-full {
610
+ width: 100%;
611
+ }
612
+ .text-center {
613
+ text-align: center;
614
+ }
615
+ .text-right {
616
+ text-align: right;
617
+ }
618
+ .truncate {
619
+ overflow: hidden;
620
+ text-overflow: ellipsis;
621
+ white-space: nowrap;
622
+ }
623
+ .whitespace-pre-wrap {
624
+ white-space: pre-wrap;
625
+ }
626
+ .relative {
627
+ position: relative;
628
+ }
629
+ .absolute {
630
+ position: absolute;
631
+ }
632
+ .sticky {
633
+ position: sticky;
634
+ }
635
+ .hidden {
636
+ display: none;
637
+ }
638
+ .overflow-hidden {
639
+ overflow: hidden;
640
+ }
641
+ .overflow-auto {
642
+ overflow: auto;
643
+ }
644
+ .pointer-events-none {
645
+ pointer-events: none;
646
+ }
647
+ .select-none {
648
+ user-select: none;
649
+ }
650
+ .cursor-pointer {
651
+ cursor: pointer;
652
+ }
653
+ .line-through {
654
+ text-decoration: line-through;
655
+ }
656
 
657
  /* ─── Animations ────────────────────────────────────────────────────────── */
658
  .page-enter {
 
675
  }
676
 
677
  @keyframes slideInRight {
678
+ from {
679
+ opacity: 0;
680
+ transform: translateX(40px);
681
+ }
682
+ to {
683
+ opacity: 1;
684
+ transform: translateX(0);
685
+ }
686
  }
687
 
688
  @keyframes spin {
689
+ to {
690
+ transform: rotate(360deg);
691
+ }
692
+ }
693
+ .animate-spin {
694
+ animation: spin 1s linear infinite;
695
  }
 
696
 
697
  @keyframes pulse {
698
+ 0%,
699
+ 100% {
700
+ opacity: 1;
701
+ }
702
+ 50% {
703
+ opacity: 0.5;
704
+ }
705
+ }
706
+ .animate-pulse {
707
+ animation: pulse 2s ease-in-out infinite;
708
  }
 
709
 
710
  /* Marquee */
711
+ .marquee-outer {
712
+ overflow: hidden;
713
+ }
714
  .marquee-inner {
715
  display: flex;
716
  width: max-content;
717
  animation: marqueeScroll 32s linear infinite;
718
  }
719
  @keyframes marqueeScroll {
720
+ from {
721
+ transform: translateX(0);
722
+ }
723
+ to {
724
+ transform: translateX(-50%);
725
+ }
726
  }
727
 
728
  /* Carousel progress */
729
  @keyframes carouselProgress {
730
+ from {
731
+ width: 0%;
732
+ }
733
+ to {
734
+ width: 100%;
735
+ }
736
  }
737
 
738
  /* Float animation */
739
  @keyframes float {
740
+ 0%,
741
+ 100% {
742
+ transform: translateY(0);
743
+ }
744
+ 50% {
745
+ transform: translateY(-8px);
746
+ }
747
+ }
748
+ .animate-float {
749
+ animation: float 3s ease-in-out infinite;
750
  }
 
751
 
752
  /* ─── Navbar ────────────────────────────────────────────────────────────── */
753
  .navbar {
 
912
 
913
  /* ─── Visible only on specific screen widths ────────────────────────────── */
914
  @media (max-width: 768px) {
915
+ .hide-mobile {
916
+ display: none !important;
917
+ }
918
  }
919
 
920
  @media (min-width: 769px) {
921
+ .show-mobile-only {
922
+ display: none !important;
923
+ }
924
  }
925
 
926
  /* ─── Chart & Score Enhancements ────────────────────────────────────────── */
 
929
  .score-tab-enter > * {
930
  animation: chartSlideUp 0.5s ease-out both;
931
  }
932
+ .score-tab-enter > *:nth-child(1) {
933
+ animation-delay: 0s;
934
+ }
935
+ .score-tab-enter > *:nth-child(2) {
936
+ animation-delay: 0.1s;
937
+ }
938
+ .score-tab-enter > *:nth-child(3) {
939
+ animation-delay: 0.2s;
940
+ }
941
+ .score-tab-enter > *:nth-child(4) {
942
+ animation-delay: 0.3s;
943
+ }
944
 
945
  @keyframes chartSlideUp {
946
  from {
 
960
  }
961
  .score-card-hover:hover {
962
  transform: translateY(-4px);
963
+ box-shadow:
964
+ 0 12px 40px rgba(0, 0, 0, 0.3),
965
+ 0 0 0 1px rgba(255, 255, 255, 0.08);
966
  border-color: rgba(255, 255, 255, 0.15);
967
  }
968
 
 
990
  .stress-flipped-bg {
991
  position: absolute;
992
  inset: 0;
993
+ background: linear-gradient(
994
+ 135deg,
995
+ rgba(239, 68, 68, 0.08) 0%,
996
+ transparent 60%
997
+ );
998
  animation: stressFlash 3s ease-in-out infinite;
999
  pointer-events: none;
1000
  }
1001
  @keyframes stressFlash {
1002
+ 0%,
1003
+ 100% {
1004
+ opacity: 0.3;
1005
+ }
1006
+ 50% {
1007
+ opacity: 0.8;
1008
+ }
1009
  }
1010
 
1011
  /* Stress bar fill animation */
 
1015
  transform-origin: left;
1016
  }
1017
  @keyframes stressBarGrow {
1018
+ from {
1019
+ transform: scaleX(0);
1020
+ }
1021
+ to {
1022
+ transform: scaleX(1);
1023
+ }
1024
  }
1025
 
1026
  /* Recharts hover enhancements */
1027
  .recharts-bar-rectangle {
1028
+ transition:
1029
+ filter 0.25s ease,
1030
+ opacity 0.25s ease;
1031
  }
1032
 
1033
  .recharts-radar-polygon {
 
1036
 
1037
  /* Recharts tooltip animation */
1038
  .recharts-tooltip-wrapper {
1039
+ transition:
1040
+ transform 0.15s ease,
1041
+ opacity 0.15s ease !important;
1042
  }
1043
 
1044
  /* Recharts active dot pulse */
 
1053
 
1054
  /* Radar chart label animation */
1055
  @keyframes radarFadeIn {
1056
+ from {
1057
+ opacity: 0;
1058
+ transform: scale(0.9);
1059
+ }
1060
+ to {
1061
+ opacity: 1;
1062
+ transform: scale(1);
1063
+ }
1064
  }
1065
 
1066
  .recharts-polar-angle-axis-tick text {
frontend/src/pages/AnalysisPage.jsx CHANGED
@@ -17,7 +17,9 @@ export default function AnalysisPage() {
17
  const sourceRef = useRef(null);
18
 
19
  useEffect(() => {
20
- function onResize() { setIsMobile(window.innerWidth < 768); }
 
 
21
  window.addEventListener("resize", onResize);
22
  return () => window.removeEventListener("resize", onResize);
23
  }, []);
@@ -32,7 +34,10 @@ export default function AnalysisPage() {
32
  setResult(event.data);
33
  setIsComplete(true);
34
  setPercent(100);
35
- if (sourceRef.current) { sourceRef.current.close(); sourceRef.current = null; }
 
 
 
36
  } else if (event.type === "error") {
37
  setSseError(event.message);
38
  setEvents((prev) => [...prev, event]);
@@ -42,12 +47,20 @@ export default function AnalysisPage() {
42
  useEffect(() => {
43
  const source = createSSEConnection(jobId, handleEvent);
44
  sourceRef.current = source;
45
- return () => { if (sourceRef.current) { sourceRef.current.close(); sourceRef.current = null; } };
 
 
 
 
 
46
  }, []);
47
 
48
  if (sseError) {
49
  return (
50
- <div className="page-enter flex items-center justify-center" style={{ minHeight: "100vh", padding: "24px" }}>
 
 
 
51
  <div
52
  className="card"
53
  style={{
@@ -59,11 +72,22 @@ export default function AnalysisPage() {
59
  textAlign: "center",
60
  }}
61
  >
62
- <span className="label" style={{ color: "var(--danger)", marginBottom: "8px", display: "block" }}>
 
 
 
 
 
 
 
63
  Pipeline Error
64
  </span>
65
  <p style={{ fontSize: "15px", marginBottom: "24px" }}>{sseError}</p>
66
- <Link to="/" className="btn btn-primary btn-sm" style={{ textDecoration: "none" }}>
 
 
 
 
67
  Try Again
68
  </Link>
69
  </div>
@@ -73,34 +97,59 @@ export default function AnalysisPage() {
73
 
74
  if (isMobile) {
75
  return (
76
- <div className="page-enter flex flex-col items-center justify-center" style={{ minHeight: "100vh", padding: "32px", textAlign: "center", gap: "16px" }}>
77
- <span className="serif" style={{ fontSize: "20px", fontWeight: 700, color: "var(--accent)" }}>
78
- INTELLI-CREDIT
 
 
 
 
 
 
 
 
 
 
 
79
  </span>
80
- <p style={{ fontWeight: 600 }}>Analysis dashboard is optimised for desktop.</p>
 
 
81
  <p style={{ color: "var(--text-muted)", fontSize: "13px" }}>
82
  Please open this page on a larger screen.
83
  </p>
84
- <Link to="/" style={{ fontSize: "13px" }}>← Back to Home</Link>
 
 
85
  </div>
86
  );
87
  }
88
 
89
  if (!isComplete) {
90
- return <PipelineProgress events={events} percent={percent} currentStage={currentStage} />;
 
 
 
 
 
 
91
  }
92
 
93
  return (
94
  <div className="page-enter">
95
- <ResultsDashboard
96
- result={result}
97
- activeTab={activeTab}
98
- onTabChange={setActiveTab}
99
  jobId={jobId}
100
  onScoreUpdate={(newScore, newDecision, delta) => {
101
  setResult((prev) => ({
102
  ...prev,
103
- score_breakdown: { ...prev.score_breakdown, final_score: newScore, decision: newDecision },
 
 
 
 
104
  }));
105
  }}
106
  />
 
17
  const sourceRef = useRef(null);
18
 
19
  useEffect(() => {
20
+ function onResize() {
21
+ setIsMobile(window.innerWidth < 768);
22
+ }
23
  window.addEventListener("resize", onResize);
24
  return () => window.removeEventListener("resize", onResize);
25
  }, []);
 
34
  setResult(event.data);
35
  setIsComplete(true);
36
  setPercent(100);
37
+ if (sourceRef.current) {
38
+ sourceRef.current.close();
39
+ sourceRef.current = null;
40
+ }
41
  } else if (event.type === "error") {
42
  setSseError(event.message);
43
  setEvents((prev) => [...prev, event]);
 
47
  useEffect(() => {
48
  const source = createSSEConnection(jobId, handleEvent);
49
  sourceRef.current = source;
50
+ return () => {
51
+ if (sourceRef.current) {
52
+ sourceRef.current.close();
53
+ sourceRef.current = null;
54
+ }
55
+ };
56
  }, []);
57
 
58
  if (sseError) {
59
  return (
60
+ <div
61
+ className="page-enter flex items-center justify-center"
62
+ style={{ minHeight: "100vh", padding: "24px" }}
63
+ >
64
  <div
65
  className="card"
66
  style={{
 
72
  textAlign: "center",
73
  }}
74
  >
75
+ <span
76
+ className="label"
77
+ style={{
78
+ color: "var(--danger)",
79
+ marginBottom: "8px",
80
+ display: "block",
81
+ }}
82
+ >
83
  Pipeline Error
84
  </span>
85
  <p style={{ fontSize: "15px", marginBottom: "24px" }}>{sseError}</p>
86
+ <Link
87
+ to="/"
88
+ className="btn btn-primary btn-sm"
89
+ style={{ textDecoration: "none" }}
90
+ >
91
  Try Again
92
  </Link>
93
  </div>
 
97
 
98
  if (isMobile) {
99
  return (
100
+ <div
101
+ className="page-enter flex flex-col items-center justify-center"
102
+ style={{
103
+ minHeight: "100vh",
104
+ padding: "32px",
105
+ textAlign: "center",
106
+ gap: "16px",
107
+ }}
108
+ >
109
+ <span
110
+ className="serif"
111
+ style={{ fontSize: "20px", fontWeight: 700, color: "var(--accent)" }}
112
+ >
113
+ BLUE-FIN
114
  </span>
115
+ <p style={{ fontWeight: 600 }}>
116
+ Analysis dashboard is optimised for desktop.
117
+ </p>
118
  <p style={{ color: "var(--text-muted)", fontSize: "13px" }}>
119
  Please open this page on a larger screen.
120
  </p>
121
+ <Link to="/" style={{ fontSize: "13px" }}>
122
+ ← Back to Home
123
+ </Link>
124
  </div>
125
  );
126
  }
127
 
128
  if (!isComplete) {
129
+ return (
130
+ <PipelineProgress
131
+ events={events}
132
+ percent={percent}
133
+ currentStage={currentStage}
134
+ />
135
+ );
136
  }
137
 
138
  return (
139
  <div className="page-enter">
140
+ <ResultsDashboard
141
+ result={result}
142
+ activeTab={activeTab}
143
+ onTabChange={setActiveTab}
144
  jobId={jobId}
145
  onScoreUpdate={(newScore, newDecision, delta) => {
146
  setResult((prev) => ({
147
  ...prev,
148
+ score_breakdown: {
149
+ ...prev.score_breakdown,
150
+ final_score: newScore,
151
+ decision: newDecision,
152
+ },
153
  }));
154
  }}
155
  />
frontend/src/pages/EntityOnboardingPage.jsx ADDED
@@ -0,0 +1,717 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useState } from "react";
2
+ import { useNavigate } from "react-router-dom";
3
+ import {
4
+ ArrowRight,
5
+ ArrowLeft,
6
+ Building2,
7
+ FileText,
8
+ CheckCircle,
9
+ } from "lucide-react";
10
+
11
+ const SECTORS = [
12
+ "Manufacturing",
13
+ "Textiles",
14
+ "Chemicals",
15
+ "Infrastructure",
16
+ "Real Estate",
17
+ "Trading",
18
+ "Services",
19
+ "NBFC",
20
+ "Other",
21
+ ];
22
+
23
+ const CONSTITUTIONS = [
24
+ "Private Limited",
25
+ "Public Limited",
26
+ "LLP",
27
+ "Partnership",
28
+ "Proprietorship",
29
+ ];
30
+
31
+ const LOAN_TYPES = [
32
+ "Term Loan",
33
+ "Working Capital",
34
+ "Cash Credit",
35
+ "Letter of Credit",
36
+ "Bank Guarantee",
37
+ "Mixed Facility",
38
+ ];
39
+
40
+ const COLLATERAL_TYPES = [
41
+ "Immovable Property",
42
+ "Plant & Machinery",
43
+ "Receivables",
44
+ "FD Lien",
45
+ "Unsecured",
46
+ "Mixed",
47
+ ];
48
+
49
+ const CIN_REGEX = /^[UL]\d{5}[A-Z]{2}\d{4}[A-Z]{3}\d{6}$/;
50
+ const PAN_REGEX = /^[A-Z]{5}\d{4}[A-Z]$/;
51
+
52
+ export default function EntityOnboardingPage() {
53
+ const navigate = useNavigate();
54
+ const [step, setStep] = useState(1);
55
+ const [errors, setErrors] = useState({});
56
+
57
+ const [entity, setEntity] = useState({
58
+ companyName: "",
59
+ cin: "",
60
+ pan: "",
61
+ gstin: "",
62
+ sector: "",
63
+ subSector: "",
64
+ annualTurnover: "",
65
+ yearsInOperation: "",
66
+ constitution: "",
67
+ });
68
+
69
+ const [loan, setLoan] = useState({
70
+ loanType: "",
71
+ loanAmount: "",
72
+ tenure: "",
73
+ interestRate: "",
74
+ purpose: "",
75
+ collateralType: "",
76
+ });
77
+
78
+ function updateEntity(field, value) {
79
+ setEntity((prev) => ({ ...prev, [field]: value }));
80
+ if (errors[field])
81
+ setErrors((prev) => {
82
+ const n = { ...prev };
83
+ delete n[field];
84
+ return n;
85
+ });
86
+ }
87
+
88
+ function updateLoan(field, value) {
89
+ setLoan((prev) => ({ ...prev, [field]: value }));
90
+ if (errors[field])
91
+ setErrors((prev) => {
92
+ const n = { ...prev };
93
+ delete n[field];
94
+ return n;
95
+ });
96
+ }
97
+
98
+ function validateStep1() {
99
+ const errs = {};
100
+ if (!entity.companyName.trim())
101
+ errs.companyName = "Company name is required";
102
+ if (entity.cin && !CIN_REGEX.test(entity.cin.toUpperCase()))
103
+ errs.cin = "Invalid CIN format";
104
+ if (entity.pan && !PAN_REGEX.test(entity.pan.toUpperCase()))
105
+ errs.pan = "Invalid PAN format (e.g. ABCDE1234F)";
106
+ if (!entity.sector) errs.sector = "Please select a sector";
107
+ setErrors(errs);
108
+ return Object.keys(errs).length === 0;
109
+ }
110
+
111
+ function validateStep2() {
112
+ const errs = {};
113
+ if (!loan.loanType) errs.loanType = "Please select loan type";
114
+ if (!loan.loanAmount || Number(loan.loanAmount) <= 0)
115
+ errs.loanAmount = "Loan amount is required";
116
+ if (loan.purpose && loan.purpose.length > 500)
117
+ errs.purpose = "Max 500 characters";
118
+ setErrors(errs);
119
+ return Object.keys(errs).length === 0;
120
+ }
121
+
122
+ function handleNext() {
123
+ if (validateStep1()) setStep(2);
124
+ }
125
+
126
+ function handleProceed() {
127
+ if (validateStep2()) {
128
+ // Store form data in sessionStorage for display purposes
129
+ sessionStorage.setItem("bluefin_entity", JSON.stringify(entity));
130
+ sessionStorage.setItem("bluefin_loan", JSON.stringify(loan));
131
+ navigate("/upload", {
132
+ state: { companyName: entity.companyName.trim() },
133
+ });
134
+ }
135
+ }
136
+
137
+ function FieldError({ field }) {
138
+ if (!errors[field]) return null;
139
+ return (
140
+ <span
141
+ style={{
142
+ color: "var(--danger)",
143
+ fontSize: "11px",
144
+ marginTop: "4px",
145
+ display: "block",
146
+ }}
147
+ >
148
+ {errors[field]}
149
+ </span>
150
+ );
151
+ }
152
+
153
+ const inputStyle = {
154
+ borderRadius: "var(--radius-lg)",
155
+ padding: "12px 16px",
156
+ width: "100%",
157
+ };
158
+ const labelStyle = {
159
+ display: "block",
160
+ marginBottom: "6px",
161
+ fontSize: "12px",
162
+ fontWeight: 600,
163
+ color: "var(--text-secondary)",
164
+ textTransform: "uppercase",
165
+ letterSpacing: "0.5px",
166
+ };
167
+
168
+ return (
169
+ <div
170
+ className="page-enter"
171
+ style={{
172
+ minHeight: "100vh",
173
+ display: "flex",
174
+ flexDirection: "column",
175
+ alignItems: "center",
176
+ padding: "32px 24px",
177
+ }}
178
+ >
179
+ {/* ── Progress Steps ── */}
180
+ <div
181
+ style={{
182
+ display: "flex",
183
+ alignItems: "center",
184
+ gap: "0",
185
+ marginBottom: "48px",
186
+ width: "100%",
187
+ maxWidth: "700px",
188
+ }}
189
+ >
190
+ {["Entity Details", "Documents", "Analysis", "Report"].map(
191
+ (label, i) => {
192
+ const stepNum = i + 1;
193
+ const isActive = (stepNum === 1 && step <= 2) || false;
194
+ const isCompleted = false;
195
+ let status = "pending";
196
+ if (stepNum === 1 && step >= 1)
197
+ status = step > 2 ? "completed" : "active";
198
+ if (stepNum === 2 && step > 2) status = "active";
199
+
200
+ return (
201
+ <div
202
+ key={label}
203
+ style={{
204
+ display: "flex",
205
+ alignItems: "center",
206
+ flex: i < 3 ? 1 : "none",
207
+ }}
208
+ >
209
+ <div
210
+ style={{
211
+ display: "flex",
212
+ flexDirection: "column",
213
+ alignItems: "center",
214
+ minWidth: "fit-content",
215
+ }}
216
+ >
217
+ <div
218
+ className="step-indicator-dot"
219
+ style={{
220
+ width: "36px",
221
+ height: "36px",
222
+ borderRadius: "50%",
223
+ display: "flex",
224
+ alignItems: "center",
225
+ justifyContent: "center",
226
+ fontSize: "13px",
227
+ fontWeight: 700,
228
+ transition: "all 0.4s cubic-bezier(0.4, 0, 0.2, 1)",
229
+ background:
230
+ status === "active"
231
+ ? "var(--accent)"
232
+ : status === "completed"
233
+ ? "var(--success)"
234
+ : "var(--bg-elevated)",
235
+ border: `2px solid ${status === "active" ? "var(--accent)" : status === "completed" ? "var(--success)" : "var(--border)"}`,
236
+ color:
237
+ status === "active" || status === "completed"
238
+ ? "#fff"
239
+ : "var(--text-muted)",
240
+ boxShadow:
241
+ status === "active"
242
+ ? "0 0 20px rgba(59,130,246,0.3)"
243
+ : "none",
244
+ }}
245
+ >
246
+ {status === "completed" ? (
247
+ <CheckCircle size={16} />
248
+ ) : (
249
+ stepNum
250
+ )}
251
+ </div>
252
+ <span
253
+ style={{
254
+ fontSize: "11px",
255
+ fontWeight: status === "active" ? 600 : 400,
256
+ color:
257
+ status === "active"
258
+ ? "var(--text-primary)"
259
+ : "var(--text-muted)",
260
+ marginTop: "6px",
261
+ whiteSpace: "nowrap",
262
+ transition: "all 0.3s ease",
263
+ }}
264
+ >
265
+ {label}
266
+ </span>
267
+ </div>
268
+ {i < 3 && (
269
+ <div
270
+ style={{
271
+ flex: 1,
272
+ height: "2px",
273
+ margin: "0 12px",
274
+ marginBottom: "20px",
275
+ borderRadius: "1px",
276
+ background: "var(--border)",
277
+ position: "relative",
278
+ overflow: "hidden",
279
+ }}
280
+ >
281
+ <div
282
+ style={{
283
+ position: "absolute",
284
+ top: 0,
285
+ left: 0,
286
+ bottom: 0,
287
+ width:
288
+ status === "completed" || (stepNum === 1 && step >= 2)
289
+ ? "100%"
290
+ : "0%",
291
+ background: "var(--accent)",
292
+ transition: "width 0.6s cubic-bezier(0.4, 0, 0.2, 1)",
293
+ borderRadius: "1px",
294
+ }}
295
+ />
296
+ </div>
297
+ )}
298
+ </div>
299
+ );
300
+ },
301
+ )}
302
+ </div>
303
+
304
+ {/* ── Form Card ── */}
305
+ <div
306
+ className="card"
307
+ style={{
308
+ width: "100%",
309
+ maxWidth: "700px",
310
+ padding: "40px",
311
+ borderRadius: "var(--radius-xl)",
312
+ position: "relative",
313
+ overflow: "hidden",
314
+ }}
315
+ >
316
+ {/* Animated step transition */}
317
+ <div
318
+ style={{
319
+ display: "flex",
320
+ transition: "transform 0.5s cubic-bezier(0.4, 0, 0.2, 1)",
321
+ transform: `translateX(${step === 1 ? "0" : "-100%"})`,
322
+ width: "200%",
323
+ }}
324
+ >
325
+ {/* ═══ STEP 1: Entity Details ═══ */}
326
+ <div style={{ width: "50%", flexShrink: 0, paddingRight: "40px" }}>
327
+ <div
328
+ style={{
329
+ display: "flex",
330
+ alignItems: "center",
331
+ gap: "12px",
332
+ marginBottom: "32px",
333
+ }}
334
+ >
335
+ <div
336
+ style={{
337
+ width: "40px",
338
+ height: "40px",
339
+ borderRadius: "12px",
340
+ background: "rgba(59,130,246,0.1)",
341
+ display: "flex",
342
+ alignItems: "center",
343
+ justifyContent: "center",
344
+ }}
345
+ >
346
+ <Building2 size={20} style={{ color: "var(--accent)" }} />
347
+ </div>
348
+ <div>
349
+ <h2 style={{ fontSize: "20px", marginBottom: "2px" }}>
350
+ Entity Details
351
+ </h2>
352
+ <p style={{ fontSize: "12px", color: "var(--text-muted)" }}>
353
+ Basic information about the borrower
354
+ </p>
355
+ </div>
356
+ </div>
357
+
358
+ <div
359
+ style={{
360
+ display: "grid",
361
+ gridTemplateColumns: "1fr 1fr",
362
+ gap: "16px",
363
+ }}
364
+ >
365
+ {/* Company Name — full width */}
366
+ <div style={{ gridColumn: "1 / -1" }}>
367
+ <label style={labelStyle}>
368
+ Company Name <span style={{ color: "var(--danger)" }}>*</span>
369
+ </label>
370
+ <input
371
+ className="input"
372
+ style={inputStyle}
373
+ value={entity.companyName}
374
+ onChange={(e) => updateEntity("companyName", e.target.value)}
375
+ placeholder="e.g. Mehta Textiles Pvt Ltd"
376
+ />
377
+ <FieldError field="companyName" />
378
+ </div>
379
+
380
+ <div>
381
+ <label style={labelStyle}>CIN</label>
382
+ <input
383
+ className="input"
384
+ style={inputStyle}
385
+ value={entity.cin}
386
+ onChange={(e) =>
387
+ updateEntity("cin", e.target.value.toUpperCase())
388
+ }
389
+ placeholder="U12345AB1234CDE567890"
390
+ maxLength={21}
391
+ />
392
+ <FieldError field="cin" />
393
+ </div>
394
+
395
+ <div>
396
+ <label style={labelStyle}>PAN</label>
397
+ <input
398
+ className="input"
399
+ style={inputStyle}
400
+ value={entity.pan}
401
+ onChange={(e) =>
402
+ updateEntity("pan", e.target.value.toUpperCase())
403
+ }
404
+ placeholder="ABCDE1234F"
405
+ maxLength={10}
406
+ />
407
+ <FieldError field="pan" />
408
+ </div>
409
+
410
+ <div>
411
+ <label style={labelStyle}>
412
+ GSTIN{" "}
413
+ <span style={{ color: "var(--text-muted)", fontWeight: 400 }}>
414
+ (optional)
415
+ </span>
416
+ </label>
417
+ <input
418
+ className="input"
419
+ style={inputStyle}
420
+ value={entity.gstin}
421
+ onChange={(e) =>
422
+ updateEntity("gstin", e.target.value.toUpperCase())
423
+ }
424
+ placeholder="22ABCDE1234F1Z5"
425
+ maxLength={15}
426
+ />
427
+ </div>
428
+
429
+ <div>
430
+ <label style={labelStyle}>
431
+ Sector <span style={{ color: "var(--danger)" }}>*</span>
432
+ </label>
433
+ <select
434
+ className="input"
435
+ style={inputStyle}
436
+ value={entity.sector}
437
+ onChange={(e) => updateEntity("sector", e.target.value)}
438
+ >
439
+ <option value="">Select sector</option>
440
+ {SECTORS.map((s) => (
441
+ <option key={s} value={s}>
442
+ {s}
443
+ </option>
444
+ ))}
445
+ </select>
446
+ <FieldError field="sector" />
447
+ </div>
448
+
449
+ <div>
450
+ <label style={labelStyle}>
451
+ Sub-sector{" "}
452
+ <span style={{ color: "var(--text-muted)", fontWeight: 400 }}>
453
+ (optional)
454
+ </span>
455
+ </label>
456
+ <input
457
+ className="input"
458
+ style={inputStyle}
459
+ value={entity.subSector}
460
+ onChange={(e) => updateEntity("subSector", e.target.value)}
461
+ placeholder="e.g. Synthetic fibres"
462
+ />
463
+ </div>
464
+
465
+ <div>
466
+ <label style={labelStyle}>
467
+ Constitution <span style={{ color: "var(--danger)" }}>*</span>
468
+ </label>
469
+ <select
470
+ className="input"
471
+ style={inputStyle}
472
+ value={entity.constitution}
473
+ onChange={(e) => updateEntity("constitution", e.target.value)}
474
+ >
475
+ <option value="">Select constitution</option>
476
+ {CONSTITUTIONS.map((c) => (
477
+ <option key={c} value={c}>
478
+ {c}
479
+ </option>
480
+ ))}
481
+ </select>
482
+ </div>
483
+
484
+ <div>
485
+ <label style={labelStyle}>Annual Turnover (₹ Cr)</label>
486
+ <input
487
+ className="input"
488
+ type="number"
489
+ style={inputStyle}
490
+ value={entity.annualTurnover}
491
+ onChange={(e) =>
492
+ updateEntity("annualTurnover", e.target.value)
493
+ }
494
+ placeholder="e.g. 150"
495
+ min="0"
496
+ step="0.01"
497
+ />
498
+ </div>
499
+
500
+ <div>
501
+ <label style={labelStyle}>Years in Operation</label>
502
+ <input
503
+ className="input"
504
+ type="number"
505
+ style={inputStyle}
506
+ value={entity.yearsInOperation}
507
+ onChange={(e) =>
508
+ updateEntity("yearsInOperation", e.target.value)
509
+ }
510
+ placeholder="e.g. 12"
511
+ min="0"
512
+ />
513
+ </div>
514
+ </div>
515
+
516
+ <button
517
+ onClick={handleNext}
518
+ className="btn btn-primary w-full"
519
+ style={{
520
+ marginTop: "32px",
521
+ padding: "14px",
522
+ borderRadius: "var(--radius-lg)",
523
+ fontSize: "15px",
524
+ }}
525
+ >
526
+ Next — Loan Details <ArrowRight size={16} />
527
+ </button>
528
+ </div>
529
+
530
+ {/* ═══ STEP 2: Loan Details ═══ */}
531
+ <div style={{ width: "50%", flexShrink: 0, paddingLeft: "40px" }}>
532
+ <div
533
+ style={{
534
+ display: "flex",
535
+ alignItems: "center",
536
+ gap: "12px",
537
+ marginBottom: "32px",
538
+ }}
539
+ >
540
+ <div
541
+ style={{
542
+ width: "40px",
543
+ height: "40px",
544
+ borderRadius: "12px",
545
+ background: "rgba(59,130,246,0.1)",
546
+ display: "flex",
547
+ alignItems: "center",
548
+ justifyContent: "center",
549
+ }}
550
+ >
551
+ <FileText size={20} style={{ color: "var(--accent)" }} />
552
+ </div>
553
+ <div>
554
+ <h2 style={{ fontSize: "20px", marginBottom: "2px" }}>
555
+ Loan Details
556
+ </h2>
557
+ <p style={{ fontSize: "12px", color: "var(--text-muted)" }}>
558
+ Proposed facility information
559
+ </p>
560
+ </div>
561
+ </div>
562
+
563
+ <div
564
+ style={{
565
+ display: "grid",
566
+ gridTemplateColumns: "1fr 1fr",
567
+ gap: "16px",
568
+ }}
569
+ >
570
+ <div>
571
+ <label style={labelStyle}>
572
+ Loan Type <span style={{ color: "var(--danger)" }}>*</span>
573
+ </label>
574
+ <select
575
+ className="input"
576
+ style={inputStyle}
577
+ value={loan.loanType}
578
+ onChange={(e) => updateLoan("loanType", e.target.value)}
579
+ >
580
+ <option value="">Select type</option>
581
+ {LOAN_TYPES.map((t) => (
582
+ <option key={t} value={t}>
583
+ {t}
584
+ </option>
585
+ ))}
586
+ </select>
587
+ <FieldError field="loanType" />
588
+ </div>
589
+
590
+ <div>
591
+ <label style={labelStyle}>
592
+ Loan Amount (₹ Cr){" "}
593
+ <span style={{ color: "var(--danger)" }}>*</span>
594
+ </label>
595
+ <input
596
+ className="input"
597
+ type="number"
598
+ style={inputStyle}
599
+ value={loan.loanAmount}
600
+ onChange={(e) => updateLoan("loanAmount", e.target.value)}
601
+ placeholder="e.g. 25"
602
+ min="0"
603
+ step="0.01"
604
+ />
605
+ <FieldError field="loanAmount" />
606
+ </div>
607
+
608
+ <div>
609
+ <label style={labelStyle}>Tenure (months)</label>
610
+ <input
611
+ className="input"
612
+ type="number"
613
+ style={inputStyle}
614
+ value={loan.tenure}
615
+ onChange={(e) => updateLoan("tenure", e.target.value)}
616
+ placeholder="e.g. 60"
617
+ min="1"
618
+ />
619
+ </div>
620
+
621
+ <div>
622
+ <label style={labelStyle}>Proposed Interest Rate (%)</label>
623
+ <input
624
+ className="input"
625
+ type="number"
626
+ style={inputStyle}
627
+ value={loan.interestRate}
628
+ onChange={(e) => updateLoan("interestRate", e.target.value)}
629
+ placeholder="e.g. 10.5"
630
+ min="0"
631
+ step="0.1"
632
+ />
633
+ </div>
634
+
635
+ <div>
636
+ <label style={labelStyle}>Collateral Type</label>
637
+ <select
638
+ className="input"
639
+ style={inputStyle}
640
+ value={loan.collateralType}
641
+ onChange={(e) => updateLoan("collateralType", e.target.value)}
642
+ >
643
+ <option value="">Select collateral</option>
644
+ {COLLATERAL_TYPES.map((c) => (
645
+ <option key={c} value={c}>
646
+ {c}
647
+ </option>
648
+ ))}
649
+ </select>
650
+ </div>
651
+
652
+ <div style={{ gridColumn: "1 / -1" }}>
653
+ <label style={labelStyle}>
654
+ Purpose of Loan{" "}
655
+ <span style={{ color: "var(--text-muted)", fontWeight: 400 }}>
656
+ (max 500 chars)
657
+ </span>
658
+ </label>
659
+ <textarea
660
+ className="input"
661
+ style={{
662
+ ...inputStyle,
663
+ minHeight: "80px",
664
+ resize: "vertical",
665
+ }}
666
+ value={loan.purpose}
667
+ onChange={(e) => updateLoan("purpose", e.target.value)}
668
+ placeholder="Brief description of how the funds will be used..."
669
+ maxLength={500}
670
+ />
671
+ <FieldError field="purpose" />
672
+ <span
673
+ style={{
674
+ fontSize: "11px",
675
+ color: "var(--text-muted)",
676
+ float: "right",
677
+ }}
678
+ >
679
+ {loan.purpose.length}/500
680
+ </span>
681
+ </div>
682
+ </div>
683
+
684
+ <div style={{ display: "flex", gap: "12px", marginTop: "32px" }}>
685
+ <button
686
+ onClick={() => {
687
+ setStep(1);
688
+ setErrors({});
689
+ }}
690
+ className="btn btn-secondary"
691
+ style={{
692
+ padding: "14px 24px",
693
+ borderRadius: "var(--radius-lg)",
694
+ fontSize: "15px",
695
+ }}
696
+ >
697
+ <ArrowLeft size={16} /> Back
698
+ </button>
699
+ <button
700
+ onClick={handleProceed}
701
+ className="btn btn-primary"
702
+ style={{
703
+ flex: 1,
704
+ padding: "14px",
705
+ borderRadius: "var(--radius-lg)",
706
+ fontSize: "15px",
707
+ }}
708
+ >
709
+ Proceed to Document Upload <ArrowRight size={16} />
710
+ </button>
711
+ </div>
712
+ </div>
713
+ </div>
714
+ </div>
715
+ </div>
716
+ );
717
+ }
frontend/src/pages/LandingPage.jsx CHANGED
@@ -1,6 +1,13 @@
1
  import { useState, useEffect, useRef } from "react";
2
  import { useNavigate } from "react-router-dom";
3
- import { ArrowRight, Shield, Brain, Network, Search, FileText } from "lucide-react";
 
 
 
 
 
 
 
4
 
5
  /* ─── Animated counter on scroll ─── */
6
  function Counter({ target, suffix = "" }) {
@@ -203,10 +210,7 @@ export default function LandingPage() {
203
  }}
204
  >
205
  Credit decisions,{" "}
206
- <em
207
- className="serif-italic"
208
- style={{ color: "var(--accent)" }}
209
- >
210
  reimagined
211
  </em>
212
  </h1>
@@ -222,15 +226,15 @@ export default function LandingPage() {
222
  }}
223
  >
224
  End-to-end AI credit appraisal for Indian banks. Forensic PDF
225
- analysis, LightGBM scoring, autonomous research, and
226
- officer-grade memos in one pipeline.
227
  </p>
228
 
229
  {/* CTAs */}
230
  <div className="flex gap-md" style={{ marginBottom: "48px" }}>
231
  <button
232
  className="btn btn-primary btn-lg"
233
- onClick={() => navigate("/upload")}
234
  >
235
  Get Started <ArrowRight size={18} />
236
  </button>
@@ -385,7 +389,12 @@ export default function LandingPage() {
385
  key={step.n}
386
  className="card"
387
  style={{
388
- borderRadius: i === 0 ? "var(--radius-lg) 0 0 var(--radius-lg)" : i === 2 ? "0 var(--radius-lg) var(--radius-lg) 0" : "0",
 
 
 
 
 
389
  borderLeft: i > 0 ? "none" : undefined,
390
  }}
391
  >
@@ -412,7 +421,13 @@ export default function LandingPage() {
412
  >
413
  {step.title}
414
  </h3>
415
- <p style={{ color: "var(--text-muted)", fontSize: "13px", lineHeight: 1.6 }}>
 
 
 
 
 
 
416
  {step.body}
417
  </p>
418
  </div>
@@ -459,10 +474,7 @@ export default function LandingPage() {
459
  {slide.tag}
460
  </span>
461
  </div>
462
- <h3
463
- className="serif"
464
- style={{ fontSize: "20px", lineHeight: 1.3 }}
465
- >
466
  {slide.title}
467
  </h3>
468
  <p
@@ -486,7 +498,8 @@ export default function LandingPage() {
486
  width: i === activeSlide ? "28px" : "8px",
487
  height: "8px",
488
  borderRadius: "var(--radius-full)",
489
- background: i === activeSlide ? slide.accent : "var(--bg-elevated)",
 
490
  border: "none",
491
  cursor: "pointer",
492
  transition: "all 0.3s ease",
@@ -509,14 +522,21 @@ export default function LandingPage() {
509
  padding: "16px 20px",
510
  borderRadius: "var(--radius-md)",
511
  border: `1px solid ${i === activeSlide ? "var(--border-hover)" : "transparent"}`,
512
- background: i === activeSlide ? "var(--bg-surface)" : "transparent",
 
513
  cursor: "pointer",
514
  transition: "all 0.2s ease",
515
  }}
516
  >
517
- <div className="flex items-center gap-sm" style={{ marginBottom: "4px" }}>
 
 
 
518
  <Icon size={14} style={{ color: s.accent, opacity: 0.7 }} />
519
- <span className="label" style={{ color: "var(--text-muted)" }}>
 
 
 
520
  {s.tag.split("—")[0].trim()}
521
  </span>
522
  </div>
@@ -524,7 +544,10 @@ export default function LandingPage() {
524
  style={{
525
  fontSize: "14px",
526
  fontWeight: 500,
527
- color: i === activeSlide ? "var(--text-primary)" : "var(--text-muted)",
 
 
 
528
  transition: "color 0.2s",
529
  }}
530
  >
@@ -608,7 +631,13 @@ export default function LandingPage() {
608
  >
609
  {f.title}
610
  </h3>
611
- <p style={{ color: "var(--text-muted)", fontSize: "13px", lineHeight: 1.6 }}>
 
 
 
 
 
 
612
  {f.body}
613
  </p>
614
  </div>
@@ -631,7 +660,10 @@ export default function LandingPage() {
631
  overflow: "hidden",
632
  }}
633
  >
634
- <div className="eyebrow" style={{ marginBottom: "20px", justifyContent: "center" }}>
 
 
 
635
  Get started today
636
  </div>
637
  <h2
@@ -656,7 +688,7 @@ export default function LandingPage() {
656
  </p>
657
  <button
658
  className="btn btn-primary btn-lg"
659
- onClick={() => navigate("/upload")}
660
  >
661
  Upload Documents <ArrowRight size={18} />
662
  </button>
 
1
  import { useState, useEffect, useRef } from "react";
2
  import { useNavigate } from "react-router-dom";
3
+ import {
4
+ ArrowRight,
5
+ Shield,
6
+ Brain,
7
+ Network,
8
+ Search,
9
+ FileText,
10
+ } from "lucide-react";
11
 
12
  /* ─── Animated counter on scroll ─── */
13
  function Counter({ target, suffix = "" }) {
 
210
  }}
211
  >
212
  Credit decisions,{" "}
213
+ <em className="serif-italic" style={{ color: "var(--accent)" }}>
 
 
 
214
  reimagined
215
  </em>
216
  </h1>
 
226
  }}
227
  >
228
  End-to-end AI credit appraisal for Indian banks. Forensic PDF
229
+ analysis, LightGBM scoring, autonomous research, and officer-grade
230
+ memos in one pipeline.
231
  </p>
232
 
233
  {/* CTAs */}
234
  <div className="flex gap-md" style={{ marginBottom: "48px" }}>
235
  <button
236
  className="btn btn-primary btn-lg"
237
+ onClick={() => navigate("/onboard")}
238
  >
239
  Get Started <ArrowRight size={18} />
240
  </button>
 
389
  key={step.n}
390
  className="card"
391
  style={{
392
+ borderRadius:
393
+ i === 0
394
+ ? "var(--radius-lg) 0 0 var(--radius-lg)"
395
+ : i === 2
396
+ ? "0 var(--radius-lg) var(--radius-lg) 0"
397
+ : "0",
398
  borderLeft: i > 0 ? "none" : undefined,
399
  }}
400
  >
 
421
  >
422
  {step.title}
423
  </h3>
424
+ <p
425
+ style={{
426
+ color: "var(--text-muted)",
427
+ fontSize: "13px",
428
+ lineHeight: 1.6,
429
+ }}
430
+ >
431
  {step.body}
432
  </p>
433
  </div>
 
474
  {slide.tag}
475
  </span>
476
  </div>
477
+ <h3 className="serif" style={{ fontSize: "20px", lineHeight: 1.3 }}>
 
 
 
478
  {slide.title}
479
  </h3>
480
  <p
 
498
  width: i === activeSlide ? "28px" : "8px",
499
  height: "8px",
500
  borderRadius: "var(--radius-full)",
501
+ background:
502
+ i === activeSlide ? slide.accent : "var(--bg-elevated)",
503
  border: "none",
504
  cursor: "pointer",
505
  transition: "all 0.3s ease",
 
522
  padding: "16px 20px",
523
  borderRadius: "var(--radius-md)",
524
  border: `1px solid ${i === activeSlide ? "var(--border-hover)" : "transparent"}`,
525
+ background:
526
+ i === activeSlide ? "var(--bg-surface)" : "transparent",
527
  cursor: "pointer",
528
  transition: "all 0.2s ease",
529
  }}
530
  >
531
+ <div
532
+ className="flex items-center gap-sm"
533
+ style={{ marginBottom: "4px" }}
534
+ >
535
  <Icon size={14} style={{ color: s.accent, opacity: 0.7 }} />
536
+ <span
537
+ className="label"
538
+ style={{ color: "var(--text-muted)" }}
539
+ >
540
  {s.tag.split("—")[0].trim()}
541
  </span>
542
  </div>
 
544
  style={{
545
  fontSize: "14px",
546
  fontWeight: 500,
547
+ color:
548
+ i === activeSlide
549
+ ? "var(--text-primary)"
550
+ : "var(--text-muted)",
551
  transition: "color 0.2s",
552
  }}
553
  >
 
631
  >
632
  {f.title}
633
  </h3>
634
+ <p
635
+ style={{
636
+ color: "var(--text-muted)",
637
+ fontSize: "13px",
638
+ lineHeight: 1.6,
639
+ }}
640
+ >
641
  {f.body}
642
  </p>
643
  </div>
 
660
  overflow: "hidden",
661
  }}
662
  >
663
+ <div
664
+ className="eyebrow"
665
+ style={{ marginBottom: "20px", justifyContent: "center" }}
666
+ >
667
  Get started today
668
  </div>
669
  <h2
 
688
  </p>
689
  <button
690
  className="btn btn-primary btn-lg"
691
+ onClick={() => navigate("/onboard")}
692
  >
693
  Upload Documents <ArrowRight size={18} />
694
  </button>
frontend/src/pages/UploadPage.jsx CHANGED
@@ -1,22 +1,69 @@
1
  import { useState } from "react";
2
- import { useNavigate } from "react-router-dom";
3
- import { ArrowRight, Loader } from "lucide-react";
4
  import FileDropZone from "../components/FileDropZone.jsx";
5
  import { createJob, uploadFiles } from "../api/client.js";
6
  import { useAuth } from "../context/AuthContext.jsx";
7
 
8
  const FILE_ZONES = [
9
- { fileType: "annual_report", label: "Annual Report", description: "Digital or scanned PDF", acceptedFormats: ".pdf", required: true },
10
- { fileType: "gst_3b", label: "GSTR-3B", description: "Monthly summary return", acceptedFormats: ".csv,.xlsx", required: true },
11
- { fileType: "gst_2a", label: "GSTR-2A", description: "Auto-populated inward supplies", acceptedFormats: ".csv,.xlsx", required: false, fraudCritical: true },
12
- { fileType: "bank_statement", label: "Bank Statement", description: "12–24 months CSV/XLSX", acceptedFormats: ".csv,.xlsx", required: true },
13
- { fileType: "gst_1", label: "GSTR-1", description: "Outward supply return", acceptedFormats: ".csv,.xlsx", required: false },
14
- { fileType: "itr", label: "ITR Filing", description: "Income Tax Return PDF", acceptedFormats: ".pdf", required: false },
15
- { fileType: "mca", label: "MCA Filing", description: "Director & shareholding data", acceptedFormats: ".pdf", required: false },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
  ];
17
 
18
  const REQUIRED_KEYS = ["annual_report", "gst_3b", "bank_statement"];
19
- const LABELS = { annual_report: "Annual Report", gst_3b: "GSTR-3B", bank_statement: "Bank Statement" };
 
 
 
 
20
 
21
  function computeFraudCoverage(files) {
22
  if (!files.gst_3b) return 20;
@@ -27,8 +74,10 @@ function computeFraudCoverage(files) {
27
 
28
  export default function UploadPage() {
29
  const navigate = useNavigate();
 
30
  const { user } = useAuth();
31
- const [companyName, setCompanyName] = useState("");
 
32
  const [files, setFiles] = useState({});
33
  const [isLoading, setIsLoading] = useState(false);
34
  const [uploadError, setUploadError] = useState(null);
@@ -49,9 +98,15 @@ export default function UploadPage() {
49
  }
50
 
51
  async function handleSubmit() {
52
- if (!companyName.trim()) { setUploadError("Please enter the company name."); return; }
 
 
 
53
  for (const key of REQUIRED_KEYS) {
54
- if (!files[key]) { setUploadError(`${LABELS[key]} is required.`); return; }
 
 
 
55
  }
56
 
57
  setIsLoading(true);
@@ -74,244 +129,482 @@ export default function UploadPage() {
74
  const canSubmit = companyName.trim() && requiredDone === 3 && !isLoading;
75
 
76
  return (
77
- <div className="page-enter" style={{ minHeight: "100vh", display: "flex" }}>
78
- {/* ── Left panel ── */}
 
 
 
79
  <div
80
- className="hide-mobile"
81
  style={{
82
- width: "380px",
83
- flexShrink: 0,
84
  display: "flex",
85
- flexDirection: "column",
86
- justifyContent: "flex-start",
87
- gap: "64px",
88
- padding: "32px 48px",
89
- borderRight: "1px solid var(--border)",
90
- position: "relative",
91
- overflow: "hidden",
92
- background: "radial-gradient(ellipse 100% 60% at 30% 20%, rgba(59,130,246,0.04) 0%, transparent 60%), var(--bg-secondary)",
93
  }}
94
  >
95
- <div>
96
- <div className="eyebrow" style={{ marginBottom: "24px" }}>
97
- New Analysis
98
- </div>
99
- <h1 style={{ fontSize: "clamp(1.6rem, 3vw, 2.2rem)", marginBottom: "16px" }}>
100
- Feed the pipeline.
101
- <br />
102
- <em className="serif-italic" style={{ color: "var(--accent)" }}>
103
- Get the full picture.
104
- </em>
105
- </h1>
106
- <p style={{ color: "var(--text-muted)", fontSize: "13px", lineHeight: 1.7, maxWidth: "280px" }}>
107
- Three documents are enough to trigger all 12 pipeline stages — fraud forensics, ML scoring, research agent, entity graph, and CAM generation.
108
- </p>
109
- </div>
110
-
111
- {/* Progress indicator */}
112
- <div>
113
- <div className="flex justify-between items-center" style={{ marginBottom: "10px" }}>
114
- <span className="label">Documents uploaded</span>
115
- <span style={{ fontSize: "12px", fontWeight: 600, color: "var(--accent)" }}>
116
- {uploadedCount} / {FILE_ZONES.length}
117
- </span>
118
- </div>
119
- <div className="progress-track">
120
- <div
121
- className="progress-fill"
122
- style={{ width: `${(uploadedCount / FILE_ZONES.length) * 100}%` }}
123
- />
124
- </div>
125
 
126
- <div className="flex flex-col gap-xs" style={{ marginTop: "16px" }}>
127
- {FILE_ZONES.map((z) => {
128
- const done = !!files[z.fileType];
129
- return (
130
- <div key={z.fileType} className="flex items-center gap-sm">
 
 
 
 
 
 
 
 
 
 
 
 
131
  <div
132
  style={{
133
- width: "20px",
134
- height: "20px",
135
- borderRadius: "var(--radius-full)",
136
  display: "flex",
137
  alignItems: "center",
138
  justifyContent: "center",
139
- fontSize: "11px",
140
- fontWeight: 600,
141
- flexShrink: 0,
142
- transition: "all 0.3s ease",
143
- background: done ? "var(--success-subtle)" : "rgba(255,255,255,0.04)",
144
- border: `1px solid ${done ? "var(--success)" : "var(--border)"}`,
145
- color: done ? "var(--success)" : "var(--text-muted)",
 
 
 
 
 
 
 
 
 
 
 
146
  }}
147
  >
148
- {done ? "" : "·"}
 
 
 
 
149
  </div>
150
  <span
151
  style={{
152
- fontSize: "12px",
153
- transition: "color 0.2s",
154
- color: done ? "var(--text-primary)" : "var(--text-muted)",
 
 
 
 
 
155
  }}
156
  >
157
- {z.label}
158
- {z.required && (
159
- <span style={{ color: done ? "var(--text-muted)" : "var(--danger)", marginLeft: "3px" }}>*</span>
160
- )}
161
  </span>
162
  </div>
163
- );
164
- })}
165
- </div>
166
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
167
  </div>
168
 
169
- {/* ── Right panel / main form ── */}
170
- <div
171
- style={{
172
- flex: 1,
173
- display: "flex",
174
- flexDirection: "column",
175
- justifyContent: "flex-start",
176
- padding: "48px 40px",
177
- maxWidth: "720px",
178
- overflowY: "auto",
179
- }}
180
- >
181
- {/* Mobile headline */}
182
- <div className="show-mobile-only" style={{ marginBottom: "32px" }}>
183
- <div className="eyebrow" style={{ marginBottom: "12px" }}>New Analysis</div>
184
- <h2>Upload documents to begin</h2>
185
- </div>
186
-
187
- {/* Company name */}
188
- <div style={{ marginBottom: "32px" }}>
189
- <label className="label" style={{ display: "block", marginBottom: "10px" }}>
190
- Company Name
191
- </label>
192
- <input
193
- type="text"
194
- value={companyName}
195
- onChange={(e) => { setCompanyName(e.target.value); if (uploadError) setUploadError(null); }}
196
- placeholder="e.g. Mehta Textiles Pvt Ltd"
197
- className="input"
198
- style={{ borderRadius: "var(--radius-lg)", padding: "14px 20px" }}
199
- />
200
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
201
 
202
- {/* Section Required */}
203
- <div className="flex items-center gap-md" style={{ marginBottom: "16px" }}>
204
- <span className="label">Required Documents</span>
205
- <div style={{ flex: 1, height: "1px", background: "var(--border)" }} />
206
- <span className="badge badge-danger" style={{ fontSize: "10px" }}>{requiredDone} / 3</span>
207
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
208
 
209
- <div className="grid grid-3" style={{ gap: "12px", marginBottom: "20px" }}>
210
- {FILE_ZONES.filter((z) => z.required).map((zone) => (
211
- <FileDropZone
212
- key={zone.fileType}
213
- {...zone}
214
- file={files[zone.fileType] || null}
215
- onFileSelect={(f) => handleFileSelect(zone.fileType, f)}
216
- />
217
- ))}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
218
  </div>
219
 
220
- {/* Fraud Coverage Indicator */}
221
  <div
222
- className="card flex items-center gap-md"
223
  style={{
224
- padding: "12px 16px",
225
- marginBottom: "32px",
226
- borderColor: fraudCoverage === 100 ? "rgba(34,197,94,0.3)" : fraudCoverage >= 85 ? "rgba(59,130,246,0.3)" : "rgba(234,179,8,0.3)",
227
- background: fraudCoverage === 100 ? "var(--success-subtle)" : fraudCoverage >= 85 ? "rgba(59,130,246,0.05)" : "var(--warning-subtle)",
 
 
 
228
  }}
229
  >
230
- <div style={{ flex: 1 }}>
231
- <div className="flex items-center gap-sm" style={{ marginBottom: "6px" }}>
232
- <span className="label">Fraud Check Coverage</span>
233
- <span style={{ fontSize: "12px", fontWeight: 700, color: fraudCoverage === 100 ? "var(--success)" : fraudCoverage >= 85 ? "var(--accent)" : "var(--warning)" }}>
234
- {fraudCoverage}%
235
- </span>
236
  </div>
237
- <div className="progress-track" style={{ height: "4px" }}>
238
- <div className="progress-fill" style={{ width: `${fraudCoverage}%`, background: fraudCoverage === 100 ? "var(--success)" : fraudCoverage >= 85 ? "var(--accent)" : "var(--warning)" }} />
239
- </div>
240
- {fraudCoverage < 85 && (
241
- <p style={{ fontSize: "11px", color: "var(--warning)", marginTop: "6px" }}>
242
- {!files.gst_2a ? "Upload GSTR-2A to enable ITC mismatch fraud check (+35% coverage)" : "Upload GSTR-1 to enable HSN anomaly check (+15% coverage)"}
243
- </p>
244
- )}
245
  </div>
246
- </div>
247
 
248
- {/* Section Optional */}
249
- <div className="flex items-center gap-md" style={{ marginBottom: "16px" }}>
250
- <span className="label">Optional Documents</span>
251
- <div style={{ flex: 1, height: "1px", background: "var(--border)" }} />
252
- <span className="label">Improves accuracy</span>
253
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
254
 
255
- <div className="grid grid-2" style={{ gap: "12px", marginBottom: "32px" }}>
256
- {FILE_ZONES.filter((z) => !z.required).map((zone) => (
257
- <FileDropZone
258
- key={zone.fileType}
259
- {...zone}
260
- file={files[zone.fileType] || null}
261
- onFileSelect={(f) => handleFileSelect(zone.fileType, f)}
 
262
  />
263
- ))}
264
- </div>
 
 
265
 
266
- {/* Error */}
267
- {uploadError && (
268
  <div
269
- className="card flex items-center gap-sm"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
270
  style={{
271
- background: "var(--danger-subtle)",
272
- borderColor: "rgba(239,68,68,0.25)",
273
  padding: "12px 16px",
274
- marginBottom: "16px",
275
- borderRadius: "var(--radius-md)",
276
- color: "var(--danger)",
277
- fontSize: "13px",
 
 
 
 
 
 
 
 
 
278
  }}
279
  >
280
- {uploadError}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
281
  </div>
282
- )}
283
 
284
- {/* Submit */}
285
- <button
286
- onClick={handleSubmit}
287
- disabled={!canSubmit}
288
- className={`btn w-full ${canSubmit ? "btn-primary" : ""}`}
289
- style={{
290
- padding: "16px",
291
- borderRadius: "var(--radius-lg)",
292
- fontSize: "15px",
293
- ...(!canSubmit ? {
294
- background: "var(--bg-elevated)",
295
- border: "1px solid var(--border)",
296
- color: "var(--text-muted)",
297
- cursor: "not-allowed",
298
- boxShadow: "none",
299
- } : {}),
300
- }}
301
- >
302
- {isLoading ? (
303
- <span className="flex items-center justify-center gap-sm">
304
- <Loader size={16} className="animate-spin" />
305
- Starting pipeline…
306
- </span>
307
- ) : (
308
- <>Run Credit Analysis <ArrowRight size={16} /></>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
309
  )}
310
- </button>
311
 
312
- <p style={{ color: "var(--text-muted)", fontSize: "12px", textAlign: "center", marginTop: "12px" }}>
313
- Analysis typically takes 2–4 minutes across 12 pipeline stages.
314
- </p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
315
  </div>
316
  </div>
317
  );
 
1
  import { useState } from "react";
2
+ import { useNavigate, useLocation } from "react-router-dom";
3
+ import { ArrowRight, Loader, CheckCircle } from "lucide-react";
4
  import FileDropZone from "../components/FileDropZone.jsx";
5
  import { createJob, uploadFiles } from "../api/client.js";
6
  import { useAuth } from "../context/AuthContext.jsx";
7
 
8
  const FILE_ZONES = [
9
+ {
10
+ fileType: "annual_report",
11
+ label: "Annual Report",
12
+ description: "Digital or scanned PDF",
13
+ acceptedFormats: ".pdf",
14
+ required: true,
15
+ },
16
+ {
17
+ fileType: "gst_3b",
18
+ label: "GSTR-3B",
19
+ description: "Monthly summary return",
20
+ acceptedFormats: ".csv,.xlsx",
21
+ required: true,
22
+ },
23
+ {
24
+ fileType: "gst_2a",
25
+ label: "GSTR-2A",
26
+ description: "Auto-populated inward supplies",
27
+ acceptedFormats: ".csv,.xlsx",
28
+ required: false,
29
+ fraudCritical: true,
30
+ },
31
+ {
32
+ fileType: "bank_statement",
33
+ label: "Bank Statement",
34
+ description: "12–24 months CSV/XLSX",
35
+ acceptedFormats: ".csv,.xlsx",
36
+ required: true,
37
+ },
38
+ {
39
+ fileType: "gst_1",
40
+ label: "GSTR-1",
41
+ description: "Outward supply return",
42
+ acceptedFormats: ".csv,.xlsx",
43
+ required: false,
44
+ },
45
+ {
46
+ fileType: "itr",
47
+ label: "ITR Filing",
48
+ description: "Income Tax Return PDF",
49
+ acceptedFormats: ".pdf",
50
+ required: false,
51
+ },
52
+ {
53
+ fileType: "mca",
54
+ label: "MCA Filing",
55
+ description: "Director & shareholding data",
56
+ acceptedFormats: ".pdf",
57
+ required: false,
58
+ },
59
  ];
60
 
61
  const REQUIRED_KEYS = ["annual_report", "gst_3b", "bank_statement"];
62
+ const LABELS = {
63
+ annual_report: "Annual Report",
64
+ gst_3b: "GSTR-3B",
65
+ bank_statement: "Bank Statement",
66
+ };
67
 
68
  function computeFraudCoverage(files) {
69
  if (!files.gst_3b) return 20;
 
74
 
75
  export default function UploadPage() {
76
  const navigate = useNavigate();
77
+ const location = useLocation();
78
  const { user } = useAuth();
79
+ const passedName = location.state?.companyName || "";
80
+ const [companyName, setCompanyName] = useState(passedName);
81
  const [files, setFiles] = useState({});
82
  const [isLoading, setIsLoading] = useState(false);
83
  const [uploadError, setUploadError] = useState(null);
 
98
  }
99
 
100
  async function handleSubmit() {
101
+ if (!companyName.trim()) {
102
+ setUploadError("Please enter the company name.");
103
+ return;
104
+ }
105
  for (const key of REQUIRED_KEYS) {
106
+ if (!files[key]) {
107
+ setUploadError(`${LABELS[key]} is required.`);
108
+ return;
109
+ }
110
  }
111
 
112
  setIsLoading(true);
 
129
  const canSubmit = companyName.trim() && requiredDone === 3 && !isLoading;
130
 
131
  return (
132
+ <div
133
+ className="page-enter"
134
+ style={{ minHeight: "100vh", display: "flex", flexDirection: "column" }}
135
+ >
136
+ {/* ── Journey Progress Indicator ── */}
137
  <div
 
138
  style={{
 
 
139
  display: "flex",
140
+ alignItems: "center",
141
+ gap: "0",
142
+ padding: "24px 40px 0",
143
+ maxWidth: "700px",
144
+ margin: "0 auto",
145
+ width: "100%",
 
 
146
  }}
147
  >
148
+ {["Entity Details", "Documents", "Analysis", "Report"].map(
149
+ (label, i) => {
150
+ const stepNum = i + 1;
151
+ let status = "pending";
152
+ if (stepNum === 1) status = "completed";
153
+ if (stepNum === 2) status = "active";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
154
 
155
+ return (
156
+ <div
157
+ key={label}
158
+ style={{
159
+ display: "flex",
160
+ alignItems: "center",
161
+ flex: i < 3 ? 1 : "none",
162
+ }}
163
+ >
164
+ <div
165
+ style={{
166
+ display: "flex",
167
+ flexDirection: "column",
168
+ alignItems: "center",
169
+ minWidth: "fit-content",
170
+ }}
171
+ >
172
  <div
173
  style={{
174
+ width: "32px",
175
+ height: "32px",
176
+ borderRadius: "50%",
177
  display: "flex",
178
  alignItems: "center",
179
  justifyContent: "center",
180
+ fontSize: "12px",
181
+ fontWeight: 700,
182
+ transition: "all 0.4s cubic-bezier(0.4, 0, 0.2, 1)",
183
+ background:
184
+ status === "active"
185
+ ? "var(--accent)"
186
+ : status === "completed"
187
+ ? "var(--success)"
188
+ : "var(--bg-elevated)",
189
+ border: `2px solid ${status === "active" ? "var(--accent)" : status === "completed" ? "var(--success)" : "var(--border)"}`,
190
+ color:
191
+ status === "active" || status === "completed"
192
+ ? "#fff"
193
+ : "var(--text-muted)",
194
+ boxShadow:
195
+ status === "active"
196
+ ? "0 0 20px rgba(59,130,246,0.3)"
197
+ : "none",
198
  }}
199
  >
200
+ {status === "completed" ? (
201
+ <CheckCircle size={14} />
202
+ ) : (
203
+ stepNum
204
+ )}
205
  </div>
206
  <span
207
  style={{
208
+ fontSize: "10px",
209
+ fontWeight: status === "active" ? 600 : 400,
210
+ color:
211
+ status === "active"
212
+ ? "var(--text-primary)"
213
+ : "var(--text-muted)",
214
+ marginTop: "4px",
215
+ whiteSpace: "nowrap",
216
  }}
217
  >
218
+ {label}
 
 
 
219
  </span>
220
  </div>
221
+ {i < 3 && (
222
+ <div
223
+ style={{
224
+ flex: 1,
225
+ height: "2px",
226
+ margin: "0 8px",
227
+ marginBottom: "18px",
228
+ background: "var(--border)",
229
+ position: "relative",
230
+ overflow: "hidden",
231
+ }}
232
+ >
233
+ <div
234
+ style={{
235
+ position: "absolute",
236
+ top: 0,
237
+ left: 0,
238
+ bottom: 0,
239
+ width: status === "completed" ? "100%" : "0%",
240
+ background: "var(--accent)",
241
+ transition: "width 0.6s cubic-bezier(0.4, 0, 0.2, 1)",
242
+ }}
243
+ />
244
+ </div>
245
+ )}
246
+ </div>
247
+ );
248
+ },
249
+ )}
250
  </div>
251
 
252
+ <div style={{ flex: 1, display: "flex" }}>
253
+ {/* ── Left panel ── */}
254
+ <div
255
+ className="hide-mobile"
256
+ style={{
257
+ width: "380px",
258
+ flexShrink: 0,
259
+ display: "flex",
260
+ flexDirection: "column",
261
+ justifyContent: "flex-start",
262
+ gap: "64px",
263
+ padding: "32px 48px",
264
+ borderRight: "1px solid var(--border)",
265
+ position: "relative",
266
+ overflow: "hidden",
267
+ background:
268
+ "radial-gradient(ellipse 100% 60% at 30% 20%, rgba(59,130,246,0.04) 0%, transparent 60%), var(--bg-secondary)",
269
+ }}
270
+ >
271
+ <div>
272
+ <div className="eyebrow" style={{ marginBottom: "24px" }}>
273
+ New Analysis
274
+ </div>
275
+ <h1
276
+ style={{
277
+ fontSize: "clamp(1.6rem, 3vw, 2.2rem)",
278
+ marginBottom: "16px",
279
+ }}
280
+ >
281
+ Feed the pipeline.
282
+ <br />
283
+ <em className="serif-italic" style={{ color: "var(--accent)" }}>
284
+ Get the full picture.
285
+ </em>
286
+ </h1>
287
+ <p
288
+ style={{
289
+ color: "var(--text-muted)",
290
+ fontSize: "13px",
291
+ lineHeight: 1.7,
292
+ maxWidth: "280px",
293
+ }}
294
+ >
295
+ Three documents are enough to trigger all 12 pipeline stages —
296
+ fraud forensics, ML scoring, research agent, entity graph, and CAM
297
+ generation.
298
+ </p>
299
+ </div>
300
 
301
+ {/* Progress indicator */}
302
+ <div>
303
+ <div
304
+ className="flex justify-between items-center"
305
+ style={{ marginBottom: "10px" }}
306
+ >
307
+ <span className="label">Documents uploaded</span>
308
+ <span
309
+ style={{
310
+ fontSize: "12px",
311
+ fontWeight: 600,
312
+ color: "var(--accent)",
313
+ }}
314
+ >
315
+ {uploadedCount} / {FILE_ZONES.length}
316
+ </span>
317
+ </div>
318
+ <div className="progress-track">
319
+ <div
320
+ className="progress-fill"
321
+ style={{
322
+ width: `${(uploadedCount / FILE_ZONES.length) * 100}%`,
323
+ }}
324
+ />
325
+ </div>
326
 
327
+ <div className="flex flex-col gap-xs" style={{ marginTop: "16px" }}>
328
+ {FILE_ZONES.map((z) => {
329
+ const done = !!files[z.fileType];
330
+ return (
331
+ <div key={z.fileType} className="flex items-center gap-sm">
332
+ <div
333
+ style={{
334
+ width: "20px",
335
+ height: "20px",
336
+ borderRadius: "var(--radius-full)",
337
+ display: "flex",
338
+ alignItems: "center",
339
+ justifyContent: "center",
340
+ fontSize: "11px",
341
+ fontWeight: 600,
342
+ flexShrink: 0,
343
+ transition: "all 0.3s ease",
344
+ background: done
345
+ ? "var(--success-subtle)"
346
+ : "rgba(255,255,255,0.04)",
347
+ border: `1px solid ${done ? "var(--success)" : "var(--border)"}`,
348
+ color: done ? "var(--success)" : "var(--text-muted)",
349
+ }}
350
+ >
351
+ {done ? "✓" : "·"}
352
+ </div>
353
+ <span
354
+ style={{
355
+ fontSize: "12px",
356
+ transition: "color 0.2s",
357
+ color: done
358
+ ? "var(--text-primary)"
359
+ : "var(--text-muted)",
360
+ }}
361
+ >
362
+ {z.label}
363
+ {z.required && (
364
+ <span
365
+ style={{
366
+ color: done ? "var(--text-muted)" : "var(--danger)",
367
+ marginLeft: "3px",
368
+ }}
369
+ >
370
+ *
371
+ </span>
372
+ )}
373
+ </span>
374
+ </div>
375
+ );
376
+ })}
377
+ </div>
378
+ </div>
379
  </div>
380
 
381
+ {/* ── Right panel / main form ── */}
382
  <div
 
383
  style={{
384
+ flex: 1,
385
+ display: "flex",
386
+ flexDirection: "column",
387
+ justifyContent: "flex-start",
388
+ padding: "48px 40px",
389
+ maxWidth: "720px",
390
+ overflowY: "auto",
391
  }}
392
  >
393
+ {/* Mobile headline */}
394
+ <div className="show-mobile-only" style={{ marginBottom: "32px" }}>
395
+ <div className="eyebrow" style={{ marginBottom: "12px" }}>
396
+ New Analysis
 
 
397
  </div>
398
+ <h2>Upload documents to begin</h2>
 
 
 
 
 
 
 
399
  </div>
 
400
 
401
+ {/* Company name */}
402
+ <div style={{ marginBottom: "32px" }}>
403
+ <label
404
+ className="label"
405
+ style={{ display: "block", marginBottom: "10px" }}
406
+ >
407
+ Company Name
408
+ </label>
409
+ <input
410
+ type="text"
411
+ value={companyName}
412
+ onChange={(e) => {
413
+ setCompanyName(e.target.value);
414
+ if (uploadError) setUploadError(null);
415
+ }}
416
+ placeholder="e.g. Mehta Textiles Pvt Ltd"
417
+ className="input"
418
+ style={{ borderRadius: "var(--radius-lg)", padding: "14px 20px" }}
419
+ />
420
+ </div>
421
 
422
+ {/* Section Required */}
423
+ <div
424
+ className="flex items-center gap-md"
425
+ style={{ marginBottom: "16px" }}
426
+ >
427
+ <span className="label">Required Documents</span>
428
+ <div
429
+ style={{ flex: 1, height: "1px", background: "var(--border)" }}
430
  />
431
+ <span className="badge badge-danger" style={{ fontSize: "10px" }}>
432
+ {requiredDone} / 3
433
+ </span>
434
+ </div>
435
 
 
 
436
  <div
437
+ className="grid grid-3"
438
+ style={{ gap: "12px", marginBottom: "20px" }}
439
+ >
440
+ {FILE_ZONES.filter((z) => z.required).map((zone) => (
441
+ <FileDropZone
442
+ key={zone.fileType}
443
+ {...zone}
444
+ file={files[zone.fileType] || null}
445
+ onFileSelect={(f) => handleFileSelect(zone.fileType, f)}
446
+ />
447
+ ))}
448
+ </div>
449
+
450
+ {/* Fraud Coverage Indicator */}
451
+ <div
452
+ className="card flex items-center gap-md"
453
  style={{
 
 
454
  padding: "12px 16px",
455
+ marginBottom: "32px",
456
+ borderColor:
457
+ fraudCoverage === 100
458
+ ? "rgba(34,197,94,0.3)"
459
+ : fraudCoverage >= 85
460
+ ? "rgba(59,130,246,0.3)"
461
+ : "rgba(234,179,8,0.3)",
462
+ background:
463
+ fraudCoverage === 100
464
+ ? "var(--success-subtle)"
465
+ : fraudCoverage >= 85
466
+ ? "rgba(59,130,246,0.05)"
467
+ : "var(--warning-subtle)",
468
  }}
469
  >
470
+ <div style={{ flex: 1 }}>
471
+ <div
472
+ className="flex items-center gap-sm"
473
+ style={{ marginBottom: "6px" }}
474
+ >
475
+ <span className="label">Fraud Check Coverage</span>
476
+ <span
477
+ style={{
478
+ fontSize: "12px",
479
+ fontWeight: 700,
480
+ color:
481
+ fraudCoverage === 100
482
+ ? "var(--success)"
483
+ : fraudCoverage >= 85
484
+ ? "var(--accent)"
485
+ : "var(--warning)",
486
+ }}
487
+ >
488
+ {fraudCoverage}%
489
+ </span>
490
+ </div>
491
+ <div className="progress-track" style={{ height: "4px" }}>
492
+ <div
493
+ className="progress-fill"
494
+ style={{
495
+ width: `${fraudCoverage}%`,
496
+ background:
497
+ fraudCoverage === 100
498
+ ? "var(--success)"
499
+ : fraudCoverage >= 85
500
+ ? "var(--accent)"
501
+ : "var(--warning)",
502
+ }}
503
+ />
504
+ </div>
505
+ {fraudCoverage < 85 && (
506
+ <p
507
+ style={{
508
+ fontSize: "11px",
509
+ color: "var(--warning)",
510
+ marginTop: "6px",
511
+ }}
512
+ >
513
+ {!files.gst_2a
514
+ ? "Upload GSTR-2A to enable ITC mismatch fraud check (+35% coverage)"
515
+ : "Upload GSTR-1 to enable HSN anomaly check (+15% coverage)"}
516
+ </p>
517
+ )}
518
+ </div>
519
  </div>
 
520
 
521
+ {/* Section — Optional */}
522
+ <div
523
+ className="flex items-center gap-md"
524
+ style={{ marginBottom: "16px" }}
525
+ >
526
+ <span className="label">Optional Documents</span>
527
+ <div
528
+ style={{ flex: 1, height: "1px", background: "var(--border)" }}
529
+ />
530
+ <span className="label">Improves accuracy</span>
531
+ </div>
532
+
533
+ <div
534
+ className="grid grid-2"
535
+ style={{ gap: "12px", marginBottom: "32px" }}
536
+ >
537
+ {FILE_ZONES.filter((z) => !z.required).map((zone) => (
538
+ <FileDropZone
539
+ key={zone.fileType}
540
+ {...zone}
541
+ file={files[zone.fileType] || null}
542
+ onFileSelect={(f) => handleFileSelect(zone.fileType, f)}
543
+ />
544
+ ))}
545
+ </div>
546
+
547
+ {/* Error */}
548
+ {uploadError && (
549
+ <div
550
+ className="card flex items-center gap-sm"
551
+ style={{
552
+ background: "var(--danger-subtle)",
553
+ borderColor: "rgba(239,68,68,0.25)",
554
+ padding: "12px 16px",
555
+ marginBottom: "16px",
556
+ borderRadius: "var(--radius-md)",
557
+ color: "var(--danger)",
558
+ fontSize: "13px",
559
+ }}
560
+ >
561
+ {uploadError}
562
+ </div>
563
  )}
 
564
 
565
+ {/* Submit */}
566
+ <button
567
+ onClick={handleSubmit}
568
+ disabled={!canSubmit}
569
+ className={`btn w-full ${canSubmit ? "btn-primary" : ""}`}
570
+ style={{
571
+ padding: "16px",
572
+ borderRadius: "var(--radius-lg)",
573
+ fontSize: "15px",
574
+ ...(!canSubmit
575
+ ? {
576
+ background: "var(--bg-elevated)",
577
+ border: "1px solid var(--border)",
578
+ color: "var(--text-muted)",
579
+ cursor: "not-allowed",
580
+ boxShadow: "none",
581
+ }
582
+ : {}),
583
+ }}
584
+ >
585
+ {isLoading ? (
586
+ <span className="flex items-center justify-center gap-sm">
587
+ <Loader size={16} className="animate-spin" />
588
+ Starting pipeline…
589
+ </span>
590
+ ) : (
591
+ <>
592
+ Run Credit Analysis <ArrowRight size={16} />
593
+ </>
594
+ )}
595
+ </button>
596
+
597
+ <p
598
+ style={{
599
+ color: "var(--text-muted)",
600
+ fontSize: "12px",
601
+ textAlign: "center",
602
+ marginTop: "12px",
603
+ }}
604
+ >
605
+ Analysis typically takes 2–4 minutes across 12 pipeline stages.
606
+ </p>
607
+ </div>
608
  </div>
609
  </div>
610
  );