| @ -0,0 +1,24 @@ | |||
| # Logs | |||
| logs | |||
| *.log | |||
| npm-debug.log* | |||
| yarn-debug.log* | |||
| yarn-error.log* | |||
| pnpm-debug.log* | |||
| lerna-debug.log* | |||
| node_modules | |||
| dist | |||
| dist-ssr | |||
| *.local | |||
| # Editor directories and files | |||
| .vscode/* | |||
| !.vscode/extensions.json | |||
| .idea | |||
| .DS_Store | |||
| *.suo | |||
| *.ntvs* | |||
| *.njsproj | |||
| *.sln | |||
| *.sw? | |||
| @ -0,0 +1,116 @@ | |||
| import React, { useState } from 'react'; | |||
| import Header from './src/Header/Header'; | |||
| import BenefitsSidebar from './src/BasicDetails/BenefitsSidebar'; | |||
| import QuoteForm from './src/BasicDetails/QuoteForm'; | |||
| import AIAdvisor from './Components/AIAdvisor'; | |||
| import WhyChooseUs from './Components/WhyChooseUs'; | |||
| import { FormData, InsuranceQuote, VehicleInfo } from './types'; | |||
| import QuoteListPage from './src/Quotation/QuoteListPage'; | |||
| import ProposalPage from './src/KYCDetails/ProposalPage'; | |||
| import CheckoutPage from './src/VehicleDetails/CheckoutPage'; | |||
| import BasicDetails from './src/BasicDetails'; | |||
| const App: React.FC = () => { | |||
| const [step, setStep] = useState(1); | |||
| const [formData, setFormData] = useState<FormData>({ | |||
| registrationNumber: '', | |||
| previousPolicyNumber: '', | |||
| expiryDate: '', | |||
| coverType: 'comprehensive', | |||
| claimStatus: 'no', | |||
| ncb: 0, | |||
| }); | |||
| const [selectedQuote, setSelectedQuote] = useState<InsuranceQuote | null>(null); | |||
| const vehicleInfo: VehicleInfo = { | |||
| model: 'H NESS CB350 DLX2 DUALTONE', | |||
| make: 'HONDA MOTORCYCLE AND SCOOTER INDIA (P) LTD', | |||
| fuelType: 'PETROL', | |||
| variant: 'HIGHNESS CB 350 DLX 2', | |||
| registeredCity: 'R.T.O.BORIVALI, Maharashtra', | |||
| registeredDate: '01-Feb-2021', | |||
| chassisNumber: 'ME4NC586AMA005171', | |||
| engineNumber: 'NC58EA1008396', | |||
| previousPolicyNumber: '2546754321', | |||
| previousInsurer: 'TATA General Insurance', | |||
| previousPolicyEndDate: '2025-07-31', | |||
| regNo: formData.registrationNumber || 'MH47AX9310' | |||
| }; | |||
| const handleFormChange = (updates: Partial<FormData>) => { | |||
| setFormData((prev) => ({ ...prev, ...updates })); | |||
| }; | |||
| const handleSubmit = (e: React.FormEvent) => { | |||
| e.preventDefault(); | |||
| setStep(2); | |||
| window.scrollTo({ top: 0, behavior: 'smooth' }); | |||
| }; | |||
| const handleBuyNow = (quote: InsuranceQuote) => { | |||
| setSelectedQuote(quote); | |||
| setStep(3); | |||
| window.scrollTo({ top: 0, behavior: 'smooth' }); | |||
| }; | |||
| const handleKycComplete = () => { | |||
| setStep(4); | |||
| window.scrollTo({ top: 0, behavior: 'smooth' }); | |||
| }; | |||
| const userProfile = { | |||
| name: 'Akanksha Sahai Srivastava', | |||
| email: 'akshri.inv@gmail.com', | |||
| avatarLetter: 'A' | |||
| }; | |||
| return ( | |||
| <div className="min-h-screen flex flex-col bg-[#f8fafc]"> | |||
| <Header user={step >= 2 ? userProfile : undefined} /> | |||
| {step === 1 && ( | |||
| <BasicDetails | |||
| formData={formData} | |||
| onChange={handleFormChange} | |||
| onSubmit={handleSubmit} | |||
| /> | |||
| )} | |||
| {step === 2 && ( | |||
| <QuoteListPage onBuyNow={handleBuyNow} /> | |||
| )} | |||
| {step === 3 && selectedQuote && ( | |||
| <ProposalPage quote={selectedQuote} vehicle={vehicleInfo} onNext={handleKycComplete} /> | |||
| )} | |||
| {step === 4 && selectedQuote && ( | |||
| <CheckoutPage quote={selectedQuote} vehicle={vehicleInfo} /> | |||
| )} | |||
| <footer className="bg-white py-12 border-t border-slate-100 mt-auto"> | |||
| <div className="container mx-auto px-4 text-center"> | |||
| <div className="flex justify-center mb-6 opacity-30 grayscale"> | |||
| <div className="flex flex-col leading-none text-left"> | |||
| <span className="text-xl font-bold" style={{ color: '#1a2b4b', fontFamily: 'serif' }}>nivesh</span> | |||
| <span className="text-[8px] font-bold tracking-[0.2em] uppercase" style={{ color: '#1a2b4b' }}>Insurance</span> | |||
| </div> | |||
| </div> | |||
| <div className="max-w-3xl mx-auto space-y-3"> | |||
| <p className="text-slate-500 text-[10px] font-medium leading-relaxed"> | |||
| A wholly owned subsidiary of Providential Platforms Private Limited (<a href="https://www.nivesh.com" target="_blank" rel="noopener noreferrer" className="text-[#e31e24] hover:underline">https://www.nivesh.com</a>) | |||
| </p> | |||
| <p className="text-slate-400 text-[10px] font-medium leading-relaxed"> | |||
| Nivesh Insurance BROKERS PRIVATE LIMITED is a Direct Broker (Life & General) registered by IRDAI vide Registration Code IRDA/DB856/21, Certificate of Registration No. 769 valid upto 02-09-2027. | |||
| </p> | |||
| </div> | |||
| </div> | |||
| </footer> | |||
| </div> | |||
| ); | |||
| }; | |||
| export default App; | |||
| @ -0,0 +1,101 @@ | |||
| import React, { useEffect, useState, useRef } from 'react'; | |||
| import { GoogleGenAI } from "@google/genai"; | |||
| import { FormData } from '../types'; | |||
| interface AIAdvisorProps { | |||
| formData: FormData; | |||
| } | |||
| const AIAdvisor: React.FC<AIAdvisorProps> = ({ formData }) => { | |||
| // New pitchable initial line | |||
| const [advice, setAdvice] = useState<string>("Smart recommendations tailored to your vehicle's profile."); | |||
| const [isLoading, setIsLoading] = useState(false); | |||
| const abortControllerRef = useRef<AbortController | null>(null); | |||
| const getExpertAdvice = async (data: FormData) => { | |||
| if (!data.registrationNumber && data.ncb === 0 && data.claimStatus === 'no') return; | |||
| setIsLoading(true); | |||
| if (abortControllerRef.current) abortControllerRef.current.abort(); | |||
| abortControllerRef.current = new AbortController(); | |||
| try { | |||
| const ai = new GoogleGenAI({ apiKey: process.env.API_KEY || '' }); | |||
| const prompt = ` | |||
| You are a Senior Insurance Underwriter at Nivesh Insurance. | |||
| Analyze this 2-wheeler insurance profile and provide ONE professional, concise guidance tip (max 15 words). | |||
| Profile Details: | |||
| - Vehicle Reg: ${data.registrationNumber || 'Not provided'} | |||
| - Previous Claim: ${data.claimStatus} | |||
| - Current No Claim Bonus: ${data.ncb}% | |||
| - Chosen Cover: ${data.coverType} | |||
| Focus on technical insurance advice like NCB protection, Zero-Dep value, or IDV optimization. | |||
| Do NOT mention "AI", "Algorithms", or "Premium Discounts". Use an authoritative, professional tone. | |||
| `; | |||
| const response = await ai.models.generateContent({ | |||
| model: 'gemini-3-flash-preview', | |||
| contents: prompt, | |||
| config: { | |||
| temperature: 0.6, | |||
| maxOutputTokens: 60, | |||
| } | |||
| }); | |||
| const text = response.text; | |||
| if (text) { | |||
| setAdvice(text.trim()); | |||
| } | |||
| } catch (error: any) { | |||
| if (error.name !== 'AbortError') { | |||
| setAdvice("Maintain a consistent insurance history for seamless future claim processing."); | |||
| } | |||
| } finally { | |||
| setIsLoading(false); | |||
| } | |||
| }; | |||
| useEffect(() => { | |||
| const handler = setTimeout(() => { | |||
| getExpertAdvice(formData); | |||
| }, 1200); | |||
| return () => clearTimeout(handler); | |||
| }, [formData.registrationNumber, formData.claimStatus, formData.ncb, formData.coverType]); | |||
| return ( | |||
| <div className="bg-white border-2 border-slate-100 rounded-3xl p-6 flex gap-5 items-center relative overflow-hidden group hover:border-[#1a2b4b]/20 transition-all duration-500 shadow-sm"> | |||
| <div className="absolute top-0 right-0 -mr-4 -mt-4 w-24 h-24 bg-[#1a2b4b]/5 rounded-full blur-2xl group-hover:bg-[#e31e24]/5 transition-colors duration-500"></div> | |||
| <div className="flex-shrink-0 relative"> | |||
| <div className={`w-12 h-12 bg-slate-50 border border-slate-100 rounded-2xl flex items-center justify-center shadow-inner transition-transform duration-500 ${isLoading ? 'scale-95' : 'group-hover:scale-105'}`}> | |||
| <svg className={`w-6 h-6 ${isLoading ? 'text-[#e31e24] animate-pulse' : 'text-[#1a2b4b]'}`} fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |||
| <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" /> | |||
| </svg> | |||
| </div> | |||
| </div> | |||
| <div className="flex-grow"> | |||
| <div className="flex items-center gap-2 mb-1"> | |||
| <h4 className="text-[10px] font-black text-[#e31e24] uppercase tracking-[0.2em]">Smart Insight</h4> | |||
| {isLoading && <span className="text-[8px] font-bold text-slate-400 animate-pulse italic">Optimizing...</span>} | |||
| </div> | |||
| <p className={`text-sm font-semibold text-[#1a2b4b] leading-snug transition-all duration-500 ${isLoading ? 'opacity-40' : 'opacity-100'}`}> | |||
| {advice} | |||
| </p> | |||
| </div> | |||
| <div className="hidden md:block flex-shrink-0 ml-2"> | |||
| <div className="px-3 py-1 bg-[#1a2b4b]/5 rounded-full border border-[#1a2b4b]/10"> | |||
| <span className="text-[9px] font-black text-[#1a2b4b]">PROTECT</span> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| ); | |||
| }; | |||
| export default AIAdvisor; | |||
| @ -0,0 +1,119 @@ | |||
| import React, { useState } from 'react'; | |||
| import { InsuranceQuote } from '../types'; | |||
| interface QuoteCardProps { | |||
| quote: InsuranceQuote; | |||
| onBuy?: () => void; | |||
| } | |||
| const QuoteCard: React.FC<QuoteCardProps> = ({ quote, onBuy }) => { | |||
| const [isExpanded, setIsExpanded] = useState(false); | |||
| // Specific features to highlight on the front line | |||
| const highlightFeatures = quote.features.filter(f => | |||
| ['PERSONAL_ACCIDENT_COVER', 'ROAD_SIDE_ASSISTANCE', 'ZERO_DEPRECIATION', 'ENGINE_COVER', 'Tax'].includes(f.label) | |||
| ).slice(0, 4); // Keep only one line worth of features | |||
| return ( | |||
| <div className="bg-white rounded-2xl border border-slate-200 shadow-sm hover:shadow-md transition-shadow overflow-hidden mb-4"> | |||
| {/* Top Row: Insurer & Pricing */} | |||
| <div className="p-5 flex flex-col md:flex-row items-center gap-6"> | |||
| {/* Insurer Logo & Name */} | |||
| <div className="flex items-center gap-4 w-full md:w-1/3"> | |||
| <div className="w-14 h-14 bg-white rounded-lg p-1 flex items-center justify-center border border-slate-100 flex-shrink-0"> | |||
| <img src={quote.insurerLogo} alt={quote.insurerName} className="max-h-full max-w-full object-contain" /> | |||
| </div> | |||
| <div> | |||
| <h3 className="text-[#1a2b4b] font-bold text-sm leading-tight line-clamp-2">{quote.insurerName}</h3> | |||
| <div className="flex items-center gap-2 mt-1"> | |||
| <span className="bg-emerald-50 text-emerald-600 text-[9px] font-black px-1.5 py-0.5 rounded uppercase border border-emerald-100">Quick Approval</span> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| {/* IDV Value Display */} | |||
| <div className="flex-1 flex items-center justify-center md:border-x border-slate-100"> | |||
| <div className="text-center md:px-4"> | |||
| <p className="text-[10px] text-slate-400 uppercase font-black tracking-widest mb-0.5">IDV Value</p> | |||
| <p className="text-sm font-bold text-[#1a2b4b]">₹ {quote.idvValue.toLocaleString()}</p> | |||
| </div> | |||
| </div> | |||
| {/* Action & Price */} | |||
| <div className="flex items-center gap-4 w-full md:w-auto justify-between md:justify-end"> | |||
| <div className="text-right"> | |||
| <p className="text-[10px] text-slate-400 font-bold line-through">₹ {(quote.premium + 150).toLocaleString()}</p> | |||
| <p className="text-2xl font-black text-[#1a2b4b] leading-none"> | |||
| <span className="text-xs font-bold mr-0.5">₹</span> | |||
| {quote.premium.toLocaleString()} | |||
| </p> | |||
| </div> | |||
| <button | |||
| onClick={onBuy} | |||
| className="bg-[#2b458c] hover:bg-[#1a2b4b] text-white px-8 py-3 rounded-xl font-black text-sm transition-all shadow-lg shadow-[#2b458c]/10" | |||
| > | |||
| BUY NOW | |||
| </button> | |||
| </div> | |||
| </div> | |||
| {/* NEW SECTION: Key features in one line below Buy Now */} | |||
| <div className="px-5 pb-4"> | |||
| <div className="flex flex-wrap gap-4 items-center"> | |||
| <span className="text-[9px] font-black text-slate-400 uppercase tracking-widest mr-2">Key Benefits:</span> | |||
| {highlightFeatures.map((f, i) => ( | |||
| <div key={i} className="flex items-center gap-1.5"> | |||
| <svg className="w-3 h-3 text-emerald-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="3" d="M5 13l4 4L19 7" /></svg> | |||
| <span className="text-[10px] text-[#1a2b4b] font-bold"> | |||
| {f.label.replace(/_/g, ' ')}: <span className="text-slate-500 font-black">{typeof f.value === 'number' && f.value > 1000 ? `₹${(f.value/100000).toFixed(1)}L` : f.value}</span> | |||
| </span> | |||
| </div> | |||
| ))} | |||
| </div> | |||
| </div> | |||
| {/* Expand/Collapse Toggle Bar */} | |||
| <button | |||
| onClick={() => setIsExpanded(!isExpanded)} | |||
| className="w-full py-2 bg-slate-50/50 border-t border-slate-50 flex items-center justify-center gap-2 hover:bg-slate-50 transition-colors" | |||
| > | |||
| <span className="text-[10px] font-black text-slate-400 uppercase tracking-widest"> | |||
| {isExpanded ? 'Hide Details' : 'View All Policy Features'} | |||
| </span> | |||
| <svg | |||
| className={`w-3 h-3 text-slate-400 transition-transform duration-300 ${isExpanded ? 'rotate-180' : ''}`} | |||
| fill="none" stroke="currentColor" viewBox="0 0 24 24" | |||
| > | |||
| <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M19 9l-7 7-7-7" /> | |||
| </svg> | |||
| </button> | |||
| {/* Expanded Content */} | |||
| {isExpanded && ( | |||
| <div className="p-6 bg-white border-t border-slate-100 animate-in fade-in slide-in-from-top-2 duration-300"> | |||
| <h4 className="text-[#1a2b4b] text-[10px] font-black uppercase tracking-[0.2em] mb-4 opacity-50">Detailed Coverage & Breakdown</h4> | |||
| <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-x-8 gap-y-2"> | |||
| {quote.features.map((feature, idx) => ( | |||
| <div key={idx} className="flex items-center justify-between gap-2 text-[10px] py-1 border-b border-slate-50 last:border-0"> | |||
| <div className="flex items-center gap-2 overflow-hidden"> | |||
| <svg className="w-3 h-3 text-emerald-500 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |||
| <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="3" d="M5 13l4 4L19 7" /> | |||
| </svg> | |||
| <span className="text-slate-500 font-bold uppercase tracking-tight truncate">{feature.label.replace(/_/g, ' ')}</span> | |||
| </div> | |||
| <span className="text-[#1a2b4b] font-black flex-shrink-0"> | |||
| {feature.value === true || feature.value === 'True' || feature.value === 'Included' || feature.value === 'Yes' ? 'Yes' : | |||
| feature.value === false || feature.value === 'False' || feature.value === 'No' ? 'No' : | |||
| feature.value} | |||
| </span> | |||
| </div> | |||
| ))} | |||
| </div> | |||
| </div> | |||
| )} | |||
| </div> | |||
| ); | |||
| }; | |||
| export default QuoteCard; | |||
| @ -0,0 +1,58 @@ | |||
| import React, { useState, useEffect } from 'react'; | |||
| const WhyChooseUs: React.FC = () => { | |||
| const points = [ | |||
| { title: "99.2% Settlement", subtitle: "Industry leading claim record", icon: "🏆" }, | |||
| { title: "24/7 Support", subtitle: "Round the clock assistance", icon: "📞" }, | |||
| { title: "Instant Policy", subtitle: "Buy and download in 2 minutes", icon: "⚡" }, | |||
| { title: "Paperless Claims", subtitle: "Fully digital hassle-free process", icon: "📲" }, | |||
| { title: "Lowest Prices", subtitle: "Best quotes guaranteed", icon: "💰" } | |||
| ]; | |||
| const [currentIndex, setCurrentIndex] = useState(0); | |||
| useEffect(() => { | |||
| const timer = setInterval(() => { | |||
| setCurrentIndex((prev) => (prev + 1) % points.length); | |||
| }, 3000); | |||
| return () => clearInterval(timer); | |||
| }, []); | |||
| return ( | |||
| <div className="bg-[#f1f5f9] rounded-3xl p-6 border border-slate-200 overflow-hidden relative"> | |||
| <div className="flex items-center justify-between mb-4"> | |||
| <h3 className="text-[#1a2b4b] text-xs font-black uppercase tracking-widest">Why Choose Us?</h3> | |||
| <div className="flex gap-1"> | |||
| {points.map((_, i) => ( | |||
| <div | |||
| key={i} | |||
| className={`w-1.5 h-1.5 rounded-full transition-all duration-300 ${i === currentIndex ? 'bg-[#e31e24] w-4' : 'bg-slate-300'}`} | |||
| /> | |||
| ))} | |||
| </div> | |||
| </div> | |||
| <div className="relative h-16"> | |||
| {points.map((point, idx) => ( | |||
| <div | |||
| key={idx} | |||
| className={`absolute inset-0 flex items-center gap-4 transition-all duration-700 ease-in-out ${ | |||
| idx === currentIndex ? 'opacity-100 translate-x-0' : 'opacity-0 translate-x-8 pointer-events-none' | |||
| }`} | |||
| > | |||
| <div className="w-12 h-12 bg-white rounded-2xl flex items-center justify-center text-2xl shadow-sm border border-slate-100"> | |||
| {point.icon} | |||
| </div> | |||
| <div> | |||
| <h4 className="text-[#1a2b4b] font-bold text-sm leading-none mb-1">{point.title}</h4> | |||
| <p className="text-slate-500 text-[11px] font-medium">{point.subtitle}</p> | |||
| </div> | |||
| </div> | |||
| ))} | |||
| </div> | |||
| </div> | |||
| ); | |||
| }; | |||
| export default WhyChooseUs; | |||
| @ -0,0 +1,20 @@ | |||
| <div align="center"> | |||
| <img width="1200" height="475" alt="GHBanner" src="https://github.com/user-attachments/assets/0aa67016-6eaf-458a-adb2-6e31a0763ed6" /> | |||
| </div> | |||
| # Run and deploy your AI Studio app | |||
| This contains everything you need to run your app locally. | |||
| View your app in AI Studio: https://ai.studio/apps/drive/13U7FfFtZi6YY5N1GH86-GpTd7TFEKQyc | |||
| ## Run Locally | |||
| **Prerequisites:** Node.js | |||
| 1. Install dependencies: | |||
| `npm install` | |||
| 2. Set the `GEMINI_API_KEY` in [.env.local](.env.local) to your Gemini API key | |||
| 3. Run the app: | |||
| `npm run dev` | |||
| @ -0,0 +1,34 @@ | |||
| <!DOCTYPE html> | |||
| <html lang="en"> | |||
| <head> | |||
| <meta charset="UTF-8" /> | |||
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |||
| <title>Nivesh Insurance | 2-Wheeler Quote</title> | |||
| <script src="https://cdn.tailwindcss.com"></script> | |||
| <link rel="preconnect" href="https://fonts.googleapis.com"> | |||
| <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> | |||
| <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet"> | |||
| <style> | |||
| body { | |||
| font-family: 'Inter', sans-serif; | |||
| background-color: #f8fafc; | |||
| } | |||
| </style> | |||
| <script type="importmap"> | |||
| { | |||
| "imports": { | |||
| "@google/genai": "https://esm.sh/@google/genai@^1.34.0", | |||
| "react/": "https://esm.sh/react@^19.2.3/", | |||
| "react": "https://esm.sh/react@^19.2.3", | |||
| "react-dom/": "https://esm.sh/react-dom@^19.2.3/" | |||
| } | |||
| } | |||
| </script> | |||
| <link rel="stylesheet" href="/index.css"> | |||
| </head> | |||
| <body> | |||
| <div id="root"></div> | |||
| <script type="module" src="/index.tsx"></script> | |||
| </body> | |||
| </html> | |||
| @ -0,0 +1,38 @@ | |||
| import React from 'react'; | |||
| import ReactDOM from 'react-dom/client'; | |||
| import App from './App'; | |||
| import PageRoutes from './src/Routes'; | |||
| import Header from './src/Header/Header'; | |||
| const rootElement = document.getElementById('root'); | |||
| if (!rootElement) { | |||
| throw new Error("Could not find root element to mount to"); | |||
| } | |||
| const root = ReactDOM.createRoot(rootElement); | |||
| root.render( | |||
| <React.StrictMode> | |||
| {/* <App /> */} | |||
| <Header /> | |||
| <PageRoutes /> | |||
| <footer className="bg-white py-12 border-t border-slate-100 mt-auto"> | |||
| <div className="container mx-auto px-4 text-center"> | |||
| <div className="flex justify-center mb-6 opacity-30 grayscale"> | |||
| <div className="flex flex-col leading-none text-left"> | |||
| <span className="text-xl font-bold" style={{ color: '#1a2b4b', fontFamily: 'serif' }}>nivesh</span> | |||
| <span className="text-[8px] font-bold tracking-[0.2em] uppercase" style={{ color: '#1a2b4b' }}>Insurance</span> | |||
| </div> | |||
| </div> | |||
| <div className="max-w-3xl mx-auto space-y-3"> | |||
| <p className="text-slate-500 text-[10px] font-medium leading-relaxed"> | |||
| A wholly owned subsidiary of Providential Platforms Private Limited (<a href="https://www.nivesh.com" target="_blank" rel="noopener noreferrer" className="text-[#e31e24] hover:underline">https://www.nivesh.com</a>) | |||
| </p> | |||
| <p className="text-slate-400 text-[10px] font-medium leading-relaxed"> | |||
| Nivesh Insurance BROKERS PRIVATE LIMITED is a Direct Broker (Life & General) registered by IRDAI vide Registration Code IRDA/DB856/21, Certificate of Registration No. 769 valid upto 02-09-2027. | |||
| </p> | |||
| </div> | |||
| </div> | |||
| </footer> | |||
| </React.StrictMode> | |||
| ); | |||
| @ -0,0 +1,7 @@ | |||
| { | |||
| "name": "Nivesh Insurance - 2-Wheeler full journey", | |||
| "description": "A modern, high-conversion insurance quote generation landing page for two-wheeler insurance, featuring AI-assisted guidance and a clean, user-friendly interface.", | |||
| "requestFramePermissions": [ | |||
| "camera" | |||
| ] | |||
| } | |||
| @ -0,0 +1,24 @@ | |||
| { | |||
| "name": "nivesh-insurance---2-wheeler-full-journey", | |||
| "private": true, | |||
| "version": "0.0.0", | |||
| "type": "module", | |||
| "scripts": { | |||
| "dev": "vite", | |||
| "build": "vite build", | |||
| "preview": "vite preview", | |||
| "start": "react-scripts start" | |||
| }, | |||
| "dependencies": { | |||
| "@google/genai": "^1.34.0", | |||
| "react": "^19.2.3", | |||
| "react-dom": "^19.2.3", | |||
| "react-router-dom": "^7.12.0" | |||
| }, | |||
| "devDependencies": { | |||
| "@types/node": "^22.14.0", | |||
| "@vitejs/plugin-react": "^5.0.0", | |||
| "typescript": "~5.8.2", | |||
| "vite": "^6.2.0" | |||
| } | |||
| } | |||
| @ -0,0 +1,70 @@ | |||
| import React from 'react'; | |||
| const BenefitsSidebar: React.FC = () => { | |||
| const coveragePoints = [ | |||
| "Damage due to accident of carrying truck / conveyance", | |||
| "Fire, lightening or explosion", | |||
| "Theft / pilferage", | |||
| "Loading and unloading damage", | |||
| "Malicious damage", | |||
| "Overturning or Derailment", | |||
| "Breakage of bridge", | |||
| "Collision", | |||
| "Damage to the goods / cargo due to sea / river / lake water entering inside the vehicle" | |||
| ]; | |||
| return ( | |||
| <div className="space-y-4"> | |||
| <div className="bg-[#f8f9fb] border border-slate-200 rounded-3xl p-6 shadow-sm"> | |||
| <h3 className="text-[#1a2b4b] text-lg font-bold mb-6 flex items-center gap-2"> | |||
| <span className="w-1.5 h-6 bg-[#e31e24] rounded-full"></span> | |||
| Standard Risks Covered | |||
| </h3> | |||
| <div className="space-y-4"> | |||
| {coveragePoints.map((point, idx) => ( | |||
| <div key={idx} className="flex gap-3 items-start group"> | |||
| <div className="flex-shrink-0 mt-0.5"> | |||
| <div className="w-5 h-5 rounded-full bg-emerald-50 flex items-center justify-center border border-emerald-100 group-hover:bg-emerald-500 group-hover:border-emerald-500 transition-colors duration-300"> | |||
| <svg | |||
| className="w-2.5 h-2.5 text-emerald-600 group-hover:text-white transition-colors duration-300" | |||
| fill="none" | |||
| viewBox="0 0 24 24" | |||
| stroke="currentColor" | |||
| > | |||
| <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="4" d="M5 13l4 4L19 7" /> | |||
| </svg> | |||
| </div> | |||
| </div> | |||
| <p className="text-[12px] font-medium text-[#1a2b4b]/80 leading-snug"> | |||
| {point} | |||
| </p> | |||
| </div> | |||
| ))} | |||
| </div> | |||
| <div className="mt-8 pt-6 border-t border-slate-200"> | |||
| <div className="flex items-center justify-between p-3 bg-white rounded-2xl border border-slate-100"> | |||
| <div className="flex items-center gap-2"> | |||
| <div className="w-2 h-2 rounded-full bg-emerald-500 animate-pulse"></div> | |||
| <span className="text-[10px] font-bold text-slate-500 uppercase">Settlement Ratio</span> | |||
| </div> | |||
| <span className="text-sm font-black text-[#1a2b4b]">99.2%</span> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <div className="p-6 rounded-3xl bg-gradient-to-br from-[#1a2b4b] to-[#253b66] text-white relative overflow-hidden group"> | |||
| <div className="absolute top-[-10px] right-[-10px] w-24 h-24 bg-white/5 rounded-full blur-2xl group-hover:bg-[#e31e24]/10 transition-all duration-500"></div> | |||
| <h4 className="text-sm font-bold mb-2 text-white/90 uppercase tracking-widest">Instant Support</h4> | |||
| <p className="text-xs text-white/70 mb-4">Questions about coverage? Our experts are here to help you choose.</p> | |||
| <button className="w-full py-3 bg-[#e31e24] hover:bg-[#c41a1f] text-white rounded-xl text-xs font-bold transition-all shadow-lg shadow-black/20"> | |||
| Talk to an Expert | |||
| </button> | |||
| </div> | |||
| </div> | |||
| ); | |||
| }; | |||
| export default BenefitsSidebar; | |||
| @ -0,0 +1,171 @@ | |||
| import React from 'react'; | |||
| import { FormData } from '@/types'; | |||
| import { useNavigate } from 'react-router-dom'; | |||
| interface QuoteFormProps { | |||
| data: FormData; | |||
| onChange: (updates: Partial<FormData>) => void; | |||
| onSubmit: (e: React.FormEvent) => void; | |||
| } | |||
| const QuoteForm: React.FC<QuoteFormProps> = ({ data, onChange, onSubmit }) => { | |||
| console.log("SKJDHJSHDSJD", data); | |||
| const navigate = useNavigate() | |||
| const handleSubmit = (e: React.FormEvent) => { | |||
| e.preventDefault(); | |||
| onSubmit(e); | |||
| // navigate("/quote-list"); | |||
| }; | |||
| const handleRedirection = () => { | |||
| if (data.registrationNumber || data.expiryDate) { | |||
| navigate("/quote-list"); | |||
| } | |||
| }; | |||
| return ( | |||
| <form onSubmit={handleSubmit} className="space-y-8"> | |||
| <div className="grid grid-cols-1 md:grid-cols-2 gap-x-8 gap-y-6"> | |||
| {/* Registration Number */} | |||
| <div className="space-y-2"> | |||
| <label className="text-xs font-black uppercase tracking-widest text-slate-400"> | |||
| Vehicle Registration Number <span className="text-[#e31e24]">*</span> | |||
| </label> | |||
| <input | |||
| type="text" | |||
| placeholder="MH 12 AB 1234" | |||
| className="w-full px-5 py-4 rounded-2xl border-2 border-slate-100 bg-white focus:border-[#e31e24] focus:ring-4 focus:ring-[#e31e24]/5 outline-none transition-all placeholder:text-slate-300 uppercase font-bold text-[#1a2b4b]" | |||
| value={data.registrationNumber} | |||
| onChange={(e) => onChange({ registrationNumber: e.target.value })} | |||
| required | |||
| /> | |||
| </div> | |||
| {/* Previous Policy Number */} | |||
| <div className="space-y-2"> | |||
| <label className="text-xs font-black uppercase tracking-widest text-slate-400"> | |||
| Previous Policy Number | |||
| </label> | |||
| <input | |||
| type="text" | |||
| placeholder="Optional" | |||
| className="w-full px-5 py-4 rounded-2xl border-2 border-slate-100 bg-white focus:border-[#1a2b4b] focus:ring-4 focus:ring-[#1a2b4b]/5 outline-none transition-all placeholder:text-slate-300 text-[#1a2b4b]" | |||
| value={data.previousPolicyNumber} | |||
| onChange={(e) => onChange({ previousPolicyNumber: e.target.value })} | |||
| /> | |||
| </div> | |||
| {/* Expiry Date */} | |||
| <div className="space-y-2"> | |||
| <label className="text-xs font-black uppercase tracking-widest text-slate-400"> | |||
| Expiry Date <span className="text-[#e31e24]">*</span> | |||
| </label> | |||
| <input | |||
| type="date" | |||
| className="w-full px-5 py-4 rounded-2xl border-2 border-slate-100 bg-white focus:border-[#1a2b4b] focus:ring-4 focus:ring-[#1a2b4b]/5 outline-none transition-all font-medium text-[#1a2b4b]" | |||
| value={data.expiryDate} | |||
| onChange={(e) => onChange({ expiryDate: e.target.value })} | |||
| required | |||
| /> | |||
| </div> | |||
| {/* Cover Type */} | |||
| <div className="space-y-2"> | |||
| <label className="text-xs font-black uppercase tracking-widest text-slate-400"> | |||
| Cover Type | |||
| </label> | |||
| <div className="relative"> | |||
| <select | |||
| className="w-full px-5 py-4 rounded-2xl border-2 border-slate-100 focus:border-[#1a2b4b] focus:ring-4 focus:ring-[#1a2b4b]/5 outline-none transition-all bg-white appearance-none cursor-pointer font-medium text-[#1a2b4b]" | |||
| value={data.coverType} | |||
| onChange={(e) => onChange({ coverType: e.target.value as any })} | |||
| > | |||
| <option value="comprehensive">Comprehensive Cover</option> | |||
| <option value="third-party">Third Party Only</option> | |||
| <option value="own-damage">Standalone Own Damage</option> | |||
| </select> | |||
| <div className="absolute right-5 top-1/2 -translate-y-1/2 pointer-events-none text-slate-400"> | |||
| <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M19 9l-7 7-7-7" /></svg> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| {/* Claim Status */} | |||
| <div className="space-y-2"> | |||
| <label className="text-xs font-black uppercase tracking-widest text-slate-400"> | |||
| Claimed Last Year? | |||
| </label> | |||
| <div className="flex gap-3 p-1.5 bg-slate-50 rounded-2xl border border-slate-100"> | |||
| <button | |||
| type="button" | |||
| className={`flex-1 py-3 rounded-xl font-bold text-sm transition-all ${data.claimStatus === 'no' | |||
| ? 'bg-white text-[#e31e24] shadow-sm' | |||
| : 'text-slate-400 hover:text-slate-600' | |||
| }`} | |||
| onClick={() => onChange({ claimStatus: 'no' })} | |||
| > | |||
| No | |||
| </button> | |||
| <button | |||
| type="button" | |||
| className={`flex-1 py-3 rounded-xl font-bold text-sm transition-all ${data.claimStatus === 'yes' | |||
| ? 'bg-white text-[#e31e24] shadow-sm' | |||
| : 'text-slate-400 hover:text-slate-600' | |||
| }`} | |||
| onClick={() => onChange({ claimStatus: 'yes' })} | |||
| > | |||
| Yes | |||
| </button> | |||
| </div> | |||
| </div> | |||
| {/* NCB Percentage */} | |||
| <div className="space-y-2"> | |||
| <label className="text-xs font-black uppercase tracking-widest text-slate-400 flex justify-between"> | |||
| No Claim Bonus % | |||
| <span className="text-[#e31e24] font-black">SAVE UP TO 50%</span> | |||
| </label> | |||
| <div className="relative"> | |||
| <select | |||
| className="w-full px-5 py-4 rounded-2xl border-2 border-slate-100 focus:border-[#1a2b4b] focus:ring-4 focus:ring-[#1a2b4b]/5 outline-none transition-all bg-white appearance-none cursor-pointer font-bold text-[#1a2b4b]" | |||
| value={data.ncb} | |||
| onChange={(e) => onChange({ ncb: parseInt(e.target.value) })} | |||
| disabled={data.claimStatus === 'yes'} | |||
| > | |||
| <option value="0">0% (New Policy)</option> | |||
| <option value="20">20%</option> | |||
| <option value="25">25%</option> | |||
| <option value="35">35%</option> | |||
| <option value="45">45%</option> | |||
| <option value="50">50%</option> | |||
| </select> | |||
| <div className="absolute right-5 top-1/2 -translate-y-1/2 pointer-events-none text-slate-400"> | |||
| <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M19 9l-7 7-7-7" /></svg> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <div className="pt-6 border-t border-slate-50"> | |||
| <button | |||
| type="submit" | |||
| className="w-full bg-[#e31e24] hover:bg-[#c41a1f] text-white font-black py-5 px-8 rounded-2xl shadow-xl shadow-[#e31e24]/20 hover:shadow-[#e31e24]/30 hover:-translate-y-0.5 transition-all flex items-center justify-center gap-3 text-lg" | |||
| // onClick={() => navigate("/quote-list")} | |||
| onClick={() => handleRedirection()} | |||
| > | |||
| Check Live Prices | |||
| <svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |||
| <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M13 7l5 5m0 0l-5 5m5-5H6" /> | |||
| </svg> | |||
| </button> | |||
| </div> | |||
| </form> | |||
| ); | |||
| }; | |||
| export default QuoteForm; | |||
| @ -0,0 +1,84 @@ | |||
| import AIAdvisor from '@/Components/AIAdvisor'; | |||
| import WhyChooseUs from '@/Components/WhyChooseUs'; | |||
| import React, { useState } from 'react'; | |||
| import QuoteForm from './QuoteForm'; | |||
| import BenefitsSidebar from './BenefitsSidebar'; | |||
| import { FormData } from '@/types'; | |||
| const BasicDetails = (props) => { | |||
| const { handleSubmit } = props; | |||
| const [formData, setFormData] = useState<FormData>({ | |||
| registrationNumber: '', | |||
| previousPolicyNumber: '', | |||
| expiryDate: '', | |||
| coverType: 'comprehensive', | |||
| claimStatus: 'no', | |||
| ncb: 0, | |||
| }); | |||
| const handleFormChange = (updates: Partial<FormData>) => { | |||
| setFormData((prev) => ({ ...prev, ...updates })); | |||
| }; | |||
| return ( | |||
| <main className="flex-grow container mx-auto px-4 py-8 lg:py-16"> | |||
| <div className="max-w-6xl mx-auto"> | |||
| <div className="grid grid-cols-1 lg:grid-cols-12 gap-8 items-start"> | |||
| <aside className="lg:col-span-4 space-y-6 lg:sticky lg:top-28"> | |||
| <div className="mb-6"> | |||
| <h1 className="text-3xl font-bold text-[#1a2b4b] leading-tight mb-2"> | |||
| Protect your <br /><span className="text-[#e31e24]">2-Wheeler</span> today | |||
| </h1> | |||
| <p className="text-slate-500 text-sm"> | |||
| Join 2M+ riders who trust Nivesh for their journeys. | |||
| </p> | |||
| </div> | |||
| <BenefitsSidebar /> | |||
| </aside> | |||
| <div className="lg:col-span-8 space-y-8"> | |||
| <div className="bg-white rounded-3xl shadow-[0_10px_40px_-15px_rgba(0,0,0,0.08)] border border-slate-100 overflow-hidden"> | |||
| <div className="p-8 lg:p-10"> | |||
| <div className="flex items-center justify-between mb-8"> | |||
| <h2 className="text-2xl font-bold text-[#1a2b4b]"> | |||
| Get a Quick Quote | |||
| </h2> | |||
| <div className="text-[10px] font-black text-emerald-600 bg-emerald-50 px-3 py-1 rounded-full uppercase tracking-widest border border-emerald-100"> | |||
| Live Prices | |||
| </div> | |||
| </div> | |||
| <QuoteForm | |||
| data={formData} | |||
| onChange={handleFormChange} | |||
| onSubmit={handleSubmit} | |||
| /> | |||
| </div> | |||
| <div className="bg-[#1a2b4b] p-5 text-center"> | |||
| <p className="text-[11px] text-white/80 font-bold uppercase tracking-widest flex items-center justify-center gap-2"> | |||
| <svg className="w-4 h-4 text-emerald-400" fill="currentColor" viewBox="0 0 20 20"> | |||
| <path fillRule="evenodd" d="M2.166 4.999A11.954 11.954 0 0010 1.944 11.954 11.954 0 0017.834 5c.11.65.166 1.32.166 2.001 0 5.225-3.34 9.67-8 11.317C5.34 16.67 2 12.225 2 7c0-.682.057-1.35.166-2.001zm11.541 3.708a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd" /> | |||
| </svg> | |||
| IRDAI Certified Insurance Broker | |||
| </p> | |||
| </div> | |||
| </div> | |||
| <div className="grid grid-cols-1 md:grid-cols-2 gap-6"> | |||
| <AIAdvisor | |||
| formData={formData} | |||
| /> | |||
| <WhyChooseUs /> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </main> | |||
| ); | |||
| }; | |||
| export default BasicDetails; | |||
| @ -0,0 +1,8 @@ | |||
| export const formFileds = { | |||
| registrationNumber: '', | |||
| previousPolicyNumber: '', | |||
| expiryDate: '', | |||
| coverType: 'comprehensive', | |||
| claimStatus: 'no', | |||
| ncb: 0, | |||
| } | |||
| @ -0,0 +1,65 @@ | |||
| import React from 'react'; | |||
| interface HeaderProps { | |||
| user?: { | |||
| name: string; | |||
| email: string; | |||
| avatarLetter: string; | |||
| }; | |||
| } | |||
| const Header: React.FC<HeaderProps> = ({ user }) => { | |||
| return ( | |||
| <header className="bg-white border-b border-slate-100 sticky top-0 z-50"> | |||
| <div className="container mx-auto px-4 h-20 flex items-center justify-between"> | |||
| <div className="flex items-center gap-2"> | |||
| {/* Brand Logo Recreation */} | |||
| <div className="flex flex-col leading-none"> | |||
| <span className="text-3xl font-bold tracking-tight" style={{ color: '#e31e24', fontFamily: 'serif' }}> | |||
| nivesh | |||
| </span> | |||
| <span className="text-[11px] font-bold tracking-[0.2em] uppercase mt-[-2px]" style={{ color: '#1a2b4b' }}> | |||
| Insurance | |||
| </span> | |||
| </div> | |||
| </div> | |||
| <nav className="hidden md:flex items-center gap-10"> | |||
| <div className="relative group"> | |||
| <a href="#" className="text-[#1a2b4b] font-medium transition-colors flex items-center gap-1"> | |||
| Insurance | |||
| </a> | |||
| </div> | |||
| <a href="#" className="text-slate-600 hover:text-[#e31e24] font-medium transition-colors">Insurance Advisors</a> | |||
| <div className="relative group"> | |||
| <a href="#" className="text-slate-600 hover:text-[#e31e24] font-medium transition-colors flex items-center gap-1"> | |||
| Support | |||
| <svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M19 9l-7 7-7-7" /></svg> | |||
| </a> | |||
| </div> | |||
| </nav> | |||
| <div className="flex items-center gap-4"> | |||
| {user ? ( | |||
| <div className="flex items-center gap-4 bg-slate-50 border border-slate-100 rounded-xl px-4 py-2"> | |||
| <div className="text-right hidden sm:block"> | |||
| <p className="text-xs font-bold text-[#1a2b4b]">{user.name}</p> | |||
| <p className="text-[10px] text-slate-400">{user.email}</p> | |||
| </div> | |||
| <div className="w-10 h-10 rounded-full bg-indigo-600 flex items-center justify-center text-white font-bold text-lg shadow-sm border-2 border-white"> | |||
| {user.avatarLetter} | |||
| </div> | |||
| </div> | |||
| ) : ( | |||
| <button className="bg-[#1a2b4b] text-white px-5 py-2 rounded-lg font-semibold text-sm hover:bg-[#253b66] transition-all"> | |||
| My Account | |||
| </button> | |||
| )} | |||
| </div> | |||
| </div> | |||
| </header> | |||
| ); | |||
| }; | |||
| export default Header; | |||
| @ -0,0 +1,200 @@ | |||
| import React, { useState } from 'react'; | |||
| import { InsuranceQuote, VehicleInfo, ProposalData } from '@/types'; | |||
| const ProposalPage: React.FC = () => { | |||
| const [proposal, setProposal] = useState<ProposalData>({ | |||
| pan: 'AATPU7693N', | |||
| name: 'Suketu Upadhyay', | |||
| dob: '1984-07-03', | |||
| phone: '9833604121', | |||
| email: 'Suketu123_Upadhyay@hotmail.com' | |||
| }); | |||
| const quote: InsuranceQuote = { | |||
| id: 'QUOTE123', | |||
| insurerName: 'TATA General Insurance', | |||
| insurerLogo: 'https://via.placeholder.com/100x40.png?text=TATA', | |||
| premium: 5000, | |||
| idvValue: 120000, | |||
| features: ['24x7 Support', 'Roadside Assistance'] | |||
| }; | |||
| const vehicle: VehicleInfo = { | |||
| model: 'H NESS CB350 DLX2 DUALTONE', | |||
| make: 'HONDA MOTORCYCLE AND SCOOTER INDIA (P) LTD', | |||
| fuelType: 'PETROL', | |||
| variant: 'HIGHNESS CB 350 DLX 2', | |||
| registeredCity: 'R.T.O.BORIVALI, Maharashtra', | |||
| registeredDate: '01-Feb-2021', | |||
| chassisNumber: 'ME4NC586AMA005171', | |||
| engineNumber: 'NC58EA1008396', | |||
| previousPolicyNumber: '2546754321', | |||
| previousInsurer: 'TATA General Insurance', | |||
| previousPolicyEndDate: '2025-07-31', | |||
| regNo: 'MH47AX9310' | |||
| }; | |||
| const tax = Math.round(quote.premium * 0.18); | |||
| const basePrice = quote.premium; | |||
| const total = basePrice + tax; | |||
| return ( | |||
| <div className="container mx-auto px-4 py-8 space-y-6 animate-in fade-in duration-500"> | |||
| {/* 1. Vehicle Details Header */} | |||
| <div className="bg-[#1a2b4b] text-white rounded-2xl shadow-xl overflow-hidden relative border border-white/10"> | |||
| <div className="p-6 grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-x-8 gap-y-6"> | |||
| <HeaderDetail label="MODEL:" value={vehicle.model} /> | |||
| <HeaderDetail label="MAKE:" value={vehicle.make} /> | |||
| <HeaderDetail label="FUEL_TYPE:" value={vehicle.fuelType} /> | |||
| <HeaderDetail label="VARIANT:" value={vehicle.variant} /> | |||
| <HeaderDetail label="REGISTERED_CITY:" value={vehicle.registeredCity} /> | |||
| <HeaderDetail label="REGISTERED_DATE:" value={vehicle.registeredDate} /> | |||
| <HeaderDetail label="CHASSIS_NUMBER:" value={vehicle.chassisNumber} /> | |||
| <HeaderDetail label="ENGINE_NUMBER:" value={vehicle.engineNumber} /> | |||
| <HeaderDetail label="PREVIOUS_INSURER:" value={vehicle.previousInsurer} /> | |||
| <HeaderDetail label="PREVIOUS_POLICY_NUMBER:" value={vehicle.previousPolicyNumber} /> | |||
| <HeaderDetail label="PREVIOUS_POLICY_END_DATE:" value={vehicle.previousPolicyEndDate} /> | |||
| <HeaderDetail label="REGISTRATION_NO:" value={vehicle.regNo} /> | |||
| </div> | |||
| </div> | |||
| <div className="flex flex-col lg:flex-row gap-8"> | |||
| {/* 2. KYC Information Form (Left) */} | |||
| <div className="lg:w-2/3 bg-white rounded-3xl p-8 border border-slate-200 shadow-sm self-start"> | |||
| <h3 className="text-lg font-black text-[#1a2b4b] mb-6 flex items-center gap-2"> | |||
| Owner Information | |||
| <span className="w-1.5 h-1.5 rounded-full bg-[#2b458c]"></span> | |||
| </h3> | |||
| <div className="grid grid-cols-1 md:grid-cols-2 gap-x-8 gap-y-6"> | |||
| <div className="space-y-2"> | |||
| <label className="text-xs font-bold text-slate-400">Pan *</label> | |||
| <input | |||
| type="text" | |||
| value={proposal.pan} | |||
| onChange={e => setProposal({ ...proposal, pan: e.target.value })} | |||
| className="w-full px-4 py-3.5 bg-white rounded-lg border border-slate-200 focus:ring-2 focus:ring-[#2b458c] font-medium text-[#1a2b4b] outline-none transition-all" | |||
| /> | |||
| </div> | |||
| <div className="space-y-2"> | |||
| <label className="text-xs font-bold text-slate-400">Name *</label> | |||
| <input | |||
| type="text" | |||
| value={proposal.name} | |||
| onChange={e => setProposal({ ...proposal, name: e.target.value })} | |||
| className="w-full px-4 py-3.5 bg-white rounded-lg border border-slate-200 focus:ring-2 focus:ring-[#2b458c] font-medium text-[#1a2b4b] outline-none transition-all" | |||
| /> | |||
| </div> | |||
| <div className="space-y-2"> | |||
| <label className="text-xs font-bold text-slate-400">Date of Birth *</label> | |||
| <input | |||
| type="date" | |||
| value={proposal.dob} | |||
| onChange={e => setProposal({ ...proposal, dob: e.target.value })} | |||
| className="w-full px-4 py-3.5 bg-white rounded-lg border border-slate-200 focus:ring-2 focus:ring-[#2b458c] font-medium text-[#1a2b4b] outline-none transition-all" | |||
| /> | |||
| </div> | |||
| <div className="space-y-2"> | |||
| <label className="text-xs font-bold text-slate-400">Phone Number *</label> | |||
| <input | |||
| type="tel" | |||
| value={proposal.phone} | |||
| onChange={e => setProposal({ ...proposal, phone: e.target.value })} | |||
| className="w-full px-4 py-3.5 bg-white rounded-lg border border-slate-200 focus:ring-2 focus:ring-[#2b458c] font-medium text-[#1a2b4b] outline-none transition-all" | |||
| /> | |||
| </div> | |||
| <div className="md:col-span-2 space-y-2"> | |||
| <label className="text-xs font-bold text-slate-400">Email *</label> | |||
| <input | |||
| type="email" | |||
| value={proposal.email} | |||
| onChange={e => setProposal({ ...proposal, email: e.target.value })} | |||
| className="w-full px-4 py-3.5 bg-white rounded-lg border border-slate-200 focus:ring-2 focus:ring-[#2b458c] font-medium text-[#1a2b4b] outline-none transition-all" | |||
| /> | |||
| </div> | |||
| </div> | |||
| <div className="mt-12 flex justify-end"> | |||
| <button | |||
| // onClick={onNext} | |||
| className="bg-[#e31e24] hover:bg-[#c41a1f] text-white px-12 py-4 rounded-lg font-bold transition-all shadow-lg hover:-translate-y-0.5" | |||
| > | |||
| Proceed to KYC | |||
| </button> | |||
| </div> | |||
| </div> | |||
| {/* 3. Premium Breakup (Right) */} | |||
| <div className="lg:w-1/3"> | |||
| <div className="bg-white rounded-3xl p-7 border border-slate-200 shadow-sm space-y-8 lg:sticky lg:top-28"> | |||
| <div className="flex items-center justify-between py-2 border-b border-slate-50"> | |||
| <div className="flex flex-col"> | |||
| <span className="text-[9px] font-black text-slate-400 uppercase tracking-widest mb-1">Insurance Partner</span> | |||
| <p className="text-[11px] font-bold text-[#1a2b4b] max-w-[180px] leading-tight">{quote.insurerName}</p> | |||
| </div> | |||
| <img src={quote.insurerLogo} alt={quote.insurerName} className="h-10 w-auto object-contain" /> | |||
| </div> | |||
| <div className="space-y-4"> | |||
| <PriceRow label="BASE PRICE" value={basePrice} /> | |||
| <PriceRow label="CONVIENCE FEE" value={0} /> | |||
| <PriceRow label="TAX" value={tax} /> | |||
| <PriceRow label="PROCESSING FEE" value={0} /> | |||
| <div className="pt-6 border-t-2 border-slate-100 flex justify-between items-center"> | |||
| <span className="text-sm font-black text-[#1a2b4b] uppercase tracking-wider">Total</span> | |||
| <span className="text-2xl font-black text-[#1a2b4b]">₹{total.toLocaleString()}</span> | |||
| </div> | |||
| </div> | |||
| <div className="pt-4 text-center"> | |||
| <p className="text-[10px] text-slate-400 leading-relaxed font-medium"> | |||
| By clicking on 'Proceed', I agree to the <a href="#" className="text-[#e31e24] font-bold hover:underline">terms & conditions</a> | |||
| </p> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| ); | |||
| }; | |||
| // Reusable Components | |||
| const HeaderDetail = ({ label, value }: { label: string; value: string }) => ( | |||
| <div className="flex flex-col gap-0.5"> | |||
| <span className="text-[9px] font-black text-slate-400 uppercase tracking-widest">{label}</span> | |||
| <span className="text-[11px] font-black text-white uppercase break-words leading-tight">{value || '-'}</span> | |||
| </div> | |||
| ); | |||
| const PriceRow = ({ label, value }: { label: string; value: number }) => ( | |||
| <div className="flex justify-between items-center"> | |||
| <span className="text-[11px] font-bold text-slate-400 uppercase tracking-wide">{label}</span> | |||
| <span className="text-sm font-black text-[#1a2b4b]">₹{value.toLocaleString()}</span> | |||
| </div> | |||
| ); | |||
| const FormInput = ({ | |||
| label, | |||
| value, | |||
| onChange, | |||
| type = 'text', | |||
| colSpan = 1 | |||
| }: { | |||
| label: string; | |||
| value: string; | |||
| onChange: (val: string) => void; | |||
| type?: string; | |||
| colSpan?: number; | |||
| }) => ( | |||
| <div className={colSpan > 1 ? 'md:col-span-2 space-y-2' : 'space-y-2'}> | |||
| <label className="text-xs font-bold text-slate-400">{label}</label> | |||
| <input | |||
| type={type} | |||
| value={value} | |||
| onChange={e => onChange(e.target.value)} | |||
| className="w-full px-4 py-3.5 bg-white rounded-lg border border-slate-200 focus:ring-2 focus:ring-[#2b458c] font-medium text-[#1a2b4b] outline-none transition-all" | |||
| /> | |||
| </div> | |||
| ); | |||
| export default ProposalPage; | |||
| @ -0,0 +1,247 @@ | |||
| import React, { useState } from 'react'; | |||
| import QuoteCard from '@/Components/QuoteCard'; | |||
| import { InsuranceQuote } from '@/types'; | |||
| interface QuoteListPageProps { | |||
| onBuyNow: (quote: InsuranceQuote) => void; | |||
| } | |||
| const QuoteListPage: React.FC<QuoteListPageProps> = ({ onBuyNow }) => { | |||
| const [isEditingIDV, setIsEditingIDV] = useState(false); | |||
| const [idvType, setIdvType] = useState<'lowest' | 'recommended' | 'custom'>('recommended'); | |||
| const [customIDV, setCustomIDV] = useState(9507); | |||
| // Tenure State | |||
| const [isEditingTenure, setIsEditingTenure] = useState(false); | |||
| const [tenure, setTenure] = useState<'1 Year' | '2 Year' | '3 Year'>('1 Year'); | |||
| const minIDV = 6914; | |||
| const maxIDV = 9507; | |||
| const currentIDV = idvType === 'custom' ? customIDV : (idvType === 'lowest' ? minIDV : maxIDV); | |||
| const dummyQuotes: InsuranceQuote[] = [ | |||
| { | |||
| id: '1', | |||
| insurerName: 'Universal Sompo General Insurance Ltd', | |||
| insurerLogo: 'https://companieslogo.com/img/orig/UNIVERSAL_SOMPO.NS-4b7b3d9c.png?t=1659939525', | |||
| idvValue: currentIDV, | |||
| premium: tenure === '1 Year' ? 2117 : tenure === '2 Year' ? 3850 : 5200, | |||
| features: [ | |||
| { label: 'PERSONAL_ACCIDENT_COVER', value: 1500000 }, | |||
| { label: 'ROAD_SIDE_ASSISTANCE', value: 'Included' }, | |||
| { label: 'ELECTRICAL_ACCESSORIES_COVER', value: 0 }, | |||
| { label: 'ENGINE_COVER', value: 0 }, | |||
| { label: 'KEY_COVER', value: 'No' }, | |||
| { label: 'TYRE_COVER', value: 0 }, | |||
| { label: 'PER_DAY_CASH', value: 0 }, | |||
| { label: 'Convience Fee', value: 0 }, | |||
| { label: 'Tax', value: 381 }, | |||
| { label: 'Manual Review', value: 'False' }, | |||
| { label: 'DEPRECIATION_COVER', value: 'No' }, | |||
| { label: 'CONSUMABLES_COVER', value: 'No' }, | |||
| { label: 'NON_ELECTRICAL_ACCESSORIES_COVER', value: 0 }, | |||
| { label: 'EXTERNAL_CNG_OR_LPG_COVER', value: 'No' }, | |||
| { label: 'PERSONAL_BAGGAGE_COVER', value: 0 }, | |||
| { label: 'RETURN_TO_INVOICE', value: 0 }, | |||
| { label: 'Base Price', value: 2117 }, | |||
| { label: 'Processing Fee', value: 0 }, | |||
| { label: 'Offer validity', value: 'P15D' } | |||
| ] | |||
| }, | |||
| { | |||
| id: '2', | |||
| insurerName: 'Digit Two Wheeler Insurance', | |||
| insurerLogo: 'https://cdn.freelogovectors.net/wp-content/uploads/2023/11/go-digit-logo-freelogovectors.net_.png', | |||
| idvValue: 9100.00, | |||
| premium: 942, | |||
| features: [ | |||
| { label: 'PERSONAL_ACCIDENT_COVER', value: 1500000 }, | |||
| { label: 'ZERO_DEPRECIATION', value: 'Yes' }, | |||
| { label: 'Tax', value: 142 }, | |||
| { label: 'Base Price', value: 800 } | |||
| ] | |||
| } | |||
| ]; | |||
| return ( | |||
| <div className="container mx-auto px-4 py-6 md:py-10"> | |||
| <div className="flex flex-col lg:flex-row gap-8"> | |||
| {/* Left Sidebar Filters */} | |||
| <aside className="lg:w-1/3 xl:w-1/4"> | |||
| <div className="sticky top-28 space-y-6"> | |||
| <div className="bg-white rounded-2xl border border-slate-200 p-6 shadow-sm"> | |||
| <h3 className="text-[#1a2b4b] text-xs font-black uppercase tracking-widest mb-6 flex items-center justify-between"> | |||
| Modify Quote | |||
| <span className="w-1.5 h-1.5 rounded-full bg-emerald-500"></span> | |||
| </h3> | |||
| <div className="space-y-6"> | |||
| {/* Tenure */} | |||
| <div className="p-3 bg-slate-50 rounded-xl border border-slate-100"> | |||
| <div className="flex items-center justify-between mb-1"> | |||
| <h4 className="text-[10px] font-black text-slate-400 uppercase tracking-widest">Policy Tenure</h4> | |||
| {!isEditingTenure && ( | |||
| <button | |||
| onClick={() => setIsEditingTenure(true)} | |||
| className="text-[#e31e24] hover:scale-110 transition-transform" | |||
| > | |||
| <svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z" /></svg> | |||
| </button> | |||
| )} | |||
| </div> | |||
| {!isEditingTenure ? ( | |||
| <p className="text-sm font-bold text-[#1a2b4b]">{tenure}</p> | |||
| ) : ( | |||
| <div className="flex flex-col gap-2 mt-2 animate-in fade-in slide-in-from-top-1"> | |||
| {(['1 Year', '2 Year', '3 Year'] as const).map((t) => ( | |||
| <button | |||
| key={t} | |||
| onClick={() => { | |||
| setTenure(t); | |||
| setIsEditingTenure(false); | |||
| }} | |||
| className={`w-full text-left px-3 py-2 rounded-lg text-xs font-bold transition-all border ${ | |||
| tenure === t | |||
| ? 'bg-white border-[#e31e24] text-[#e31e24] shadow-sm' | |||
| : 'bg-slate-100 border-slate-200 text-[#1a2b4b] hover:bg-white' | |||
| }`} | |||
| > | |||
| {t} | |||
| </button> | |||
| ))} | |||
| </div> | |||
| )} | |||
| </div> | |||
| {/* IDV Section */} | |||
| <div className="space-y-4"> | |||
| <div className="flex items-center justify-between"> | |||
| <h4 className="text-[10px] font-black text-slate-400 uppercase tracking-widest">Cover Value (IDV):</h4> | |||
| {!isEditingIDV && ( | |||
| <button | |||
| onClick={() => setIsEditingIDV(true)} | |||
| className="text-[#e31e24] text-[10px] font-black uppercase hover:underline" | |||
| > | |||
| Edit | |||
| </button> | |||
| )} | |||
| </div> | |||
| <p className="text-[10px] text-slate-400 leading-snug"> | |||
| The maximum value you will receive in case of total damage or theft. | |||
| </p> | |||
| <div className="pt-2"> | |||
| <input | |||
| type="range" | |||
| min={minIDV} | |||
| max={maxIDV} | |||
| value={currentIDV} | |||
| onChange={(e) => { setCustomIDV(parseInt(e.target.value)); setIdvType('custom'); }} | |||
| className="w-full h-1.5 bg-slate-100 rounded-full appearance-none cursor-pointer accent-[#1a2b4b]" | |||
| /> | |||
| <div className="flex justify-between text-[10px] font-bold text-slate-400 mt-1"> | |||
| <span>₹ {minIDV.toLocaleString()}</span> | |||
| <span>₹ {maxIDV.toLocaleString()}</span> | |||
| </div> | |||
| </div> | |||
| {!isEditingIDV ? ( | |||
| <div className="bg-indigo-50 border border-indigo-100 rounded-xl p-3 text-center"> | |||
| <p className="text-[10px] font-black text-indigo-700 uppercase"> | |||
| {idvType === 'lowest' ? 'Cheapest' : idvType === 'recommended' ? 'Recommended' : 'Custom'} IDV | |||
| </p> | |||
| <p className="text-sm font-black text-indigo-900">₹ {currentIDV.toLocaleString()}</p> | |||
| </div> | |||
| ) : ( | |||
| <div className="space-y-2 animate-in fade-in slide-in-from-top-1"> | |||
| <button | |||
| onClick={() => { setIdvType('lowest'); setIsEditingIDV(false); }} | |||
| className={`w-full text-left p-3 rounded-xl border transition-all ${idvType === 'lowest' ? 'border-[#e31e24] bg-red-50' : 'border-slate-100 bg-white hover:bg-slate-50'}`} | |||
| > | |||
| <p className="text-xs font-bold text-[#1a2b4b]">Lowest ₹ {minIDV.toLocaleString()}</p> | |||
| <p className="text-[9px] text-slate-500 font-medium">Cheapest premium</p> | |||
| </button> | |||
| <button | |||
| onClick={() => { setIdvType('recommended'); setIsEditingIDV(false); }} | |||
| className={`w-full text-left p-3 rounded-xl border transition-all ${idvType === 'recommended' ? 'border-[#e31e24] bg-red-50' : 'border-slate-100 bg-white hover:bg-slate-50'}`} | |||
| > | |||
| <p className="text-xs font-bold text-[#1a2b4b]">Recommended ₹ {maxIDV.toLocaleString()}</p> | |||
| <p className="text-[9px] text-slate-500 font-medium">Based on your bike age</p> | |||
| </button> | |||
| <div className={`p-3 rounded-xl border transition-all ${idvType === 'custom' ? 'border-[#e31e24] bg-red-50' : 'border-slate-100 bg-white'}`}> | |||
| <div className="flex items-center justify-between"> | |||
| <p className="text-xs font-bold text-[#1a2b4b]">Set your own</p> | |||
| <input | |||
| type="number" | |||
| min={minIDV} | |||
| max={maxIDV} | |||
| value={customIDV} | |||
| onChange={(e) => { | |||
| const val = parseInt(e.target.value); | |||
| if (!isNaN(val)) { setCustomIDV(val); setIdvType('custom'); } | |||
| }} | |||
| className="w-20 px-2 py-1 text-xs font-bold border rounded bg-white outline-none focus:border-[#e31e24]" | |||
| /> | |||
| </div> | |||
| <button | |||
| onClick={() => setIsEditingIDV(false)} | |||
| className="w-full mt-2 py-1 text-[9px] font-black text-[#e31e24] uppercase border border-[#e31e24]/20 rounded" | |||
| > | |||
| Apply Custom | |||
| </button> | |||
| </div> | |||
| </div> | |||
| )} | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <div className="bg-white rounded-2xl border border-slate-200 p-4 shadow-sm text-center"> | |||
| <p className="text-[10px] text-slate-400 font-medium">Looking for specific add-ons?</p> | |||
| <button className="text-[10px] font-black text-[#e31e24] uppercase mt-1 hover:underline">Customize Coverages</button> | |||
| </div> | |||
| </div> | |||
| </aside> | |||
| {/* Main Quotes List */} | |||
| <main className="lg:w-2/3 xl:w-3/4"> | |||
| <div className="mb-6 flex items-center justify-between"> | |||
| <h2 className="text-[#1a2b4b] font-bold"> | |||
| Found below <span className="text-[#e31e24]">Quotes</span> for your ride | |||
| </h2> | |||
| <div className="flex items-center gap-2"> | |||
| <span className="text-[10px] font-black text-slate-400 uppercase">Sort by:</span> | |||
| <select className="text-[11px] font-bold bg-transparent border-none focus:ring-0 cursor-pointer text-[#1a2b4b]"> | |||
| <option>Lowest Premium</option> | |||
| <option>Highest IDV</option> | |||
| </select> | |||
| </div> | |||
| </div> | |||
| <div className="space-y-4"> | |||
| {dummyQuotes.map(q => <QuoteCard key={q.id} quote={q} onBuy={() => onBuyNow(q)} />)} | |||
| </div> | |||
| <div className="mt-12 text-center p-8 border-2 border-dashed border-slate-200 rounded-3xl"> | |||
| <div className="inline-flex items-center gap-3 text-slate-400 text-sm font-medium"> | |||
| <div className="flex gap-1"> | |||
| <div className="w-1.5 h-1.5 rounded-full bg-slate-300 animate-bounce"></div> | |||
| <div className="w-1.5 h-1.5 rounded-full bg-slate-300 animate-bounce [animation-delay:0.2s]"></div> | |||
| <div className="w-1.5 h-1.5 rounded-full bg-slate-300 animate-bounce [animation-delay:0.4s]"></div> | |||
| </div> | |||
| Updating latest rates from insurers... | |||
| </div> | |||
| </div> | |||
| </main> | |||
| </div> | |||
| </div> | |||
| ); | |||
| }; | |||
| export default QuoteListPage; | |||
| @ -0,0 +1,43 @@ | |||
| import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom"; | |||
| import BasicDetails from "../BasicDetails"; | |||
| import QuoteListPage from "../Quotation/QuoteListPage"; | |||
| import ProposalPage from "../KYCDetails/ProposalPage"; | |||
| // import { | |||
| // BasicDetails | |||
| // } from "../Pages"; | |||
| // Routes config | |||
| const routes = [ | |||
| { | |||
| path: "/basic-details", | |||
| element: <BasicDetails />, | |||
| }, | |||
| { | |||
| path: "/quote-list", | |||
| element: <QuoteListPage />, | |||
| }, | |||
| { | |||
| path: "/kyc-details", | |||
| element: <ProposalPage />, | |||
| }, | |||
| ]; | |||
| const PageRoutes = () => { | |||
| return ( | |||
| <BrowserRouter> | |||
| <Routes> | |||
| <Route path="/" element={<Navigate to="/basic-details" replace />} /> | |||
| {routes?.map(({ path, element }, index) => ( | |||
| <Route | |||
| // key={index} | |||
| path={path} element={element} /> | |||
| ))} | |||
| </Routes> | |||
| </BrowserRouter> | |||
| ); | |||
| }; | |||
| export default PageRoutes; | |||
| @ -0,0 +1,257 @@ | |||
| import React, { useState } from 'react'; | |||
| import { InsuranceQuote, ProposalData, VehicleInfo } from '@/types'; | |||
| interface CheckoutPageProps { | |||
| quote: InsuranceQuote; | |||
| } | |||
| const CheckoutPage: React.FC<CheckoutPageProps & { vehicle: VehicleInfo }> = ({ quote, vehicle }) => { | |||
| const [isSubmitted, setIsSubmitted] = useState(false); | |||
| const [isPaid, setIsPaid] = useState(false); | |||
| const [proposal, setProposal] = useState<ProposalData>({ | |||
| pan: 'AATPU7693N', | |||
| name: 'Suketu Upadhyay', | |||
| dob: '1984-07-03', | |||
| phone: '9833604121', | |||
| email: 'Suketu123_Upadhyay@hotmail.com', | |||
| nomineeName: 'Vasudha Upadhyay', | |||
| nomineeDob: '1986-06-03', | |||
| nomineeRelationship: 'Spouse', | |||
| previousTPPolicyNo: '', | |||
| previousTPPolicyIssuer: '', | |||
| appointeeName: '', | |||
| appointeeRelationship: '', | |||
| isHypothecated: 'No', | |||
| bankName: '' | |||
| }); | |||
| const tax = Math.round(quote.premium * 0.18); | |||
| const basePrice = quote.premium; | |||
| const total = basePrice + tax; | |||
| const handleSubmit = (e: React.FormEvent) => { | |||
| e.preventDefault(); | |||
| setIsSubmitted(true); | |||
| window.scrollTo({ top: 0, behavior: 'smooth' }); | |||
| }; | |||
| const handlePay = () => { | |||
| // Simulating redirect to payment gateway and back | |||
| setIsPaid(true); | |||
| }; | |||
| return ( | |||
| <div className="relative min-h-screen"> | |||
| <div className={`container mx-auto px-4 py-8 space-y-6 transition-all duration-500 ${isPaid ? 'blur-sm pointer-events-none' : ''} animate-in fade-in duration-700`}> | |||
| {/* 1. High-Density Vehicle Header (Top) */} | |||
| <div className="bg-white rounded-2xl border border-slate-200 shadow-sm overflow-hidden"> | |||
| <div className="p-6 grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-3 xl:grid-cols-4 gap-x-12 gap-y-4"> | |||
| <HeaderItem label="MODEL:" value={vehicle.model} /> | |||
| <HeaderItem label="MAKE:" value={vehicle.make} /> | |||
| <HeaderItem label="FUEL_TYPE:" value={vehicle.fuelType} /> | |||
| <HeaderItem label="VARIANT:" value={vehicle.variant} /> | |||
| <HeaderItem label="REGISTERED_CITY:" value={vehicle.registeredCity} /> | |||
| <HeaderItem label="REGISTERED_DATE:" value={vehicle.registeredDate} /> | |||
| <HeaderItem label="CHASSIS_NUMBER:" value={vehicle.chassisNumber} /> | |||
| <HeaderItem label="ENGINE_NUMBER:" value={vehicle.engineNumber} /> | |||
| <HeaderItem label="PREVIOUS_POLICY_NUMBER:" value={vehicle.previousPolicyNumber} /> | |||
| <HeaderItem label="PREVIOUS_INSURER:" value={vehicle.previousInsurer} /> | |||
| <HeaderItem label="PREVIOUS_POLICY_END_DATE:" value={vehicle.previousPolicyEndDate} /> | |||
| <HeaderItem label="VEHICLE REG NO:" value={vehicle.regNo} /> | |||
| </div> | |||
| </div> | |||
| <div className="flex flex-col lg:flex-row gap-8"> | |||
| {/* 2. Full Proposal Form (Left) */} | |||
| <div className={`lg:w-2/3 bg-white rounded-3xl p-8 border border-slate-200 shadow-sm transition-opacity duration-500 ${isSubmitted ? 'opacity-80' : ''}`}> | |||
| <div className="grid grid-cols-1 md:grid-cols-2 gap-x-8 gap-y-5"> | |||
| <FormField label="Vehicle Reg No. *" value={vehicle.regNo} readOnly={true} /> | |||
| <FormField label="Chassis number *" value={vehicle.chassisNumber} readOnly={true} /> | |||
| <FormField label="Engine number *" value={vehicle.engineNumber} readOnly={true} /> | |||
| <FormField label="Previous policy No. *" value={vehicle.previousPolicyNumber} readOnly={isSubmitted} /> | |||
| <FormField label="Previous policy Issuer *" value={vehicle.previousInsurer} readOnly={isSubmitted} /> | |||
| <FormField label="Previous TP policy Issuer" value={proposal.previousTPPolicyIssuer || 'Previous TP policy Issuer'} placeholder readOnly={isSubmitted} /> | |||
| <FormField label="Previous TP policy No." value={proposal.previousTPPolicyNo || 'Previous TP policy No.'} placeholder readOnly={isSubmitted} /> | |||
| <FormField label="Nominee Name *" value={proposal.nomineeName} readOnly={isSubmitted} /> | |||
| <FormField label="Nominee DOB *" value={proposal.nomineeDob} type="date" readOnly={isSubmitted} /> | |||
| <FormField label="Nominee Relationship *" value={proposal.nomineeRelationship} isSelect readOnly={isSubmitted} /> | |||
| <FormField label="Appointee name" value={proposal.appointeeName || 'Appointee name'} placeholder readOnly={isSubmitted} /> | |||
| <FormField label="Appointee relationship with nominee" value={proposal.appointeeRelationship || 'Appointee relationship with nominee'} placeholder readOnly={isSubmitted} /> | |||
| <FormField label="Hypothecation/Loan flag" value={proposal.isHypothecated} isSelect readOnly={isSubmitted} /> | |||
| <FormField label="Bank name if hypothecated" value={proposal.bankName || 'Bank name if hypothecated'} placeholder readOnly={isSubmitted} /> | |||
| </div> | |||
| <div className="mt-12 flex justify-center"> | |||
| <button | |||
| onClick={handleSubmit} | |||
| disabled={isSubmitted} | |||
| className={`px-16 py-3 rounded-lg font-bold transition-all shadow-lg uppercase tracking-wide ${isSubmitted | |||
| ? 'bg-slate-400 cursor-not-allowed opacity-60 text-white' | |||
| : 'bg-[#2b458c]/80 hover:bg-[#1e3a8a] text-white' | |||
| }`} | |||
| > | |||
| {isSubmitted ? 'Submitted' : 'Submit'} | |||
| </button> | |||
| </div> | |||
| </div> | |||
| {/* 3. Summary Card (Right) */} | |||
| <div className="lg:w-1/3"> | |||
| <div className="bg-white rounded-3xl p-8 border border-slate-200 shadow-sm space-y-8 lg:sticky lg:top-28"> | |||
| <div className="flex items-center gap-4 py-2 border-b border-slate-50"> | |||
| <img src={quote.insurerLogo} alt={quote.insurerName} className="h-12 w-12 object-contain" /> | |||
| <div className="flex flex-col"> | |||
| <span className="text-[10px] font-black text-slate-400 uppercase tracking-widest mb-0.5">Insurance Partner</span> | |||
| <p className="text-[11px] font-bold text-[#1a2b4b] leading-tight">{quote.insurerName}</p> | |||
| </div> | |||
| </div> | |||
| <div className="space-y-4"> | |||
| <PriceDetail label="BASE PRICE" value={basePrice} /> | |||
| <PriceDetail label="CONVIENCE FEE" value={0} /> | |||
| <PriceDetail label="TAX" value={tax} /> | |||
| <PriceDetail label="PROCESSING FEE" value={0} /> | |||
| <div className="pt-6 border-t border-slate-100 flex justify-between items-center"> | |||
| <span className="text-[13px] font-bold text-[#1a2b4b]">Total</span> | |||
| <span className="text-xl font-black text-[#1a2b4b]">₹{total.toLocaleString()}</span> | |||
| </div> | |||
| </div> | |||
| {isSubmitted && ( | |||
| <div className="pt-4 animate-in slide-in-from-bottom-4 duration-500"> | |||
| <button | |||
| onClick={handlePay} | |||
| className="w-full bg-[#1e3a8a] hover:bg-[#162a63] text-white py-4 rounded-xl font-black text-sm shadow-xl transition-all hover:-translate-y-1" | |||
| > | |||
| Pay securely | |||
| </button> | |||
| </div> | |||
| )} | |||
| <div className="pt-2 text-center"> | |||
| <p className="text-[10px] text-slate-400 leading-relaxed font-medium"> | |||
| By clicking on '{isSubmitted ? 'Pay now' : 'Submit'}', I agree to the <a href="#" className="text-blue-500 font-bold hover:underline">terms & conditions</a> | |||
| </p> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| {/* 4. Success Modal Overlay */} | |||
| {isPaid && ( | |||
| <div className="fixed inset-0 z-[100] flex items-center justify-center bg-black/40 backdrop-blur-[2px] animate-in fade-in duration-300"> | |||
| <div className="bg-white rounded-[2rem] shadow-2xl w-full max-w-lg p-10 text-center space-y-8 animate-in zoom-in-95 duration-500"> | |||
| <div className="space-y-2"> | |||
| <h2 className="text-xl md:text-2xl font-bold text-[#1a2b4b]"> | |||
| Your policy purchased successfully ! | |||
| </h2> | |||
| </div> | |||
| <div className="flex justify-center"> | |||
| <div className="relative"> | |||
| <div className="w-24 h-24 rounded-full bg-emerald-100 flex items-center justify-center animate-pulse"> | |||
| <div className="w-20 h-20 rounded-full bg-[#22c55e] flex items-center justify-center shadow-lg shadow-emerald-200"> | |||
| <svg className="w-10 h-10 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |||
| <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="4" d="M5 13l4 4L19 7" /> | |||
| </svg> | |||
| </div> | |||
| </div> | |||
| {/* Decorative particles */} | |||
| <div className="absolute -top-2 -right-2 w-3 h-3 bg-emerald-400 rounded-full animate-ping"></div> | |||
| <div className="absolute top-1/2 -left-6 w-2 h-2 bg-emerald-300 rounded-full animate-ping delay-75"></div> | |||
| </div> | |||
| </div> | |||
| <div className="space-y-6"> | |||
| <p className="text-[#1a2b4b] font-bold text-sm"> | |||
| Policy No: <span className="text-slate-500">USGI/WEBAG/1126181/00/000</span> | |||
| </p> | |||
| <div className="space-y-4"> | |||
| <button className="w-full bg-[#2b458c] hover:bg-[#1e3a8a] text-white py-3.5 rounded-xl font-bold text-sm shadow-xl transition-all hover:scale-[1.02]"> | |||
| Download Policy | |||
| </button> | |||
| <button | |||
| onClick={() => window.location.reload()} | |||
| className="block w-full text-blue-500 font-bold text-sm hover:underline" | |||
| > | |||
| Back to Homepage | |||
| </button> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| )} | |||
| </div> | |||
| ); | |||
| }; | |||
| const HeaderItem = ({ label, value }: { label: string; value: string }) => ( | |||
| <div className="flex flex-col gap-0.5"> | |||
| <span className="text-[10px] font-medium text-slate-400">{label}</span> | |||
| <span className="text-[11px] font-bold text-[#1a2b4b] uppercase break-words"> | |||
| {value || '-'} | |||
| </span> | |||
| </div> | |||
| ); | |||
| const FormField = ({ | |||
| label, | |||
| value, | |||
| readOnly, | |||
| placeholder, | |||
| type = "text", | |||
| isSelect = false | |||
| }: { | |||
| label: string; | |||
| value?: string; | |||
| readOnly?: boolean; | |||
| placeholder?: boolean; | |||
| type?: string; | |||
| isSelect?: boolean; | |||
| }) => ( | |||
| <div className="space-y-1.5"> | |||
| <label className={`text-[11px] font-medium transition-colors ${readOnly ? 'text-slate-300' : 'text-[#1a2b4b]/60'}`}> | |||
| {label} | |||
| </label> | |||
| <div className="relative"> | |||
| <input | |||
| type={type} | |||
| defaultValue={value} | |||
| readOnly={readOnly} | |||
| className={`w-full px-4 py-2.5 rounded-lg border outline-none text-[12px] font-medium transition-all ${readOnly | |||
| ? 'bg-slate-50 border-slate-100 text-slate-400 cursor-not-allowed' | |||
| : 'bg-white border-slate-200 text-[#1a2b4b] focus:border-[#2b458c]' | |||
| } ${placeholder && !readOnly ? 'text-slate-300 italic' : ''} ${isSelect && !readOnly ? 'cursor-pointer' : '' | |||
| }`} | |||
| /> | |||
| {isSelect && !readOnly && ( | |||
| <div className="absolute right-3 top-1/2 -translate-y-1/2 pointer-events-none opacity-20"> | |||
| <svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M19 9l-7 7-7-7" /></svg> | |||
| </div> | |||
| )} | |||
| {type === 'date' && !readOnly && ( | |||
| <div className="absolute right-3 top-1/2 -translate-y-1/2 pointer-events-none opacity-20"> | |||
| <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" /></svg> | |||
| </div> | |||
| )} | |||
| </div> | |||
| </div> | |||
| ); | |||
| const PriceDetail = ({ label, value }: { label: string; value: number }) => ( | |||
| <div className="flex justify-between items-center"> | |||
| <span className="text-[11px] font-bold text-slate-400 tracking-tight">{label}</span> | |||
| <span className="text-[12px] font-bold text-[#1a2b4b]">₹{value.toLocaleString()}</span> | |||
| </div> | |||
| ); | |||
| export default CheckoutPage; | |||
| @ -0,0 +1,27 @@ | |||
| import { GoogleGenAI } from "@google/genai"; | |||
| // Standard service pattern to keep AI logic separate | |||
| export const getInsuranceInsight = async (formData: any) => { | |||
| const ai = new GoogleGenAI({ apiKey: process.env.API_KEY || '' }); | |||
| const prompt = ` | |||
| Based on the following two-wheeler insurance details, provide a short 1-sentence helpful advice to the user. | |||
| Details: | |||
| - Cover: ${formData.coverType} | |||
| - Claims made: ${formData.claimStatus} | |||
| - NCB: ${formData.ncb}% | |||
| Keep it professional, helpful, and encouraging. | |||
| `; | |||
| try { | |||
| const response = await ai.models.generateContent({ | |||
| model: 'gemini-3-flash-preview', | |||
| contents: prompt, | |||
| }); | |||
| return response.text; | |||
| } catch (error) { | |||
| console.error("Gemini Error:", error); | |||
| return null; | |||
| } | |||
| }; | |||
| @ -0,0 +1,29 @@ | |||
| { | |||
| "compilerOptions": { | |||
| "target": "ES2022", | |||
| "experimentalDecorators": true, | |||
| "useDefineForClassFields": false, | |||
| "module": "ESNext", | |||
| "lib": [ | |||
| "ES2022", | |||
| "DOM", | |||
| "DOM.Iterable" | |||
| ], | |||
| "skipLibCheck": true, | |||
| "types": [ | |||
| "node" | |||
| ], | |||
| "moduleResolution": "bundler", | |||
| "isolatedModules": true, | |||
| "moduleDetection": "force", | |||
| "allowJs": true, | |||
| "jsx": "react-jsx", | |||
| "paths": { | |||
| "@/*": [ | |||
| "./*" | |||
| ] | |||
| }, | |||
| "allowImportingTsExtensions": true, | |||
| "noEmit": true | |||
| } | |||
| } | |||
| @ -0,0 +1,76 @@ | |||
| export interface FormData { | |||
| registrationNumber: string; | |||
| previousPolicyNumber: string; | |||
| expiryDate: string; | |||
| coverType: 'comprehensive' | 'third-party' | 'own-damage'; | |||
| claimStatus: 'yes' | 'no'; | |||
| ncb: number; | |||
| } | |||
| export interface Benefit { | |||
| title: string; | |||
| description: string; | |||
| icon: string; | |||
| } | |||
| export interface QuoteFeature { | |||
| label: string; | |||
| value: string | number | boolean; | |||
| } | |||
| // export interface InsuranceQuote { | |||
| // id?: string; | |||
| // insurerName: string; | |||
| // insurerLogo: string; | |||
| // premium: number; | |||
| // idvValue?: number; | |||
| // features?: string[]; | |||
| // } | |||
| export interface Feature { | |||
| label: string; | |||
| value: string | number; | |||
| } | |||
| export interface InsuranceQuote { | |||
| id: string; | |||
| insurerName: string; | |||
| insurerLogo: string; | |||
| idvValue: number; | |||
| premium: number; | |||
| features: Feature[] | string[]; | |||
| } | |||
| export interface VehicleInfo { | |||
| model: string; | |||
| make: string; | |||
| fuelType: string; | |||
| variant: string; | |||
| registeredCity: string; | |||
| registeredDate: string; | |||
| chassisNumber: string; | |||
| engineNumber: string; | |||
| previousPolicyNumber: string; | |||
| previousInsurer: string; | |||
| previousPolicyEndDate: string; | |||
| regNo: string; | |||
| } | |||
| export interface ProposalData { | |||
| pan: string; | |||
| name: string; | |||
| dob: string; | |||
| phone: string; | |||
| email: string; | |||
| // Final Review Fields | |||
| nomineeName?: string; | |||
| nomineeDob?: string; | |||
| nomineeRelationship?: string; | |||
| appointeeName?: string; | |||
| appointeeRelationship?: string; | |||
| isHypothecated?: string; | |||
| bankName?: string; | |||
| previousTPPolicyIssuer?: string; | |||
| previousTPPolicyNo?: string; | |||
| } | |||
| @ -0,0 +1,23 @@ | |||
| import path from 'path'; | |||
| import { defineConfig, loadEnv } from 'vite'; | |||
| import react from '@vitejs/plugin-react'; | |||
| export default defineConfig(({ mode }) => { | |||
| const env = loadEnv(mode, '.', ''); | |||
| return { | |||
| server: { | |||
| port: 3000, | |||
| host: '0.0.0.0', | |||
| }, | |||
| plugins: [react()], | |||
| define: { | |||
| 'process.env.API_KEY': JSON.stringify(env.GEMINI_API_KEY), | |||
| 'process.env.GEMINI_API_KEY': JSON.stringify(env.GEMINI_API_KEY) | |||
| }, | |||
| resolve: { | |||
| alias: { | |||
| '@': path.resolve(__dirname, '.'), | |||
| } | |||
| } | |||
| }; | |||
| }); | |||
Powered by TurnKey Linux.