From 393fdfddd571c2294b27af4c8c1018a45b920936 Mon Sep 17 00:00:00 2001 From: vickytechkey Date: Sun, 9 Aug 2026 10:59:03 +0530 Subject: [PATCH] adding the new pages --- src/App.test.tsx | 86 +++- src/App.tsx | 1085 +++++++++++++++++++++++++++++++++++++++------- 2 files changed, 997 insertions(+), 174 deletions(-) diff --git a/src/App.test.tsx b/src/App.test.tsx index de6b1c0..b997f5f 100644 --- a/src/App.test.tsx +++ b/src/App.test.tsx @@ -49,17 +49,60 @@ describe('Supplier Portal Onboarding & Active Dashboard Tests', () => { const emailInput = screen.getByLabelText(/Business Email \*/i) const phoneInput = screen.getByLabelText(/Mobile Number \*/i) const passInput = screen.getByLabelText(/Create Password \*/i) - const taxInput = screen.getByLabelText(/GSTIN \/ Tax ID \*/i) fireEvent.change(emailInput, { target: { value: 'seller@example.com' } }) fireEvent.change(phoneInput, { target: { value: '9876543210' } }) fireEvent.change(passInput, { target: { value: 'password123' } }) - fireEvent.change(taxInput, { target: { value: '29AAAAA1111A1Z1' } }) + + const alertMock = vi.spyOn(window, 'alert').mockImplementation(() => {}) const registerBtn = screen.getByRole('button', { name: /Register Business/i }) fireEvent.click(registerBtn) - expect(screen.getByText('Step 2 of 3: Business Details')).toBeInTheDocument() + // Step 1: Contact Verification + expect(screen.getByText(/Step 1 of 3: Contact Verification/i)).toBeInTheDocument() + + const sendWhatsappBtn = screen.getByText(/Send WhatsApp OTP/i) + fireEvent.click(sendWhatsappBtn) + + const whatsappCodeInput = screen.getByPlaceholderText(/Enter 123456/i) + fireEvent.change(whatsappCodeInput, { target: { value: '123456' } }) + const verifyWhatsappBtn = screen.getByRole('button', { name: /Verify Code/i }) + fireEvent.click(verifyWhatsappBtn) + + const sendEmailBtn = screen.getByText(/Send Email OTP/i) + fireEvent.click(sendEmailBtn) + + // There are now multiple placeholder elements with "Enter 123456". Let's fetch the visible inputs. + const codeInputs = screen.getAllByPlaceholderText(/Enter 123456/i) + fireEvent.change(codeInputs[codeInputs.length - 1], { target: { value: '123456' } }) + const verifyButtons = screen.getAllByRole('button', { name: /Verify Code/i }) + fireEvent.click(verifyButtons[verifyButtons.length - 1]) + + const nextStep1Btn = screen.getByRole('button', { name: /Next Step: Identity Verification/i }) + fireEvent.click(nextStep1Btn) + + // Step 2: Tax & Identity Verification + expect(screen.getByText(/Step 2 of 3: Tax & Identity Verification/i)).toBeInTheDocument() + + const taxInput = screen.getByLabelText(/GSTIN Number \*/i) + fireEvent.change(taxInput, { target: { value: '29AAAAA1111A1Z1' } }) + const verifyGstinBtn = screen.getByRole('button', { name: /Verify GSTIN/i }) + fireEvent.click(verifyGstinBtn) + + // Upload files + const aadharInput = screen.getByLabelText(/Aadhaar Card Upload \*/i) + const panInput = screen.getByLabelText(/PAN Card Upload \*/i) + + // Trigger file changes + fireEvent.change(aadharInput, { target: { files: [{ name: 'aadhar_card.pdf' }] } }) + fireEvent.change(panInput, { target: { files: [{ name: 'pan_card.pdf' }] } }) + + const nextStep2Btn = screen.getByRole('button', { name: /Next Step: Store & Location/i }) + fireEvent.click(nextStep2Btn) + + // Step 3: Store Details & Location Map + expect(screen.getByText(/Step 3 of 3: Store & Pickup Location/i)).toBeInTheDocument() const storeNameInput = screen.getByLabelText(/Store Display Name \*/i) fireEvent.change(storeNameInput, { target: { value: 'Crafty Store' } }) @@ -67,22 +110,41 @@ describe('Supplier Portal Onboarding & Active Dashboard Tests', () => { const bioText = screen.getByLabelText(/About Your Craft \/ Business \*/i) fireEvent.change(bioText, { target: { value: 'Handmade pottery.' } }) - const streetInput = screen.getByPlaceholderText('Street Address') - const cityInput = screen.getByPlaceholderText('City') - const stateInput = screen.getByPlaceholderText('State') - const pincodeInput = screen.getByPlaceholderText('Pincode') + const streetInput = screen.getByLabelText(/Location in Text \(Address\) \*/i) + const cityInput = screen.getByLabelText(/City \*/i) + const pincodeInput = screen.getByLabelText(/Pincode \*/i) fireEvent.change(streetInput, { target: { value: '123 Loom Lane' } }) fireEvent.change(cityInput, { target: { value: 'Handloom City' } }) - fireEvent.change(stateInput, { target: { value: 'Karnataka' } }) fireEvent.change(pincodeInput, { target: { value: '560001' } }) - - const saveBtn = screen.getByRole('button', { name: /Save & Continue/i }) - fireEvent.click(saveBtn) - // Redirects directly to Dashboard Page + const submitBtn = screen.getByRole('button', { name: /Submit Supplier Profile/i }) + fireEvent.click(submitBtn) + + // Welcome Page & Setup Tour + expect(screen.getByText(/Welcome to Global Artisans Hub!/i)).toBeInTheDocument() + + // Complete tour steps + const startTourBtn = screen.getByRole('button', { name: /Start Tour/i }) + fireEvent.click(startTourBtn) + + const next1 = screen.getByRole('button', { name: /Next: Order Management/i }) + fireEvent.click(next1) + + const next2 = screen.getByRole('button', { name: /Next: Barcode Generation/i }) + fireEvent.click(next2) + + const next3 = screen.getByRole('button', { name: /Next: Earnings & Wallet/i }) + fireEvent.click(next3) + + const finishBtn = screen.getByRole('button', { name: /Launch Dashboard 🚀/i }) + fireEvent.click(finishBtn) + + // Redirects to Dashboard Page expect(screen.getByText('Performance Analytics')).toBeInTheDocument() expect(screen.getByText(/Total Sales Volume/i)).toBeInTheDocument() + + alertMock.mockRestore() }) it('logs in successfully and navigates dashboard tools', () => { diff --git a/src/App.tsx b/src/App.tsx index a61877f..5367b4f 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,8 +1,8 @@ import { useState } from 'react' import { CONFIG } from './config' -type Page = 'home' | 'about' | 'contact' | 'login' | 'signup' | 'profile-completion' | 'confirmation' | 'dashboard' -type DashboardTab = 'overview' | 'products' | 'orders' | 'returns' | 'wallet' | 'settings' +type Page = 'home' | 'about' | 'contact' | 'login' | 'signup' | 'profile-completion' | 'confirmation' | 'dashboard' | 'forgot-password' | 'login-otp' | 'welcome-tour' +type DashboardTab = 'overview' | 'products' | 'orders' | 'returns' | 'wallet' | 'settings' | 'barcode-generator' type DateFilter = 'year' | 'week' | 'day' | 'custom' interface Product { @@ -22,7 +22,7 @@ interface Order { quantity: number customer: string total: number - status: 'Ready to Ship test' | 'Shipped' | 'Delivered' | 'Cancelled' + status: 'Ready to Ship test' | 'Ready to Ship' | 'Shipped' | 'Delivered' | 'Cancelled' | 'Pending Acceptance' | 'Rejected' carrier: string tracking: string eta: string @@ -43,15 +43,31 @@ export default function App() { const [currentPage, setCurrentPage] = useState('home') const [activeTab, setActiveTab] = useState<'login' | 'register'>('login') + // Onboarding Status + const [isProfileComplete, setIsProfileComplete] = useState(false) + const [profileStep, setProfileStep] = useState(1) + // Registration / Onboarding Form States const [email, setEmail] = useState('') const [phone, setPhone] = useState('') const [password, setPassword] = useState('') - const [gstin, setGstin] = useState('29AAAAA1111A1Z1') - const [isGstinVerified, setIsGstinVerified] = useState(true) - const [isVerifyingGstin, setIsVerifyingGstin] = useState(false) + + // Step 1: Verification + const [phoneVerified, setPhoneVerified] = useState(false) + const [emailVerified, setEmailVerified] = useState(false) + const [phoneOtpSent, setPhoneOtpSent] = useState(false) + const [emailOtpSent, setEmailOtpSent] = useState(false) + const [enteredPhoneOtp, setEnteredPhoneOtp] = useState('') + const [enteredEmailOtp, setEnteredEmailOtp] = useState('') - // Profile Completion States + // Step 2: Business details (moved GSTIN here) + const [gstin, setGstin] = useState('29AAAAA1111A1Z1') + const [isGstinVerified, setIsGstinVerified] = useState(false) + const [isVerifyingGstin, setIsVerifyingGstin] = useState(false) + const [aadharFile, setAadharFile] = useState(null) + const [panFile, setPanFile] = useState(null) + + // Step 3: Store and Location details const [storeName, setStoreName] = useState('My Artisan Handloom') const [storeLogo, setStoreLogo] = useState(null) const [businessBio, setBusinessBio] = useState('Traditional weaving and local sustainable designs.') @@ -61,6 +77,24 @@ export default function App() { state: 'Karnataka', pincode: '560001' }) + const [mapCoordinates, setMapCoordinates] = useState({ lat: 12.9716, lng: 77.5946 }) + + // --- Password Reset Page States --- + const [resetEmail, setResetEmail] = useState('') + const [resetOtpSent, setResetOtpSent] = useState(false) + const [resetOtp, setResetOtp] = useState('') + const [resetPassword, setResetPassword] = useState('') + + // --- OTP Login States --- + const [otpLoginPhone, setOtpLoginPhone] = useState('') + const [otpLoginSent, setOtpLoginSent] = useState(false) + const [otpLoginCode, setOtpLoginCode] = useState('') + + // --- Barcode Generator States --- + const [barcodeProductSku, setBarcodeProductSku] = useState('') + const [barcodeFormat, setBarcodeFormat] = useState('CODE128') + const [barcodeValue, setBarcodeValue] = useState('') + const [barcodeGenerated, setBarcodeGenerated] = useState(false) // --- Active Dashboard States --- const [dashTab, setDashTab] = useState('overview') @@ -69,7 +103,11 @@ export default function App() { // Lists const [products, setProducts] = useState(CONFIG.dashboardData.initialProducts) - const [orders] = useState(CONFIG.dashboardData.initialOrders as Order[]) + const [orders, setOrders] = useState([ + ...CONFIG.dashboardData.initialOrders as Order[], + { id: 'ORD-8501', date: '2026-08-08', item: 'Brass Elephant Figurine', quantity: 1, customer: 'Bruce Wayne', total: 120.00, status: 'Pending Acceptance', carrier: 'Pending', tracking: 'Pending', eta: 'N/A' }, + { id: 'ORD-8502', date: '2026-08-08', item: 'Handwoven Indigo Shawl', quantity: 2, customer: 'Clark Kent', total: 170.00, status: 'Pending Acceptance', carrier: 'Pending', tracking: 'Pending', eta: 'N/A' } + ]) const [returns, setReturns] = useState(CONFIG.dashboardData.initialReturns as ReturnRequest[]) // Forms & Editing @@ -102,11 +140,7 @@ export default function App() { // GSTIN verification simulator const handleVerifyGstin = () => { if (!gstin.trim()) return - setIsVerifyingGstin(true) - setTimeout(() => { - setIsVerifyingGstin(false) - setIsGstinVerified(true) - }, 1200) + setIsGstinVerified(true) } // Handle Logo Upload simulation @@ -121,8 +155,17 @@ export default function App() { } } - const navigateTo = (page: Page) => { - setCurrentPage(page) + // Route/Navigation Guard: If profile is not complete, redirect to profile-completion + const navigateTo = (page: Page, forceComplete: boolean = false) => { + const publicPages: Page[] = ['home', 'about', 'contact', 'login', 'signup', 'forgot-password', 'login-otp'] + const complete = isProfileComplete || forceComplete + + if (!complete && !publicPages.includes(page) && page !== 'profile-completion') { + alert('Access Denied: Please complete your supplier profile first!') + setCurrentPage('profile-completion') + } else { + setCurrentPage(page) + } window.scrollTo({ top: 0, behavior: 'smooth' }) } @@ -133,12 +176,18 @@ export default function App() { const handleLoginSubmit = (e: React.FormEvent) => { e.preventDefault() - navigateTo('dashboard') + setIsProfileComplete(true) + navigateTo('dashboard', true) } const handleProfileSubmit = (e: React.FormEvent) => { e.preventDefault() - navigateTo('dashboard') + if (profileStep < 3) { + setProfileStep(prev => prev + 1) + } else { + setIsProfileComplete(true) + navigateTo('welcome-tour', true) + } } // --- Dashboard Logic Actions --- @@ -481,13 +530,13 @@ export default function App() { />
- { e.preventDefault(); alert('Password reset flow initiated.'); }}>Forgot Password? +
- +
) : ( @@ -529,18 +578,6 @@ export default function App() { required /> -
- - setGstin(e.target.value)} - required - /> -
@@ -549,146 +586,519 @@ export default function App() { )} - {/* PROFILE COMPLETION VIEW (Step 2 of 3) */} + {/* FORGOT PASSWORD PAGE */} + {currentPage === 'forgot-password' && ( +
+

Reset Your Password

+

+ Enter your registered business email or mobile number to receive a secure password reset link / OTP. +

+ + {!resetOtpSent ? ( +
{ e.preventDefault(); if (resetEmail.trim()) { setResetOtpSent(true); alert('Simulated Reset Code (123456) sent successfully!'); } }}> +
+ + setResetEmail(e.target.value)} + required + /> +
+ +
+ +
+
+ ) : ( +
{ e.preventDefault(); if (resetOtp === '123456') { setPassword(resetPassword); alert('Password updated successfully! Please login with your new password.'); setResetOtpSent(false); setResetEmail(''); setResetOtp(''); setResetPassword(''); navigateTo('login'); } else { alert('Invalid OTP. Please enter 123456'); } }}> +
+ + setResetOtp(e.target.value)} + required + /> +
+
+ + setResetPassword(e.target.value)} + required + /> +
+ +
+ )} +
+ )} + + {/* LOGIN WITH OTP PAGE */} + {currentPage === 'login-otp' && ( +
+

Login with OTP

+

+ Access your seller portal using a temporary verification code sent to your mobile. +

+ + {!otpLoginSent ? ( +
{ e.preventDefault(); if (otpLoginPhone.trim()) { setOtpLoginSent(true); alert('Simulated Login Code (123456) sent to mobile!'); } }}> +
+ + setOtpLoginPhone(e.target.value)} + required + /> +
+ +
+ +
+
+ ) : ( +
{ e.preventDefault(); if (otpLoginCode === '123456') { setOtpLoginSent(false); setOtpLoginPhone(''); setOtpLoginCode(''); if (!isProfileComplete) { navigateTo('profile-completion'); } else { navigateTo('dashboard'); } } else { alert('Invalid OTP. Please enter 123456'); } }}> +
+ + setOtpLoginCode(e.target.value)} + required + /> +
+ +
+ )} +
+ )} {currentPage === 'profile-completion' && (
- Step 2 of 3: Business Details + Step {profileStep} of 3: { + profileStep === 1 ? 'Contact Verification' : + profileStep === 2 ? 'Tax & Identity Verification' : + 'Store & Pickup Location' + }
-
+
-

Complete Your Supplier Profile

+

Complete Your Supplier Profile

-
- - setStoreName(e.target.value)} - required - /> -
+ {profileStep === 1 && ( +
+

Step 1: Verify Contacts

+ + {/* Phone/WhatsApp Verification */} +
+ +
+ setPhone(e.target.value)} + disabled={phoneVerified} + /> + +
+ + {phoneOtpSent && !phoneVerified && ( +
+ setEnteredPhoneOtp(e.target.value)} + style={{ maxWidth: '150px' }} + /> + +
+ )} -
- -
-
- {storeLogo ? ( - Logo preview - ) : ( - 🖼️ + {phoneVerified && ( +
+ ✓ Mobile & WhatsApp Verified +
)}
- -
-
-
- - -
+ {/* Email Verification */} +
+ +
+ setEmail(e.target.value)} + disabled={emailVerified} + /> + +
+ + {emailOtpSent && !emailVerified && ( +
+ setEnteredEmailOtp(e.target.value)} + style={{ maxWidth: '150px' }} + /> + +
+ )} -
- -
- setAddress({ ...address, street: e.target.value })} - required - /> - setAddress({ ...address, city: e.target.value })} - required - /> -
-
- setAddress({ ...address, state: e.target.value })} - required - /> - setAddress({ ...address, pincode: e.target.value })} - required - /> -
-
+ {emailVerified && ( +
+ ✓ Email Verified +
+ )} +
-
- -
- setGstin(e.target.value)} - required - />
- {isGstinVerified && ( -

- GSTIN successfully verified with government registry. -

- )} -
+ )} -
- - -
+ {profileStep === 2 && ( +
+

Step 2: GSTIN, PAN & Aadhaar

+ + {/* GSTIN verification */} +
+ +
+ setGstin(e.target.value)} + required + /> + +
+ {isGstinVerified && ( +

+ GSTIN successfully verified with government registry. +

+ )} +
+ + {/* Aadhaar Upload */} +
+ +
+ { + if (e.target.files?.[0]) { + setAadharFile(e.target.files[0].name); + alert(`Aadhaar card uploaded: ${e.target.files[0].name}`); + } + }} + style={{ display: 'none' }} + /> + + + {aadharFile ? `Selected: ${aadharFile}` : 'No file uploaded yet'} + +
+
+ + {/* PAN Upload */} +
+ +
+ { + if (e.target.files?.[0]) { + setPanFile(e.target.files[0].name); + alert(`PAN card uploaded: ${e.target.files[0].name}`); + } + }} + style={{ display: 'none' }} + /> + + + {panFile ? `Selected: ${panFile}` : 'No file uploaded yet'} + +
+
+ +
+ + +
+
+ )} + + {profileStep === 3 && ( +
+

Step 3: Store Details & Location Map

+ +
+ + setStoreName(e.target.value)} + required + /> +
+ +
+ + +
+ + {/* Interactive Location Map Picker */} +
+ +
{ + const rect = e.currentTarget.getBoundingClientRect(); + const x = Math.round(e.clientX - rect.left); + const y = Math.round(e.clientY - rect.top); + setMapCoordinates({ lat: Number((12.9 + y * 0.001).toFixed(4)), lng: Number((77.5 + x * 0.001).toFixed(4)) }); + setAddress(prev => ({ + ...prev, + street: `Plot ${x}, Sector ${Math.round(y/10)}, Handicraft Park` + })); + alert(`Location Pinned: Lat: ${(12.9 + y * 0.001).toFixed(4)}, Lng: ${(77.5 + x * 0.001).toFixed(4)}`); + }} + > + {/* Map Marker Pin */} +
+ 📍 +
+
+ Coordinates: Lat {mapCoordinates.lat}, Lng {mapCoordinates.lng} (Click map to change pin) +
+ Click anywhere on the map grid to pin location +
+
+ +
+ + setAddress({ ...address, street: e.target.value })} + required + /> +
+ +
+
+ + setAddress({ ...address, city: e.target.value })} + required + /> +
+
+ + setAddress({ ...address, pincode: e.target.value })} + required + /> +
+
+ +
+ + +
+
+ )}
)} @@ -749,6 +1159,11 @@ export default function App() {
)} + {/* WELCOME PAGE & SETUP TOUR FOR NEW SELLERS */} + {currentPage === 'welcome-tour' && ( + navigateTo('dashboard')} storeName={storeName} /> + )} + ) : ( /* --- FULL SERVICE SUPPLIER ACTIVE DASHBOARD PAGE --- */ @@ -800,6 +1215,14 @@ export default function App() { > 💼 Wallet & Payouts + +
  • +
  • +
  • +
    document.getElementById('bulk-file-pick')?.click()}> - {isParsingBulk ? 'Processing bulk data...' : '📥 Click to select CSV Template file for bulk upload'} + {isParsingBulk ? 'Processing bulk data...' : '📂 Click or drop your populated template file here to import'}
    {bulkLog.length > 0 && ( -
    +

    Import log:

    {bulkLog.map((log, i) =>
    {log}
    )}
    @@ -1042,7 +1525,7 @@ export default function App() { Customer Total Status - Logistics & Carrier Details + Logistics & Carrier Details / Actions @@ -1055,18 +1538,58 @@ export default function App() { {o.customer} ${o.total.toFixed(2)} - + {o.status} -
    -
    Carrier: {o.carrier}
    -
    Tracking: {o.tracking}
    -
    ETA / Delivery: {o.eta}
    -
    + {o.status === 'Pending Acceptance' ? ( +
    + + +
    + ) : ( +
    +
    Carrier: {o.carrier}
    +
    Tracking: {o.tracking}
    +
    ETA / Delivery: {o.eta}
    +
    + )} ))} @@ -1076,6 +1599,157 @@ export default function App() {
    )} + {dashTab === 'barcode-generator' && ( +
    +

    Product Barcode Generator

    +

    + Generate and print industry-standard barcodes for your product inventory to track shipments, sales, and warehouse stock. +

    + +
    +
    +

    Barcode Configuration

    + +
    + + +
    + +
    + + +
    + +
    + + { setBarcodeValue(e.target.value); setBarcodeGenerated(false); }} + placeholder="Enter alphanumeric identifier" + /> +
    + + +
    + +
    + {barcodeGenerated && barcodeValue ? ( +
    +

    Generated Barcode Preview

    + + {/* Beautiful CSS Mock Barcode */} +
    +
    + {products.find(p => p.sku === barcodeProductSku)?.title || 'Custom Identifier'} +
    + + {/* Barcode lines */} +
    + {/* Simple pseudorandom pattern generator based on barcodeValue string */} + {Array.from({ length: 48 }).map((_, idx) => { + const hash = barcodeValue.split('').reduce((acc, char) => acc + char.charCodeAt(0), idx); + const isBlack = (hash * (idx + 7)) % 3 !== 0; + const width = (idx % 5 === 0) ? '5px' : (idx % 3 === 0) ? '3px' : '1px'; + return ( +
    + ); + })} +
    +
    + {barcodeValue.toUpperCase()} +
    +
    + +
    + + +
    +
    + ) : ( +
    + 🏷️ +

    Enter configuration details and click generate to render visual barcode label.

    +
    + )} +
    +
    +
    + )} + {/* RETURNS MANAGEMENT TAB */} {dashTab === 'returns' && (
    @@ -1351,3 +2025,90 @@ export default function App() { ) } + +function WelcomeTourWizard({ onComplete, storeName }: { onComplete: () => void; storeName: string }) { + const [tourStep, setTourStep] = useState(1) + const steps = [ + { + title: "Welcome to Global Artisans Hub! 🎉", + description: `Congratulations ${storeName}! Your supplier profile is verified and complete. Let's take a 1-minute tour to get you familiar with your active workspace so you can begin selling immediately.`, + icon: "🎉", + action: "Start Tour" + }, + { + title: "📦 Products & Inventory Management", + description: "Under the 'Manage Products' tab, you can add new product listings, edit stock values, and perform bulk uploads using our structured Excel template.", + icon: "📦", + action: "Next: Order Management" + }, + { + title: "🚚 Order Acceptance Control", + description: "When customers place orders, they arrive in your queue. You must review and Accept or Reject each order from the 'Orders & Transit' tab before shipping them.", + icon: "🚚", + action: "Next: Barcode Generation" + }, + { + title: "🏷️ Barcode Identification & Tracking", + description: "Track your sales in real-time. Use the 'Barcode Generator' tab to create unique visual barcodes for each product SKU. You can download and print them easily.", + icon: "🏷️", + action: "Next: Earnings & Wallet" + }, + { + title: "💼 Wallet & Payout Withdrawals", + description: "Monitor outstanding payouts and withdraw your earnings directly to your bank account anytime. Keep track of transaction receipts directly inside the Wallet tab.", + icon: "💼", + action: "Launch Dashboard 🚀" + } + ] + + const current = steps[tourStep - 1] + + return ( +
    +
    {current.icon}
    +

    {current.title}

    +

    + {current.description} +

    + +
    + {steps.map((_, idx) => ( +
    + ))} +
    + +
    + {tourStep > 1 ? ( + + ) : ( +
    + )} + +
    +
    + ) +}