2114 lines
98 KiB
TypeScript
2114 lines
98 KiB
TypeScript
import { useState } from 'react'
|
||
import { CONFIG } from './config'
|
||
|
||
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 {
|
||
id: string
|
||
title: string
|
||
category: string
|
||
price: number
|
||
stock: number
|
||
sku: string
|
||
image: string
|
||
}
|
||
|
||
interface Order {
|
||
id: string
|
||
date: string
|
||
item: string
|
||
quantity: number
|
||
customer: string
|
||
total: number
|
||
status: 'Ready to Ship test' | 'Ready to Ship' | 'Shipped' | 'Delivered' | 'Cancelled' | 'Pending Acceptance' | 'Rejected'
|
||
carrier: string
|
||
tracking: string
|
||
eta: string
|
||
}
|
||
|
||
interface ReturnRequest {
|
||
id: string
|
||
orderId: string
|
||
customer: string
|
||
item: string
|
||
reason: string
|
||
status: 'Pending Approval' | 'Approved' | 'Rejected' | 'In Transit'
|
||
image: string
|
||
returningTracking: string
|
||
}
|
||
|
||
export default function App() {
|
||
const [currentPage, setCurrentPage] = useState<Page>('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('')
|
||
|
||
// 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('')
|
||
|
||
// 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<string | null>(null)
|
||
const [panFile, setPanFile] = useState<string | null>(null)
|
||
|
||
// Step 3: Store and Location details
|
||
const [storeName, setStoreName] = useState('My Artisan Handloom')
|
||
const [storeLogo, setStoreLogo] = useState<string | null>(null)
|
||
const [businessBio, setBusinessBio] = useState('Traditional weaving and local sustainable designs.')
|
||
const [address, setAddress] = useState({
|
||
street: '123 Handloom Lane',
|
||
city: 'Textile Town',
|
||
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<DashboardTab>('overview')
|
||
const [dateFilter, setDateFilter] = useState<DateFilter>('year')
|
||
const [customDates, setCustomDates] = useState({ start: '2026-06-01', end: '2026-06-15' })
|
||
|
||
// Lists
|
||
const [products, setProducts] = useState<Product[]>(CONFIG.dashboardData.initialProducts)
|
||
const [orders, setOrders] = useState<Order[]>([
|
||
...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<ReturnRequest[]>(CONFIG.dashboardData.initialReturns as ReturnRequest[])
|
||
|
||
// Forms & Editing
|
||
const [productForm, setProductForm] = useState({
|
||
id: '',
|
||
title: '',
|
||
category: 'Apparel',
|
||
price: 0,
|
||
stock: 0,
|
||
sku: '',
|
||
image: 'https://images.unsplash.com/photo-1544022613-e87ca75a784a?auto=format&fit=crop&q=80&w=100'
|
||
})
|
||
const [isEditingProduct, setIsEditingProduct] = useState(false)
|
||
|
||
// Payout outstanding states
|
||
const [wallet, setWallet] = useState({
|
||
outstanding: 850.00,
|
||
withdrawn: 1250.00,
|
||
history: [
|
||
{ id: 'TX-9031', date: '2026-08-01', amount: 500.00, status: 'Transferred' },
|
||
{ id: 'TX-9022', date: '2026-07-15', amount: 750.00, status: 'Transferred' }
|
||
]
|
||
})
|
||
const [withdrawAmount, setWithdrawAmount] = useState('')
|
||
|
||
// Bulk Upload simulations
|
||
const [bulkLog, setBulkLog] = useState<string[]>([])
|
||
const [isParsingBulk, setIsParsingBulk] = useState(false)
|
||
|
||
// GSTIN verification simulator
|
||
const handleVerifyGstin = () => {
|
||
if (!gstin.trim()) return
|
||
setIsGstinVerified(true)
|
||
}
|
||
|
||
// Handle Logo Upload simulation
|
||
const handleLogoChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||
const file = e.target.files?.[0]
|
||
if (file) {
|
||
const reader = new FileReader()
|
||
reader.onloadend = () => {
|
||
setStoreLogo(reader.result as string)
|
||
}
|
||
reader.readAsDataURL(file)
|
||
}
|
||
}
|
||
|
||
// 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' })
|
||
}
|
||
|
||
const handleRegisterSubmit = (e: React.FormEvent) => {
|
||
e.preventDefault()
|
||
navigateTo('profile-completion')
|
||
}
|
||
|
||
const handleLoginSubmit = (e: React.FormEvent) => {
|
||
e.preventDefault()
|
||
setIsProfileComplete(true)
|
||
navigateTo('dashboard', true)
|
||
}
|
||
|
||
const handleProfileSubmit = (e: React.FormEvent) => {
|
||
e.preventDefault()
|
||
if (profileStep < 3) {
|
||
setProfileStep(prev => prev + 1)
|
||
} else {
|
||
setIsProfileComplete(true)
|
||
navigateTo('welcome-tour', true)
|
||
}
|
||
}
|
||
|
||
// --- Dashboard Logic Actions ---
|
||
|
||
// Product creation/modification
|
||
const handleSaveProduct = (e: React.FormEvent) => {
|
||
e.preventDefault()
|
||
if (isEditingProduct) {
|
||
setProducts(products.map(p => p.id === productForm.id ? { ...productForm, price: Number(productForm.price), stock: Number(productForm.stock) } : p))
|
||
setIsEditingProduct(false)
|
||
alert('Product modified successfully!')
|
||
} else {
|
||
const newProd: Product = {
|
||
...productForm,
|
||
id: String(products.length + 1),
|
||
price: Number(productForm.price),
|
||
stock: Number(productForm.stock),
|
||
sku: productForm.sku || `PROD-${Date.now().toString().slice(-6)}`
|
||
}
|
||
setProducts([...products, newProd])
|
||
alert('Product uploaded successfully!')
|
||
}
|
||
// reset form
|
||
setProductForm({ id: '', title: '', category: 'Apparel', price: 0, stock: 0, sku: '', image: 'https://images.unsplash.com/photo-1544022613-e87ca75a784a?auto=format&fit=crop&q=80&w=100' })
|
||
}
|
||
|
||
const handleEditClick = (p: Product) => {
|
||
setProductForm(p)
|
||
setIsEditingProduct(true)
|
||
}
|
||
|
||
const handleDeleteProduct = (id: string) => {
|
||
if (confirm('Are you sure you want to delete this listing?')) {
|
||
setProducts(products.filter(p => p.id !== id))
|
||
}
|
||
}
|
||
|
||
// Bulk parser simulation
|
||
const handleBulkUploadSimulate = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||
if (!e.target.files?.length) return
|
||
setIsParsingBulk(true)
|
||
setBulkLog(['Parsing CSV file template...', 'Validating rows against inventory Schema...'])
|
||
|
||
setTimeout(() => {
|
||
const parsedProducts: Product[] = [
|
||
{ id: String(products.length + 1), title: 'Hand-Carved Walnut Bowl', category: 'Kitchenware', price: 65.00, stock: 15, sku: 'BOWL-WAL-12', image: 'https://images.unsplash.com/photo-1610701596007-11502861dcfa?auto=format&fit=crop&q=80&w=100' },
|
||
{ id: String(products.length + 2), title: 'Organic Cotton Tablecloth', category: 'Linens', price: 48.00, stock: 30, sku: 'LINE-COT-15', image: 'https://images.unsplash.com/photo-1603006905003-be475563bc59?auto=format&fit=crop&q=80&w=100' }
|
||
]
|
||
setProducts(prev => [...prev, ...parsedProducts])
|
||
setBulkLog(prev => [
|
||
...prev,
|
||
'Row 1: Verified SKU BOWL-WAL-12 - Added',
|
||
'Row 2: Verified SKU LINE-COT-15 - Added',
|
||
'Successfully added 2 new listings bulk!'
|
||
])
|
||
setIsParsingBulk(false)
|
||
}, 1500)
|
||
}
|
||
|
||
// Wallet outstanding requests
|
||
const handleWithdrawRequest = (e: React.FormEvent) => {
|
||
e.preventDefault()
|
||
const amount = Number(withdrawAmount)
|
||
if (isNaN(amount) || amount <= 0) {
|
||
alert('Please enter a valid amount')
|
||
return
|
||
}
|
||
if (amount > wallet.outstanding) {
|
||
alert('Amount exceeds outstanding ready payout balance')
|
||
return
|
||
}
|
||
|
||
const txId = `TX-${Math.floor(1000 + Math.random() * 9000)}`
|
||
const newTx = {
|
||
id: txId,
|
||
date: new Date().toISOString().split('T')[0],
|
||
amount: amount,
|
||
status: 'Transferred'
|
||
}
|
||
|
||
setWallet({
|
||
outstanding: wallet.outstanding - amount,
|
||
withdrawn: wallet.withdrawn + amount,
|
||
history: [newTx, ...wallet.history]
|
||
})
|
||
setWithdrawAmount('')
|
||
alert(`Payout of $${amount} successfully transferred!`)
|
||
}
|
||
|
||
// Returns actions
|
||
const handleReturnAction = (id: string, action: 'Approved' | 'Rejected') => {
|
||
setReturns(returns.map(ret => {
|
||
if (ret.id === id) {
|
||
return {
|
||
...ret,
|
||
status: action === 'Approved' ? 'In Transit' : 'Rejected'
|
||
}
|
||
}
|
||
return ret
|
||
}))
|
||
alert(`Return request ${action.toLowerCase()}!`)
|
||
}
|
||
|
||
const selectedMetrics = CONFIG.dashboardData.filters[dateFilter]
|
||
|
||
return (
|
||
<>
|
||
{/* Dynamic Header */}
|
||
<header className="app-header">
|
||
<div className="logo-container" onClick={() => navigateTo('home')}>
|
||
<div className="logo-icon">{CONFIG.logoLetter}</div>
|
||
<span className="logo-text">{CONFIG.companyName}</span>
|
||
</div>
|
||
<nav className="nav-links">
|
||
<button
|
||
className={`nav-link ${currentPage === 'home' ? 'active' : ''}`}
|
||
onClick={() => navigateTo('home')}
|
||
>
|
||
Platform
|
||
</button>
|
||
<button
|
||
className="nav-link"
|
||
onClick={() => navigateTo('home')}
|
||
>
|
||
Pricing
|
||
</button>
|
||
<button
|
||
className={`nav-link ${currentPage === 'about' ? 'active' : ''}`}
|
||
onClick={() => navigateTo('about')}
|
||
>
|
||
Success Stories
|
||
</button>
|
||
<button
|
||
className={`nav-link ${currentPage === 'contact' ? 'active' : ''}`}
|
||
onClick={() => navigateTo('contact')}
|
||
>
|
||
Support
|
||
</button>
|
||
</nav>
|
||
<div className="nav-buttons">
|
||
{currentPage === 'dashboard' ? (
|
||
<button className="btn btn-secondary" onClick={() => navigateTo('home')}>
|
||
Logout
|
||
</button>
|
||
) : (
|
||
<>
|
||
<button className="btn btn-secondary" onClick={() => { setActiveTab('login'); navigateTo('login'); }}>
|
||
Login
|
||
</button>
|
||
<button className="btn btn-primary" onClick={() => { setActiveTab('register'); navigateTo('signup'); }}>
|
||
Get Started
|
||
</button>
|
||
</>
|
||
)}
|
||
</div>
|
||
</header>
|
||
|
||
{/* Main Content Area */}
|
||
{currentPage !== 'dashboard' ? (
|
||
<main className={`main-content ${currentPage === 'home' ? 'full-width' : ''}`}>
|
||
|
||
{/* HOMEPAGE VIEW */}
|
||
{currentPage === 'home' && (
|
||
<div>
|
||
<section className="hero-section">
|
||
<div className="hero-content">
|
||
<h1>
|
||
{CONFIG.hero.title.split('. ')[0]}.<br />
|
||
{CONFIG.hero.title.split('. ')[1]}
|
||
</h1>
|
||
<p>
|
||
{CONFIG.hero.subtitle}
|
||
</p>
|
||
<div style={{ display: 'flex', gap: '1rem' }}>
|
||
<button className="btn btn-dark" onClick={() => { setActiveTab('register'); navigateTo('signup'); }}>
|
||
Get Started Today
|
||
</button>
|
||
<button className="btn btn-outline-dark" onClick={() => navigateTo('about')}>
|
||
Learn More
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<div className="hero-image-wrapper">
|
||
<img
|
||
src={CONFIG.hero.image}
|
||
alt="Artisans Crafting"
|
||
className="hero-img"
|
||
/>
|
||
</div>
|
||
</section>
|
||
|
||
<section className="features-section">
|
||
<div className="features-grid">
|
||
{CONFIG.features.map((feature, i) => (
|
||
<div className="feature-card" key={i}>
|
||
<div className="feature-icon-wrapper">{feature.icon}</div>
|
||
<h3>{feature.title}</h3>
|
||
<p>{feature.description}</p>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</section>
|
||
|
||
<section className="featured-artisan-section">
|
||
<div className="artisan-card">
|
||
<img
|
||
src={CONFIG.featuredArtisan.avatar}
|
||
alt="Featured Artisan"
|
||
className="artisan-avatar"
|
||
/>
|
||
<div className="artisan-info">
|
||
<h4>Featured Artisan</h4>
|
||
<p className="artisan-quote">
|
||
{CONFIG.featuredArtisan.quote}
|
||
</p>
|
||
<p style={{ fontWeight: 'bold' }}>- {CONFIG.featuredArtisan.name} ({CONFIG.featuredArtisan.role})</p>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
</div>
|
||
)}
|
||
|
||
{/* ABOUT US VIEW */}
|
||
{currentPage === 'about' && (
|
||
<div>
|
||
<div className="about-mission">
|
||
<h2>{CONFIG.about.missionTitle}</h2>
|
||
<p>
|
||
{CONFIG.about.missionDescription}
|
||
</p>
|
||
</div>
|
||
|
||
<div className="team-section">
|
||
<h3>Meet Our Leaders</h3>
|
||
<div className="team-grid">
|
||
{CONFIG.about.team.map((member, i) => (
|
||
<div className="team-card" key={i}>
|
||
<img src={member.avatar} alt={member.name} className="team-avatar" />
|
||
<h4>{member.name}</h4>
|
||
<p>{member.role}</p>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* CONTACT US VIEW */}
|
||
{currentPage === 'contact' && (
|
||
<div className="contact-layout">
|
||
<div className="form-card" style={{ margin: 0, maxWidth: '100%' }}>
|
||
<h2 className="form-card-title" style={{ textAlign: 'left' }}>Get in Touch</h2>
|
||
<form onSubmit={(e) => { e.preventDefault(); alert('Thank you for contacting us! We will get back to you shortly.'); navigateTo('home'); }}>
|
||
<div className="form-group">
|
||
<label htmlFor="contact-name">Full Name *</label>
|
||
<input id="contact-name" type="text" className="form-control" placeholder="Enter your full name" required />
|
||
</div>
|
||
<div className="form-group">
|
||
<label htmlFor="contact-business">Business Name</label>
|
||
<input id="contact-business" type="text" className="form-control" placeholder="Enter your business name" />
|
||
</div>
|
||
<div className="form-group">
|
||
<label htmlFor="contact-email">Email Address *</label>
|
||
<input id="contact-email" type="email" className="form-control" placeholder="name@example.com" required />
|
||
</div>
|
||
<div className="form-group">
|
||
<label htmlFor="contact-msg">Message *</label>
|
||
<textarea id="contact-msg" className="form-control" rows={4} placeholder="How can we help you?" required></textarea>
|
||
</div>
|
||
<button type="submit" className="btn btn-dark" style={{ width: '100%', marginTop: '1rem' }}>
|
||
Submit Inquiry
|
||
</button>
|
||
</form>
|
||
</div>
|
||
|
||
<div className="contact-info-panel">
|
||
<div className="info-item">
|
||
<h4>Address</h4>
|
||
<p dangerouslySetInnerHTML={{ __html: CONFIG.address.replace(', ', ',<br />') }}></p>
|
||
</div>
|
||
<div className="info-item">
|
||
<h4>Support Email</h4>
|
||
<p>{CONFIG.supportEmail}</p>
|
||
</div>
|
||
<div className="info-item">
|
||
<h4>Partner Hotline</h4>
|
||
<p>{CONFIG.supportPhone}</p>
|
||
</div>
|
||
|
||
<div className="map-placeholder">
|
||
📍 Interactive Map Preview
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* AUTHENTICATION VIEW (LOGIN & SIGNUP) */}
|
||
{(currentPage === 'login' || currentPage === 'signup') && (
|
||
<div className="form-card">
|
||
<div className="tab-container">
|
||
<button
|
||
className={`tab-btn ${activeTab === 'login' ? 'active' : ''}`}
|
||
onClick={() => { setActiveTab('login'); navigateTo('login'); }}
|
||
>
|
||
Login
|
||
</button>
|
||
<button
|
||
className={`tab-btn ${activeTab === 'register' ? 'active' : ''}`}
|
||
onClick={() => { setActiveTab('register'); navigateTo('signup'); }}
|
||
>
|
||
Register
|
||
</button>
|
||
</div>
|
||
|
||
{activeTab === 'login' ? (
|
||
<form onSubmit={handleLoginSubmit}>
|
||
<h2 className="form-card-title">Supplier Portal Access</h2>
|
||
<div className="form-group">
|
||
<label htmlFor="login-email">Business Email</label>
|
||
<input
|
||
id="login-email"
|
||
type="email"
|
||
className="form-control"
|
||
placeholder="Enter your email"
|
||
value={email}
|
||
onChange={(e) => setEmail(e.target.value)}
|
||
required
|
||
/>
|
||
</div>
|
||
<div className="form-group">
|
||
<label htmlFor="login-password">Password</label>
|
||
<input
|
||
id="login-password"
|
||
type="password"
|
||
className="form-control"
|
||
placeholder="Enter your password"
|
||
value={password}
|
||
onChange={(e) => setPassword(e.target.value)}
|
||
required
|
||
/>
|
||
</div>
|
||
<div style={{ textAlign: 'right', marginBottom: '1.5rem' }}>
|
||
<button type="button" className="nav-link" style={{ color: 'var(--text-muted)', fontSize: '0.85rem', textDecoration: 'none', background: 'none', border: 'none', cursor: 'pointer' }} onClick={(e) => { e.preventDefault(); navigateTo('forgot-password'); }}>Forgot Password?</button>
|
||
</div>
|
||
<button type="submit" className="btn btn-primary" style={{ width: '100%', padding: '0.75rem' }}>
|
||
Secure Login
|
||
</button>
|
||
<div style={{ textAlign: 'center', marginTop: '1.5rem', fontSize: '0.9rem' }}>
|
||
<button type="button" className="nav-link" style={{ color: 'var(--primary)', fontWeight: 'bold', background: 'none', border: 'none', cursor: 'pointer' }} onClick={() => navigateTo('login-otp')}>Login with OTP</button>
|
||
</div>
|
||
</form>
|
||
) : (
|
||
<form onSubmit={handleRegisterSubmit}>
|
||
<h2 className="form-card-title">Create Your Supplier Account</h2>
|
||
<div className="form-group">
|
||
<label htmlFor="reg-email">Business Email *</label>
|
||
<input
|
||
id="reg-email"
|
||
type="email"
|
||
className="form-control"
|
||
placeholder="Enter email address"
|
||
value={email}
|
||
onChange={(e) => setEmail(e.target.value)}
|
||
required
|
||
/>
|
||
</div>
|
||
<div className="form-group">
|
||
<label htmlFor="reg-phone">Mobile Number *</label>
|
||
<input
|
||
id="reg-phone"
|
||
type="tel"
|
||
className="form-control"
|
||
placeholder="Enter 10-digit number"
|
||
value={phone}
|
||
onChange={(e) => setPhone(e.target.value)}
|
||
required
|
||
/>
|
||
</div>
|
||
<div className="form-group">
|
||
<label htmlFor="reg-pass">Create Password *</label>
|
||
<input
|
||
id="reg-pass"
|
||
type="password"
|
||
className="form-control"
|
||
placeholder="Minimum 8 characters"
|
||
value={password}
|
||
onChange={(e) => setPassword(e.target.value)}
|
||
required
|
||
/>
|
||
</div>
|
||
<button type="submit" className="btn btn-primary" style={{ width: '100%', padding: '0.75rem', marginTop: '1rem' }}>
|
||
Register Business
|
||
</button>
|
||
</form>
|
||
)}
|
||
</div>
|
||
)}
|
||
|
||
{/* FORGOT PASSWORD PAGE */}
|
||
{currentPage === 'forgot-password' && (
|
||
<div className="form-card" style={{ maxWidth: '480px' }}>
|
||
<h2 className="form-card-title">Reset Your Password</h2>
|
||
<p style={{ color: 'var(--text-muted)', marginBottom: '1.5rem', fontSize: '0.9rem' }}>
|
||
Enter your registered business email or mobile number to receive a secure password reset link / OTP.
|
||
</p>
|
||
|
||
{!resetOtpSent ? (
|
||
<form onSubmit={(e) => { e.preventDefault(); if (resetEmail.trim()) { setResetOtpSent(true); alert('Simulated Reset Code (123456) sent successfully!'); } }}>
|
||
<div className="form-group">
|
||
<label htmlFor="reset-email">Email or Phone Number *</label>
|
||
<input
|
||
id="reset-email"
|
||
type="text"
|
||
className="form-control"
|
||
placeholder="name@example.com or 10-digit number"
|
||
value={resetEmail}
|
||
onChange={(e) => setResetEmail(e.target.value)}
|
||
required
|
||
/>
|
||
</div>
|
||
<button type="submit" className="btn btn-primary" style={{ width: '100%', padding: '0.75rem' }}>
|
||
Send Verification Code
|
||
</button>
|
||
<div style={{ textAlign: 'center', marginTop: '1.5rem' }}>
|
||
<button type="button" className="nav-link" style={{ color: 'var(--primary)', background: 'none', border: 'none', cursor: 'pointer' }} onClick={() => navigateTo('login')}>Back to Login</button>
|
||
</div>
|
||
</form>
|
||
) : (
|
||
<form onSubmit={(e) => { 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'); } }}>
|
||
<div className="form-group">
|
||
<label htmlFor="reset-otp">Enter 6-Digit OTP *</label>
|
||
<input
|
||
id="reset-otp"
|
||
type="text"
|
||
className="form-control"
|
||
placeholder="Enter 123456"
|
||
value={resetOtp}
|
||
onChange={(e) => setResetOtp(e.target.value)}
|
||
required
|
||
/>
|
||
</div>
|
||
<div className="form-group">
|
||
<label htmlFor="reset-new-pass">Create New Password *</label>
|
||
<input
|
||
id="reset-new-pass"
|
||
type="password"
|
||
className="form-control"
|
||
placeholder="Minimum 8 characters"
|
||
value={resetPassword}
|
||
onChange={(e) => setResetPassword(e.target.value)}
|
||
required
|
||
/>
|
||
</div>
|
||
<button type="submit" className="btn btn-primary" style={{ width: '100%', padding: '0.75rem' }}>
|
||
Update Password & Login
|
||
</button>
|
||
</form>
|
||
)}
|
||
</div>
|
||
)}
|
||
|
||
{/* LOGIN WITH OTP PAGE */}
|
||
{currentPage === 'login-otp' && (
|
||
<div className="form-card" style={{ maxWidth: '480px' }}>
|
||
<h2 className="form-card-title">Login with OTP</h2>
|
||
<p style={{ color: 'var(--text-muted)', marginBottom: '1.5rem', fontSize: '0.9rem' }}>
|
||
Access your seller portal using a temporary verification code sent to your mobile.
|
||
</p>
|
||
|
||
{!otpLoginSent ? (
|
||
<form onSubmit={(e) => { e.preventDefault(); if (otpLoginPhone.trim()) { setOtpLoginSent(true); alert('Simulated Login Code (123456) sent to mobile!'); } }}>
|
||
<div className="form-group">
|
||
<label htmlFor="otp-login-phone">Registered Mobile Number *</label>
|
||
<input
|
||
id="otp-login-phone"
|
||
type="tel"
|
||
className="form-control"
|
||
placeholder="Enter 10-digit number"
|
||
value={otpLoginPhone}
|
||
onChange={(e) => setOtpLoginPhone(e.target.value)}
|
||
required
|
||
/>
|
||
</div>
|
||
<button type="submit" className="btn btn-primary" style={{ width: '100%', padding: '0.75rem' }}>
|
||
Send Login OTP
|
||
</button>
|
||
<div style={{ textAlign: 'center', marginTop: '1.5rem' }}>
|
||
<button type="button" className="nav-link" style={{ color: 'var(--primary)', background: 'none', border: 'none', cursor: 'pointer' }} onClick={() => navigateTo('login')}>Back to Password Login</button>
|
||
</div>
|
||
</form>
|
||
) : (
|
||
<form onSubmit={(e) => { e.preventDefault(); if (otpLoginCode === '123456') { setOtpLoginSent(false); setOtpLoginPhone(''); setOtpLoginCode(''); if (!isProfileComplete) { navigateTo('profile-completion'); } else { navigateTo('dashboard'); } } else { alert('Invalid OTP. Please enter 123456'); } }}>
|
||
<div className="form-group">
|
||
<label htmlFor="otp-login-code">Enter 6-Digit OTP *</label>
|
||
<input
|
||
id="otp-login-code"
|
||
type="text"
|
||
className="form-control"
|
||
placeholder="Enter 123456"
|
||
value={otpLoginCode}
|
||
onChange={(e) => setOtpLoginCode(e.target.value)}
|
||
required
|
||
/>
|
||
</div>
|
||
<button type="submit" className="btn btn-primary" style={{ width: '100%', padding: '0.75rem' }}>
|
||
Verify & Login
|
||
</button>
|
||
</form>
|
||
)}
|
||
</div>
|
||
)}
|
||
{currentPage === 'profile-completion' && (
|
||
<div className="form-card" style={{ maxWidth: '640px' }}>
|
||
<div className="step-progress-wrapper">
|
||
<span className="step-label">Step {profileStep} of 3: {
|
||
profileStep === 1 ? 'Contact Verification' :
|
||
profileStep === 2 ? 'Tax & Identity Verification' :
|
||
'Store & Pickup Location'
|
||
}</span>
|
||
<div className="step-bar-container">
|
||
<div className="step-bar-fill" style={{ width: `${(profileStep / 3) * 100}%` }}></div>
|
||
</div>
|
||
</div>
|
||
|
||
<h2 className="form-card-title" style={{ marginBottom: '1.5rem' }}>Complete Your Supplier Profile</h2>
|
||
|
||
<form onSubmit={handleProfileSubmit}>
|
||
{profileStep === 1 && (
|
||
<div>
|
||
<h3 style={{ fontSize: '1.1rem', marginBottom: '1rem', color: 'var(--primary)' }}>Step 1: Verify Contacts</h3>
|
||
|
||
{/* Phone/WhatsApp Verification */}
|
||
<div className="form-group" style={{ padding: '1rem', backgroundColor: 'rgba(233, 196, 110, 0.08)', borderRadius: '8px', border: '1px solid var(--border)', marginBottom: '1rem' }}>
|
||
<label htmlFor="verify-phone">Mobile / WhatsApp Number *</label>
|
||
<div style={{ display: 'flex', gap: '0.75rem', marginBottom: '0.75rem' }}>
|
||
<input
|
||
id="verify-phone"
|
||
type="tel"
|
||
className="form-control"
|
||
placeholder="e.g. 9876543210"
|
||
value={phone}
|
||
onChange={(e) => setPhone(e.target.value)}
|
||
disabled={phoneVerified}
|
||
/>
|
||
<button
|
||
type="button"
|
||
className="btn btn-primary"
|
||
onClick={() => { setPhoneOtpSent(true); alert('WhatsApp OTP Code is 123456'); }}
|
||
disabled={phoneVerified || !phone}
|
||
style={{ minWidth: '130px' }}
|
||
>
|
||
{phoneOtpSent ? 'Resend OTP' : 'Send WhatsApp OTP'}
|
||
</button>
|
||
</div>
|
||
|
||
{phoneOtpSent && !phoneVerified && (
|
||
<div style={{ display: 'flex', gap: '0.75rem', alignItems: 'center' }}>
|
||
<input
|
||
type="text"
|
||
className="form-control"
|
||
placeholder="Enter 123456"
|
||
value={enteredPhoneOtp}
|
||
onChange={(e) => setEnteredPhoneOtp(e.target.value)}
|
||
style={{ maxWidth: '150px' }}
|
||
/>
|
||
<button
|
||
type="button"
|
||
className="btn btn-dark"
|
||
onClick={() => {
|
||
if (enteredPhoneOtp === '123456') {
|
||
setPhoneVerified(true);
|
||
alert('Phone verified successfully!');
|
||
} else {
|
||
alert('Incorrect code. Enter 123456');
|
||
}
|
||
}}
|
||
>
|
||
Verify Code
|
||
</button>
|
||
</div>
|
||
)}
|
||
|
||
{phoneVerified && (
|
||
<div style={{ color: 'var(--success)', fontWeight: 600, fontSize: '0.9rem' }}>
|
||
✓ Mobile & WhatsApp Verified
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
{/* Email Verification */}
|
||
<div className="form-group" style={{ padding: '1rem', backgroundColor: 'rgba(42, 157, 143, 0.08)', borderRadius: '8px', border: '1px solid var(--border)', marginBottom: '1.5rem' }}>
|
||
<label htmlFor="verify-email">Business Email Address *</label>
|
||
<div style={{ display: 'flex', gap: '0.75rem', marginBottom: '0.75rem' }}>
|
||
<input
|
||
id="verify-email"
|
||
type="email"
|
||
className="form-control"
|
||
placeholder="name@example.com"
|
||
value={email}
|
||
onChange={(e) => setEmail(e.target.value)}
|
||
disabled={emailVerified}
|
||
/>
|
||
<button
|
||
type="button"
|
||
className="btn btn-primary"
|
||
onClick={() => { setEmailOtpSent(true); alert('Email OTP Code is 123456'); }}
|
||
disabled={emailVerified || !email}
|
||
style={{ minWidth: '130px' }}
|
||
>
|
||
{emailOtpSent ? 'Resend OTP' : 'Send Email OTP'}
|
||
</button>
|
||
</div>
|
||
|
||
{emailOtpSent && !emailVerified && (
|
||
<div style={{ display: 'flex', gap: '0.75rem', alignItems: 'center' }}>
|
||
<input
|
||
type="text"
|
||
className="form-control"
|
||
placeholder="Enter 123456"
|
||
value={enteredEmailOtp}
|
||
onChange={(e) => setEnteredEmailOtp(e.target.value)}
|
||
style={{ maxWidth: '150px' }}
|
||
/>
|
||
<button
|
||
type="button"
|
||
className="btn btn-dark"
|
||
onClick={() => {
|
||
if (enteredEmailOtp === '123456') {
|
||
setEmailVerified(true);
|
||
alert('Email verified successfully!');
|
||
} else {
|
||
alert('Incorrect code. Enter 123456');
|
||
}
|
||
}}
|
||
>
|
||
Verify Code
|
||
</button>
|
||
</div>
|
||
)}
|
||
|
||
{emailVerified && (
|
||
<div style={{ color: 'var(--success)', fontWeight: 600, fontSize: '0.9rem' }}>
|
||
✓ Email Verified
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
<button
|
||
type="submit"
|
||
className="btn btn-primary"
|
||
style={{ width: '100%', padding: '0.8rem', backgroundColor: 'var(--accent)', color: 'var(--primary)', fontWeight: 'bold' }}
|
||
disabled={!phoneVerified || !emailVerified}
|
||
>
|
||
Next Step: Identity Verification
|
||
</button>
|
||
</div>
|
||
)}
|
||
|
||
{profileStep === 2 && (
|
||
<div>
|
||
<h3 style={{ fontSize: '1.1rem', marginBottom: '1rem', color: 'var(--primary)' }}>Step 2: GSTIN, PAN & Aadhaar</h3>
|
||
|
||
{/* GSTIN verification */}
|
||
<div className="form-group">
|
||
<label htmlFor="verify-gst">GSTIN Number *</label>
|
||
<div style={{ display: 'flex', gap: '0.75rem' }}>
|
||
<input
|
||
id="verify-gst"
|
||
type="text"
|
||
className="form-control"
|
||
placeholder="Enter 15-digit GSTIN"
|
||
value={gstin}
|
||
onChange={(e) => setGstin(e.target.value)}
|
||
required
|
||
/>
|
||
<button
|
||
type="button"
|
||
className={`btn ${isGstinVerified ? 'btn-secondary' : 'btn-primary'}`}
|
||
onClick={handleVerifyGstin}
|
||
disabled={isVerifyingGstin || isGstinVerified}
|
||
style={{ minWidth: '120px' }}
|
||
>
|
||
{isVerifyingGstin ? 'Verifying...' : isGstinVerified ? 'Verified ✓' : 'Verify GSTIN'}
|
||
</button>
|
||
</div>
|
||
{isGstinVerified && (
|
||
<p style={{ color: 'var(--success)', fontSize: '0.85rem', marginTop: '0.5rem', fontWeight: 600 }}>
|
||
GSTIN successfully verified with government registry.
|
||
</p>
|
||
)}
|
||
</div>
|
||
|
||
{/* Aadhaar Upload */}
|
||
<div className="form-group" style={{ marginBottom: '1.25rem' }}>
|
||
<label htmlFor="aadhar-upload">Aadhaar Card Upload *</label>
|
||
<div style={{ display: 'flex', alignItems: 'center', gap: '1rem' }}>
|
||
<input
|
||
id="aadhar-upload"
|
||
type="file"
|
||
accept=".pdf,image/*"
|
||
onChange={(e) => {
|
||
if (e.target.files?.[0]) {
|
||
setAadharFile(e.target.files[0].name);
|
||
alert(`Aadhaar card uploaded: ${e.target.files[0].name}`);
|
||
}
|
||
}}
|
||
style={{ display: 'none' }}
|
||
/>
|
||
<button
|
||
type="button"
|
||
className="btn btn-outline-dark"
|
||
onClick={() => document.getElementById('aadhar-upload')?.click()}
|
||
>
|
||
📁 Select Aadhaar File
|
||
</button>
|
||
<span style={{ fontSize: '0.9rem', color: 'var(--text-muted)' }}>
|
||
{aadharFile ? `Selected: ${aadharFile}` : 'No file uploaded yet'}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
|
||
{/* PAN Upload */}
|
||
<div className="form-group" style={{ marginBottom: '2rem' }}>
|
||
<label htmlFor="pan-upload">PAN Card Upload *</label>
|
||
<div style={{ display: 'flex', alignItems: 'center', gap: '1rem' }}>
|
||
<input
|
||
id="pan-upload"
|
||
type="file"
|
||
accept=".pdf,image/*"
|
||
onChange={(e) => {
|
||
if (e.target.files?.[0]) {
|
||
setPanFile(e.target.files[0].name);
|
||
alert(`PAN card uploaded: ${e.target.files[0].name}`);
|
||
}
|
||
}}
|
||
style={{ display: 'none' }}
|
||
/>
|
||
<button
|
||
type="button"
|
||
className="btn btn-outline-dark"
|
||
onClick={() => document.getElementById('pan-upload')?.click()}
|
||
>
|
||
📁 Select PAN File
|
||
</button>
|
||
<span style={{ fontSize: '0.9rem', color: 'var(--text-muted)' }}>
|
||
{panFile ? `Selected: ${panFile}` : 'No file uploaded yet'}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div style={{ display: 'flex', justifyContent: 'space-between', gap: '1rem' }}>
|
||
<button type="button" className="btn btn-outline-dark" style={{ flex: 1 }} onClick={() => setProfileStep(1)}>
|
||
Back
|
||
</button>
|
||
<button
|
||
type="submit"
|
||
className="btn btn-primary"
|
||
style={{ flex: 1, backgroundColor: 'var(--accent)', color: 'var(--primary)', fontWeight: 'bold' }}
|
||
disabled={!isGstinVerified || !aadharFile || !panFile}
|
||
>
|
||
Next Step: Store & Location
|
||
</button>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{profileStep === 3 && (
|
||
<div>
|
||
<h3 style={{ fontSize: '1.1rem', marginBottom: '1rem', color: 'var(--primary)' }}>Step 3: Store Details & Location Map</h3>
|
||
|
||
<div className="form-group">
|
||
<label htmlFor="store-name">Store Display Name *</label>
|
||
<input
|
||
id="store-name"
|
||
type="text"
|
||
className="form-control"
|
||
placeholder="Enter store name"
|
||
value={storeName}
|
||
onChange={(e) => setStoreName(e.target.value)}
|
||
required
|
||
/>
|
||
</div>
|
||
|
||
<div className="form-group">
|
||
<label htmlFor="store-bio">About Your Craft / Business *</label>
|
||
<textarea
|
||
id="store-bio"
|
||
className="form-control"
|
||
rows={2}
|
||
placeholder="Describe your craft, materials, and history..."
|
||
value={businessBio}
|
||
onChange={(e) => setBusinessBio(e.target.value)}
|
||
required
|
||
></textarea>
|
||
</div>
|
||
|
||
{/* Interactive Location Map Picker */}
|
||
<div className="form-group">
|
||
<label>Store Location on Map *</label>
|
||
<div
|
||
style={{
|
||
height: '180px',
|
||
backgroundColor: '#cbd5e1',
|
||
borderRadius: '8px',
|
||
position: 'relative',
|
||
overflow: 'hidden',
|
||
border: '2px solid var(--border)',
|
||
cursor: 'crosshair',
|
||
backgroundImage: 'radial-gradient(circle, #94a3b8 1px, transparent 1px)',
|
||
backgroundSize: '16px 16px',
|
||
display: 'flex',
|
||
alignItems: 'center',
|
||
justifyContent: 'center'
|
||
}}
|
||
onClick={(e) => {
|
||
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 */}
|
||
<div
|
||
style={{
|
||
position: 'absolute',
|
||
left: `${(mapCoordinates.lng - 77.5) * 1000}px`,
|
||
top: `${(mapCoordinates.lat - 12.9) * 1000}px`,
|
||
transform: 'translate(-50%, -100%)',
|
||
fontSize: '2rem',
|
||
color: 'var(--error)',
|
||
pointerEvents: 'none',
|
||
textShadow: '0 2px 4px rgba(0,0,0,0.3)'
|
||
}}
|
||
>
|
||
📍
|
||
</div>
|
||
<div style={{
|
||
position: 'absolute',
|
||
bottom: '8px',
|
||
left: '8px',
|
||
backgroundColor: 'rgba(26, 43, 69, 0.85)',
|
||
color: 'white',
|
||
padding: '0.3rem 0.6rem',
|
||
borderRadius: '4px',
|
||
fontSize: '0.75rem',
|
||
pointerEvents: 'none'
|
||
}}>
|
||
Coordinates: Lat {mapCoordinates.lat}, Lng {mapCoordinates.lng} (Click map to change pin)
|
||
</div>
|
||
<span style={{ fontSize: '0.85rem', color: '#475569', pointerEvents: 'none', fontWeight: 600 }}>Click anywhere on the map grid to pin location</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="form-group">
|
||
<label htmlFor="location-text">Location in Text (Address) *</label>
|
||
<input
|
||
id="location-text"
|
||
type="text"
|
||
className="form-control"
|
||
placeholder="Street, Landmark, District"
|
||
value={address.street}
|
||
onChange={(e) => setAddress({ ...address, street: e.target.value })}
|
||
required
|
||
/>
|
||
</div>
|
||
|
||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '1rem' }} className="form-group">
|
||
<div>
|
||
<label htmlFor="loc-city">City *</label>
|
||
<input
|
||
id="loc-city"
|
||
type="text"
|
||
className="form-control"
|
||
value={address.city}
|
||
onChange={(e) => setAddress({ ...address, city: e.target.value })}
|
||
required
|
||
/>
|
||
</div>
|
||
<div>
|
||
<label htmlFor="loc-pincode">Pincode *</label>
|
||
<input
|
||
id="loc-pincode"
|
||
type="text"
|
||
className="form-control"
|
||
placeholder="6-digit Pincode"
|
||
value={address.pincode}
|
||
onChange={(e) => setAddress({ ...address, pincode: e.target.value })}
|
||
required
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
<div style={{ display: 'flex', justifyContent: 'space-between', gap: '1rem', marginTop: '2rem' }}>
|
||
<button type="button" className="btn btn-outline-dark" style={{ flex: 1 }} onClick={() => setProfileStep(2)}>
|
||
Back
|
||
</button>
|
||
<button
|
||
type="submit"
|
||
className="btn btn-primary"
|
||
style={{ flex: 1, backgroundColor: 'var(--accent)', color: 'var(--primary)', fontWeight: 'bold' }}
|
||
>
|
||
Submit Supplier Profile
|
||
</button>
|
||
</div>
|
||
</div>
|
||
)}
|
||
</form>
|
||
</div>
|
||
)}
|
||
|
||
{/* ONBOARDING CONFIRMATION / REVIEW STATUS VIEW (Step 3 of 3) */}
|
||
{currentPage === 'confirmation' && (
|
||
<div className="form-card" style={{ maxWidth: '640px', textAlign: 'center' }}>
|
||
<div className="step-progress-wrapper">
|
||
<span className="step-label">Step 3 of 3: Verification</span>
|
||
<div className="step-bar-container">
|
||
<div className="step-bar-fill" style={{ width: '100%' }}></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div style={{ fontSize: '4rem', marginBottom: '1.5rem' }}>🎉</div>
|
||
<h2 className="form-card-title">Welcome to {CONFIG.companyName}!</h2>
|
||
|
||
<p style={{ color: 'var(--text-muted)', marginBottom: '2rem', fontSize: '1.05rem' }}>
|
||
Your registration is under review. We will verify your GSTIN credentials and activate your dashboard access within 24-48 hours.
|
||
</p>
|
||
|
||
<div className="conf-dashboard-preview">
|
||
<div className="mock-dash-header">
|
||
<div className="mock-dash-dot"></div>
|
||
<div className="mock-dash-dot"></div>
|
||
<div className="mock-dash-dot"></div>
|
||
</div>
|
||
<div className="mock-dash-body">
|
||
<div className="mock-dash-card">
|
||
<div className="mock-dash-card-title">Total Sales</div>
|
||
<div className="mock-dash-card-val">$0.00</div>
|
||
</div>
|
||
<div className="mock-dash-card">
|
||
<div className="mock-dash-card-title">Active Orders</div>
|
||
<div className="mock-dash-card-val">0</div>
|
||
</div>
|
||
<div className="mock-dash-card">
|
||
<div className="mock-dash-card-title">Balance</div>
|
||
<div className="mock-dash-card-val">$0.00</div>
|
||
</div>
|
||
<div className="mock-dash-chart">
|
||
<div className="mock-chart-bar" style={{ height: '40px' }}></div>
|
||
<div className="mock-chart-bar" style={{ height: '60px' }}></div>
|
||
<div className="mock-chart-bar" style={{ height: '50px' }}></div>
|
||
<div className="mock-chart-bar active" style={{ height: '80px' }}></div>
|
||
<div className="mock-chart-bar" style={{ height: '30px' }}></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<button
|
||
className="btn btn-dark"
|
||
style={{ marginTop: '2.5rem', width: '100%', padding: '0.8rem' }}
|
||
onClick={() => navigateTo('dashboard')}
|
||
>
|
||
Go to Dashboard (Preview Only)
|
||
</button>
|
||
</div>
|
||
)}
|
||
|
||
{/* WELCOME PAGE & SETUP TOUR FOR NEW SELLERS */}
|
||
{currentPage === 'welcome-tour' && (
|
||
<WelcomeTourWizard onComplete={() => navigateTo('dashboard')} storeName={storeName} />
|
||
)}
|
||
|
||
</main>
|
||
) : (
|
||
/* --- FULL SERVICE SUPPLIER ACTIVE DASHBOARD PAGE --- */
|
||
<div className="dashboard-container">
|
||
|
||
{/* Sidebar */}
|
||
<aside className="dashboard-sidebar">
|
||
<div style={{ marginBottom: '2rem', paddingLeft: '1rem' }}>
|
||
<div style={{ fontSize: '0.8rem', opacity: 0.6 }}>SELLER PANEL</div>
|
||
<h4 style={{ color: 'var(--accent)', fontWeight: 700 }}>{storeName || 'Artisan Shop'}</h4>
|
||
</div>
|
||
<ul className="sidebar-menu">
|
||
<li>
|
||
<button
|
||
className={`sidebar-item-btn ${dashTab === 'overview' ? 'active' : ''}`}
|
||
onClick={() => setDashTab('overview')}
|
||
>
|
||
📊 Overview & Analytics
|
||
</button>
|
||
</li>
|
||
<li>
|
||
<button
|
||
className={`sidebar-item-btn ${dashTab === 'products' ? 'active' : ''}`}
|
||
onClick={() => setDashTab('products')}
|
||
>
|
||
📦 Manage Products
|
||
</button>
|
||
</li>
|
||
<li>
|
||
<button
|
||
className={`sidebar-item-btn ${dashTab === 'orders' ? 'active' : ''}`}
|
||
onClick={() => setDashTab('orders')}
|
||
>
|
||
🚚 Orders & Transit
|
||
</button>
|
||
</li>
|
||
<li>
|
||
<button
|
||
className={`sidebar-item-btn ${dashTab === 'returns' ? 'active' : ''}`}
|
||
onClick={() => setDashTab('returns')}
|
||
>
|
||
🔄 Returns Management
|
||
</button>
|
||
</li>
|
||
<li>
|
||
<button
|
||
className={`sidebar-item-btn ${dashTab === 'wallet' ? 'active' : ''}`}
|
||
onClick={() => setDashTab('wallet')}
|
||
>
|
||
💼 Wallet & Payouts
|
||
</button>
|
||
</li>
|
||
<li>
|
||
<button
|
||
className={`sidebar-item-btn ${dashTab === 'barcode-generator' ? 'active' : ''}`}
|
||
onClick={() => setDashTab('barcode-generator')}
|
||
>
|
||
🏷️ Barcode Generator
|
||
</button>
|
||
</li>
|
||
<li>
|
||
<button
|
||
className={`sidebar-item-btn ${dashTab === 'settings' ? 'active' : ''}`}
|
||
onClick={() => setDashTab('settings')}
|
||
>
|
||
⚙️ Store Settings
|
||
</button>
|
||
</li>
|
||
</ul>
|
||
</aside>
|
||
|
||
{/* Main Dashboard Space */}
|
||
<section className="dashboard-main">
|
||
|
||
{/* OVERVIEW PANEL */}
|
||
{dashTab === 'overview' && (
|
||
<div>
|
||
<div className="dashboard-header-bar">
|
||
<h2 className="dashboard-title">Performance Analytics</h2>
|
||
<div className="time-filter-bar">
|
||
<button className={`time-filter-btn ${dateFilter === 'year' ? 'active' : ''}`} onClick={() => setDateFilter('year')}>Year</button>
|
||
<button className={`time-filter-btn ${dateFilter === 'week' ? 'active' : ''}`} onClick={() => setDateFilter('week')}>Week</button>
|
||
<button className={`time-filter-btn ${dateFilter === 'day' ? 'active' : ''}`} onClick={() => setDateFilter('day')}>Day</button>
|
||
<button className={`time-filter-btn ${dateFilter === 'custom' ? 'active' : ''}`} onClick={() => setDateFilter('custom')}>Custom Date</button>
|
||
</div>
|
||
</div>
|
||
|
||
{dateFilter === 'custom' && (
|
||
<div className="custom-date-panel">
|
||
<div className="form-group" style={{ margin: 0 }}>
|
||
<label style={{ fontSize: '0.75rem' }}>Start Date</label>
|
||
<input type="date" className="form-control" value={customDates.start} onChange={e => setCustomDates({ ...customDates, start: e.target.value })} />
|
||
</div>
|
||
<div className="form-group" style={{ margin: 0 }}>
|
||
<label style={{ fontSize: '0.75rem' }}>End Date</label>
|
||
<input type="date" className="form-control" value={customDates.end} onChange={e => setCustomDates({ ...customDates, end: e.target.value })} />
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
<div className="metrics-row">
|
||
<div className="metric-data-card">
|
||
<div className="metric-title">Total Sales Volume</div>
|
||
<div className="metric-value">${selectedMetrics.totalSales.toFixed(2)}</div>
|
||
</div>
|
||
<div className="metric-data-card">
|
||
<div className="metric-title">Total Net Earned</div>
|
||
<div className="metric-value" style={{ color: 'var(--success)' }}>${selectedMetrics.totalEarned.toFixed(2)}</div>
|
||
</div>
|
||
<div className="metric-data-card">
|
||
<div className="metric-title">Available Stock</div>
|
||
<div className="metric-value">{selectedMetrics.stockDetails} units</div>
|
||
</div>
|
||
<div className="metric-data-card">
|
||
<div className="metric-title">Returned Items</div>
|
||
<div className="metric-value" style={{ color: 'var(--error)' }}>{selectedMetrics.returnedItems} requests</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="chart-card">
|
||
<div className="chart-header">
|
||
<h3 className="chart-title">Revenue Trajectory</h3>
|
||
<span style={{ fontSize: '0.85rem', color: 'var(--text-muted)' }}>Filtered by {dateFilter} metrics</span>
|
||
</div>
|
||
<div className="main-bar-chart">
|
||
{selectedMetrics.chartValues.map((val, idx) => (
|
||
<div
|
||
key={idx}
|
||
className={`chart-bar-col ${idx === selectedMetrics.chartValues.length - 1 ? 'highlighted' : ''}`}
|
||
style={{ height: `${val}%` }}
|
||
title={`Point ${idx + 1}: ${val}%`}
|
||
></div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* PRODUCTS TAB */}
|
||
{dashTab === 'products' && (
|
||
<div>
|
||
<h2 className="dashboard-title" style={{ marginBottom: '2rem' }}>Product Listings & Upload</h2>
|
||
|
||
<div className="tool-split-layout">
|
||
|
||
{/* Products Grid list */}
|
||
<div>
|
||
<h3 style={{ marginBottom: '1rem', color: 'var(--primary)' }}>Active Inventory ({products.length})</h3>
|
||
<div className="dashboard-table-wrapper">
|
||
<table className="dashboard-table">
|
||
<thead>
|
||
<tr>
|
||
<th>Product</th>
|
||
<th>SKU</th>
|
||
<th>Category</th>
|
||
<th>Price</th>
|
||
<th>Stock</th>
|
||
<th>Actions</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{products.map(p => (
|
||
<tr key={p.id}>
|
||
<td style={{ display: 'flex', alignItems: 'center', gap: '0.75rem' }}>
|
||
<img src={p.image} alt={p.title} className="product-thumb" />
|
||
<span style={{ fontWeight: 600 }}>{p.title}</span>
|
||
</td>
|
||
<td>{p.sku}</td>
|
||
<td>{p.category}</td>
|
||
<td>${p.price.toFixed(2)}</td>
|
||
<td style={{ fontWeight: 600, color: p.stock < 15 ? 'var(--error)' : 'inherit' }}>
|
||
{p.stock} pcs
|
||
</td>
|
||
<td>
|
||
<button className="btn btn-outline-dark" style={{ padding: '0.3rem 0.6rem', fontSize: '0.8rem', marginRight: '0.5rem' }} onClick={() => handleEditClick(p)}>Edit</button>
|
||
<button className="btn btn-outline-dark" style={{ padding: '0.3rem 0.6rem', fontSize: '0.8rem', borderColor: 'var(--error)', color: 'var(--error)' }} onClick={() => handleDeleteProduct(p.id)}>Delete</button>
|
||
</td>
|
||
</tr>
|
||
))}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
{/* Bulk tools */}
|
||
<div className="chart-card" style={{ marginTop: '2rem' }}>
|
||
<h3 style={{ marginBottom: '1rem', color: 'var(--primary)' }}>Bulk Operations</h3>
|
||
<p style={{ color: 'var(--text-muted)', fontSize: '0.9rem', marginBottom: '1.5rem' }}>
|
||
To upload products in bulk, use our standard Excel/CSV template. Review the sample format below, download the template, populate your inventory rows, and upload.
|
||
</p>
|
||
|
||
{/* Sample Template Table */}
|
||
<div style={{ overflowX: 'auto', marginBottom: '1.5rem', borderRadius: '8px', border: '1px solid var(--border)' }}>
|
||
<table style={{ width: '100%', borderCollapse: 'collapse', fontSize: '0.8rem', textAlign: 'left' }}>
|
||
<thead>
|
||
<tr style={{ backgroundColor: 'var(--primary)', color: 'white' }}>
|
||
<th style={{ padding: '0.5rem' }}>Title *</th>
|
||
<th style={{ padding: '0.5rem' }}>SKU *</th>
|
||
<th style={{ padding: '0.5rem' }}>Category *</th>
|
||
<th style={{ padding: '0.5rem' }}>Price ($) *</th>
|
||
<th style={{ padding: '0.5rem' }}>Stock *</th>
|
||
<th style={{ padding: '0.5rem' }}>Description</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr style={{ borderBottom: '1px solid var(--border)', backgroundColor: '#faf9f6' }}>
|
||
<td style={{ padding: '0.5rem' }}>Silk Banarasi Saree</td>
|
||
<td style={{ padding: '0.5rem' }}>SAR-BAN-101</td>
|
||
<td style={{ padding: '0.5rem' }}>Apparel</td>
|
||
<td style={{ padding: '0.5rem' }}>120.00</td>
|
||
<td style={{ padding: '0.5rem' }}>15</td>
|
||
<td style={{ padding: '0.5rem' }}>Pure handloom zardozi border</td>
|
||
</tr>
|
||
<tr style={{ backgroundColor: '#ffffff' }}>
|
||
<td style={{ padding: '0.5rem' }}>Wooden Carved Ganesha</td>
|
||
<td style={{ padding: '0.5rem' }}>雕-GAN-302</td>
|
||
<td style={{ padding: '0.5rem' }}>Home Decor</td>
|
||
<td style={{ padding: '0.5rem' }}>85.00</td>
|
||
<td style={{ padding: '0.5rem' }}>5</td>
|
||
<td style={{ padding: '0.5rem' }}>Premium teak wood hand carved</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<div style={{ display: 'flex', gap: '1rem', marginBottom: '1.5rem' }}>
|
||
<button
|
||
type="button"
|
||
className="btn btn-outline-dark"
|
||
style={{ flex: 1, padding: '0.6rem', fontSize: '0.85rem' }}
|
||
onClick={() => {
|
||
// Simulate download of CSV file
|
||
const csvContent = "data:text/csv;charset=utf-8,Title,SKU,Category,Price,Stock,Description\nSilk Banarasi Saree,SAR-BAN-101,Apparel,120.00,15,Pure handloom zardozi border\nWooden Carved Ganesha,GAN-302,Home Decor,85.00,5,Premium teak wood hand carved";
|
||
const encodedUri = encodeURI(csvContent);
|
||
const link = document.createElement("a");
|
||
link.setAttribute("href", encodedUri);
|
||
link.setAttribute("download", "seller_product_template.csv");
|
||
document.body.appendChild(link);
|
||
link.click();
|
||
document.body.removeChild(link);
|
||
alert('Sample template downloaded successfully!');
|
||
}}
|
||
>
|
||
📥 Download Sample Excel Template
|
||
</button>
|
||
</div>
|
||
|
||
<div className="bulk-drop-zone" onClick={() => document.getElementById('bulk-file-pick')?.click()}>
|
||
<span>{isParsingBulk ? 'Processing bulk data...' : '📂 Click or drop your populated template file here to import'}</span>
|
||
<input id="bulk-file-pick" type="file" accept=".csv" onChange={handleBulkUploadSimulate} style={{ display: 'none' }} />
|
||
</div>
|
||
|
||
{bulkLog.length > 0 && (
|
||
<div style={{ backgroundColor: '#f1f5f9', padding: '1rem', borderRadius: '8px', fontFamily: 'monospace', fontSize: '0.85rem', marginTop: '1rem' }}>
|
||
<h4 style={{ marginBottom: '0.5rem' }}>Import log:</h4>
|
||
{bulkLog.map((log, i) => <div key={i}>{log}</div>)}
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
|
||
{/* Add/Edit Form */}
|
||
<div className="form-card" style={{ margin: 0, maxWidth: '100%' }}>
|
||
<h3 style={{ marginBottom: '1.5rem', color: 'var(--primary)' }}>
|
||
{isEditingProduct ? 'Modify Product Listing' : 'Upload New Product'}
|
||
</h3>
|
||
<form onSubmit={handleSaveProduct}>
|
||
<div className="form-group">
|
||
<label htmlFor="prod-title">Product Title *</label>
|
||
<input
|
||
id="prod-title"
|
||
type="text"
|
||
className="form-control"
|
||
value={productForm.title}
|
||
onChange={e => setProductForm({ ...productForm, title: e.target.value })}
|
||
required
|
||
/>
|
||
</div>
|
||
<div className="form-group">
|
||
<label htmlFor="prod-sku">SKU Code (Auto-generates if empty)</label>
|
||
<input
|
||
id="prod-sku"
|
||
type="text"
|
||
className="form-control"
|
||
placeholder="e.g. SHAWL-IND-01"
|
||
value={productForm.sku}
|
||
onChange={e => setProductForm({ ...productForm, sku: e.target.value })}
|
||
/>
|
||
</div>
|
||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '1rem' }} className="form-group">
|
||
<div>
|
||
<label htmlFor="prod-price">Price ($) *</label>
|
||
<input
|
||
id="prod-price"
|
||
type="number"
|
||
className="form-control"
|
||
value={productForm.price || ''}
|
||
onChange={e => setProductForm({ ...productForm, price: Number(e.target.value) })}
|
||
required
|
||
/>
|
||
</div>
|
||
<div>
|
||
<label htmlFor="prod-stock">Initial Stock *</label>
|
||
<input
|
||
id="prod-stock"
|
||
type="number"
|
||
className="form-control"
|
||
value={productForm.stock || ''}
|
||
onChange={e => setProductForm({ ...productForm, stock: Number(e.target.value) })}
|
||
required
|
||
/>
|
||
</div>
|
||
</div>
|
||
<div className="form-group">
|
||
<label htmlFor="prod-category">Category</label>
|
||
<select
|
||
id="prod-category"
|
||
className="form-control"
|
||
value={productForm.category}
|
||
onChange={e => setProductForm({ ...productForm, category: e.target.value })}
|
||
>
|
||
<option value="Apparel">Apparel</option>
|
||
<option value="Home Decor">Home Decor</option>
|
||
<option value="Sculptures">Sculptures</option>
|
||
<option value="Kitchenware">Kitchenware</option>
|
||
<option value="Linens">Linens</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div style={{ display: 'flex', gap: '1rem', marginTop: '2rem' }}>
|
||
{isEditingProduct && (
|
||
<button type="button" className="btn btn-outline-dark" style={{ flex: 1 }} onClick={() => {
|
||
setIsEditingProduct(false)
|
||
setProductForm({ id: '', title: '', category: 'Apparel', price: 0, stock: 0, sku: '', image: 'https://images.unsplash.com/photo-1544022613-e87ca75a784a?auto=format&fit=crop&q=80&w=100' })
|
||
}}>Cancel</button>
|
||
)}
|
||
<button type="submit" className="btn btn-dark" style={{ flex: 1 }}>
|
||
{isEditingProduct ? 'Save Modifications' : 'Upload Product'}
|
||
</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* ORDERS & TRACKING TAB */}
|
||
{dashTab === 'orders' && (
|
||
<div>
|
||
<h2 className="dashboard-title" style={{ marginBottom: '2rem' }}>Active Orders & Payout status</h2>
|
||
|
||
<div className="dashboard-table-wrapper">
|
||
<table className="dashboard-table">
|
||
<thead>
|
||
<tr>
|
||
<th>Order ID</th>
|
||
<th>Order Date</th>
|
||
<th>Item Details</th>
|
||
<th>Qty</th>
|
||
<th>Customer</th>
|
||
<th>Total</th>
|
||
<th>Status</th>
|
||
<th>Logistics & Carrier Details / Actions</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{orders.map(o => (
|
||
<tr key={o.id}>
|
||
<td><span style={{ fontWeight: 'bold' }}>{o.id}</span></td>
|
||
<td>{o.date}</td>
|
||
<td>{o.item}</td>
|
||
<td>{o.quantity}</td>
|
||
<td>{o.customer}</td>
|
||
<td style={{ fontWeight: 600 }}>${o.total.toFixed(2)}</td>
|
||
<td>
|
||
<span className={`badge ${
|
||
o.status === 'Delivered' ? 'badge-success' :
|
||
o.status === 'Shipped' ? 'badge-info' :
|
||
o.status === 'Pending Acceptance' ? 'badge-warning' :
|
||
o.status === 'Rejected' || o.status === 'Cancelled' ? 'badge-danger' : 'badge-warning'
|
||
}`}>
|
||
{o.status}
|
||
</span>
|
||
</td>
|
||
<td>
|
||
{o.status === 'Pending Acceptance' ? (
|
||
<div style={{ display: 'flex', gap: '0.5rem' }}>
|
||
<button
|
||
className="btn btn-primary"
|
||
style={{ backgroundColor: 'var(--success)', color: 'white', border: 'none', padding: '0.35rem 0.75rem', fontSize: '0.8rem', cursor: 'pointer' }}
|
||
onClick={() => {
|
||
setOrders(orders.map(item => item.id === o.id ? {
|
||
...item,
|
||
status: 'Ready to Ship',
|
||
carrier: 'DHL Express',
|
||
tracking: `DHL-${Math.floor(100000 + Math.random() * 900000)}`,
|
||
eta: '3 Days'
|
||
} : item));
|
||
alert(`Order ${o.id} accepted successfully!`);
|
||
}}
|
||
>
|
||
Accept
|
||
</button>
|
||
<button
|
||
className="btn btn-primary"
|
||
style={{ backgroundColor: 'var(--error)', color: 'white', border: 'none', padding: '0.35rem 0.75rem', fontSize: '0.8rem', cursor: 'pointer' }}
|
||
onClick={() => {
|
||
setOrders(orders.map(item => item.id === o.id ? {
|
||
...item,
|
||
status: 'Rejected',
|
||
carrier: 'N/A',
|
||
tracking: 'N/A',
|
||
eta: 'N/A'
|
||
} : item));
|
||
alert(`Order ${o.id} rejected.`);
|
||
}}
|
||
>
|
||
Reject
|
||
</button>
|
||
</div>
|
||
) : (
|
||
<div style={{ fontSize: '0.85rem' }}>
|
||
<div><strong>Carrier</strong>: {o.carrier}</div>
|
||
<div><strong>Tracking</strong>: {o.tracking}</div>
|
||
<div><strong>ETA / Delivery</strong>: {o.eta}</div>
|
||
</div>
|
||
)}
|
||
</td>
|
||
</tr>
|
||
))}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{dashTab === 'barcode-generator' && (
|
||
<div>
|
||
<h2 className="dashboard-title" style={{ marginBottom: '2rem' }}>Product Barcode Generator</h2>
|
||
<p style={{ color: 'var(--text-muted)', marginBottom: '2rem' }}>
|
||
Generate and print industry-standard barcodes for your product inventory to track shipments, sales, and warehouse stock.
|
||
</p>
|
||
|
||
<div className="tool-split-layout">
|
||
<div className="form-card" style={{ margin: 0, maxWidth: '100%' }}>
|
||
<h3 style={{ marginBottom: '1.5rem', color: 'var(--primary)' }}>Barcode Configuration</h3>
|
||
|
||
<div className="form-group">
|
||
<label htmlFor="barcode-product">Select Product *</label>
|
||
<select
|
||
id="barcode-product"
|
||
className="form-control"
|
||
value={barcodeProductSku}
|
||
onChange={(e) => {
|
||
const sku = e.target.value;
|
||
setBarcodeProductSku(sku);
|
||
setBarcodeValue(sku);
|
||
setBarcodeGenerated(true);
|
||
}}
|
||
>
|
||
<option value="">-- Choose an active listing --</option>
|
||
{products.map(p => (
|
||
<option key={p.id} value={p.sku}>{p.title} ({p.sku})</option>
|
||
))}
|
||
</select>
|
||
</div>
|
||
|
||
<div className="form-group">
|
||
<label htmlFor="barcode-fmt">Symbology / Format *</label>
|
||
<select
|
||
id="barcode-fmt"
|
||
className="form-control"
|
||
value={barcodeFormat}
|
||
onChange={(e) => setBarcodeFormat(e.target.value)}
|
||
>
|
||
<option value="CODE128">Code 128 (Alpha-Numeric, Recommended)</option>
|
||
<option value="EAN13">EAN-13 (Standard Retail Product ID)</option>
|
||
<option value="UPCA">UPC-A (Standard Retail Product ID)</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div className="form-group">
|
||
<label htmlFor="barcode-val">Barcode Value / SKU *</label>
|
||
<input
|
||
id="barcode-val"
|
||
type="text"
|
||
className="form-control"
|
||
value={barcodeValue}
|
||
onChange={(e) => { setBarcodeValue(e.target.value); setBarcodeGenerated(false); }}
|
||
placeholder="Enter alphanumeric identifier"
|
||
/>
|
||
</div>
|
||
|
||
<button
|
||
type="button"
|
||
className="btn btn-dark"
|
||
style={{ width: '100%', marginTop: '1rem' }}
|
||
onClick={() => {
|
||
if (!barcodeValue.trim()) {
|
||
alert('Please enter a barcode value');
|
||
return;
|
||
}
|
||
setBarcodeGenerated(true);
|
||
}}
|
||
>
|
||
Generate Barcode Graphic
|
||
</button>
|
||
</div>
|
||
|
||
<div className="chart-card" style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', textAlign: 'center', minHeight: '300px' }}>
|
||
{barcodeGenerated && barcodeValue ? (
|
||
<div>
|
||
<h4 style={{ marginBottom: '1.5rem', color: 'var(--primary)' }}>Generated Barcode Preview</h4>
|
||
|
||
{/* Beautiful CSS Mock Barcode */}
|
||
<div style={{
|
||
backgroundColor: 'white',
|
||
padding: '1.5rem',
|
||
borderRadius: '8px',
|
||
border: '1px solid var(--border)',
|
||
display: 'inline-block',
|
||
boxShadow: '0 4px 12px rgba(0,0,0,0.05)',
|
||
marginBottom: '1.5rem'
|
||
}}>
|
||
<div style={{ fontSize: '0.8rem', fontWeight: 'bold', color: 'var(--primary)', marginBottom: '0.5rem', textAlign: 'center' }}>
|
||
{products.find(p => p.sku === barcodeProductSku)?.title || 'Custom Identifier'}
|
||
</div>
|
||
|
||
{/* Barcode lines */}
|
||
<div style={{
|
||
display: 'flex',
|
||
alignItems: 'stretch',
|
||
height: '70px',
|
||
width: '240px',
|
||
margin: '0 auto',
|
||
backgroundColor: 'white'
|
||
}}>
|
||
{/* 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 (
|
||
<div
|
||
key={idx}
|
||
style={{
|
||
width: width,
|
||
backgroundColor: isBlack ? 'black' : 'white',
|
||
flexGrow: 1
|
||
}}
|
||
/>
|
||
);
|
||
})}
|
||
</div>
|
||
<div style={{ marginTop: '0.5rem', letterSpacing: '4px', fontSize: '0.9rem', fontWeight: 600, color: 'black' }}>
|
||
{barcodeValue.toUpperCase()}
|
||
</div>
|
||
</div>
|
||
|
||
<div style={{ display: 'flex', gap: '0.5rem', justifyContent: 'center' }}>
|
||
<button
|
||
type="button"
|
||
className="btn btn-outline-dark"
|
||
onClick={() => alert(`Printing barcode sheet for ${barcodeValue}...`)}
|
||
>
|
||
🖨️ Print Label
|
||
</button>
|
||
<button
|
||
type="button"
|
||
className="btn btn-outline-dark"
|
||
onClick={() => alert(`Downloading SVG barcode graphic file...`)}
|
||
>
|
||
💾 Download SVG
|
||
</button>
|
||
</div>
|
||
</div>
|
||
) : (
|
||
<div style={{ color: 'var(--text-muted)' }}>
|
||
<span style={{ fontSize: '3rem' }}>🏷️</span>
|
||
<p style={{ marginTop: '1rem' }}>Enter configuration details and click generate to render visual barcode label.</p>
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* RETURNS MANAGEMENT TAB */}
|
||
{dashTab === 'returns' && (
|
||
<div>
|
||
<h2 className="dashboard-title" style={{ marginBottom: '2rem' }}>Returns & Quality Assurance Center</h2>
|
||
|
||
<h3 style={{ marginBottom: '1rem', color: 'var(--primary)' }}>Items Requested for Return</h3>
|
||
<div className="dashboard-table-wrapper" style={{ marginBottom: '3rem' }}>
|
||
<table className="dashboard-table">
|
||
<thead>
|
||
<tr>
|
||
<th>Return ID</th>
|
||
<th>Original Order ID</th>
|
||
<th>Item Info</th>
|
||
<th>Customer</th>
|
||
<th>Reason Given</th>
|
||
<th>Status</th>
|
||
<th>Actions</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{returns.filter(r => r.status === 'Pending Approval').map(r => (
|
||
<tr key={r.id}>
|
||
<td><strong>{r.id}</strong></td>
|
||
<td>{r.orderId}</td>
|
||
<td style={{ display: 'flex', alignItems: 'center', gap: '0.75rem' }}>
|
||
<img src={r.image} alt={r.item} className="product-thumb" />
|
||
<span>{r.item}</span>
|
||
</td>
|
||
<td>{r.customer}</td>
|
||
<td style={{ fontStyle: 'italic' }}>"{r.reason}"</td>
|
||
<td>
|
||
<span className="badge badge-warning">{r.status}</span>
|
||
</td>
|
||
<td>
|
||
<button className="btn btn-primary" style={{ padding: '0.3rem 0.6rem', fontSize: '0.8rem', marginRight: '0.5rem', backgroundColor: 'var(--success)', border: 'none', color: 'white' }} onClick={() => handleReturnAction(r.id, 'Approved')}>Approve</button>
|
||
<button className="btn btn-primary" style={{ padding: '0.3rem 0.6rem', fontSize: '0.8rem', backgroundColor: 'var(--error)', border: 'none', color: 'white' }} onClick={() => handleReturnAction(r.id, 'Rejected')}>Reject</button>
|
||
</td>
|
||
</tr>
|
||
))}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<h3 style={{ marginBottom: '1rem', color: 'var(--primary)' }}>In-Transit Return Tracking</h3>
|
||
<div className="dashboard-table-wrapper">
|
||
<table className="dashboard-table">
|
||
<thead>
|
||
<tr>
|
||
<th>Return ID</th>
|
||
<th>Item</th>
|
||
<th>Customer</th>
|
||
<th>Return Tracking Number</th>
|
||
<th>Progress Status</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{returns.filter(r => r.status !== 'Pending Approval').map(r => (
|
||
<tr key={r.id}>
|
||
<td><strong>{r.id}</strong></td>
|
||
<td>{r.item}</td>
|
||
<td>{r.customer}</td>
|
||
<td>{r.returningTracking}</td>
|
||
<td>
|
||
<span className={`badge ${r.status === 'Rejected' ? 'badge-danger' : 'badge-info'}`}>
|
||
{r.status}
|
||
</span>
|
||
</td>
|
||
</tr>
|
||
))}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* WALLET & PAYOUTS TAB */}
|
||
{dashTab === 'wallet' && (
|
||
<div>
|
||
<h2 className="dashboard-title" style={{ marginBottom: '2rem' }}>Wallet & Payout Portal</h2>
|
||
|
||
<div className="metrics-row">
|
||
<div className="metric-data-card">
|
||
<div className="metric-title">Outstanding Ready Balance</div>
|
||
<div className="metric-value" style={{ color: 'var(--success)' }}>${wallet.outstanding.toFixed(2)}</div>
|
||
</div>
|
||
<div className="metric-data-card">
|
||
<div className="metric-title">Total Payouts Withdrawn</div>
|
||
<div className="metric-value">${wallet.withdrawn.toFixed(2)}</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="tool-split-layout">
|
||
{/* Withdrawal form */}
|
||
<div className="form-card" style={{ margin: 0, maxWidth: '100%' }}>
|
||
<h3 style={{ marginBottom: '1.5rem', color: 'var(--primary)' }}>Withdraw Payout Funds</h3>
|
||
<form onSubmit={handleWithdrawRequest}>
|
||
<div className="form-group">
|
||
<label htmlFor="payout-val">Withdrawal Amount ($) *</label>
|
||
<input
|
||
id="payout-val"
|
||
type="number"
|
||
className="form-control"
|
||
placeholder="e.g. 200"
|
||
value={withdrawAmount}
|
||
onChange={e => setWithdrawAmount(e.target.value)}
|
||
required
|
||
/>
|
||
</div>
|
||
<button type="submit" className="btn btn-dark" style={{ width: '100%', marginTop: '1.5rem' }}>
|
||
Process Payout Withdrawal
|
||
</button>
|
||
</form>
|
||
</div>
|
||
|
||
{/* Payout History */}
|
||
<div className="chart-card">
|
||
<h3 style={{ marginBottom: '1.5rem', color: 'var(--primary)' }}>Transaction Payout Logs</h3>
|
||
<div className="dashboard-table-wrapper" style={{ border: 'none', boxShadow: 'none' }}>
|
||
<table className="dashboard-table">
|
||
<thead>
|
||
<tr>
|
||
<th>Transaction ID</th>
|
||
<th>Transfer Date</th>
|
||
<th>Amount</th>
|
||
<th>Status</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{wallet.history.map(tx => (
|
||
<tr key={tx.id}>
|
||
<td>{tx.id}</td>
|
||
<td>{tx.date}</td>
|
||
<td style={{ fontWeight: 600 }}>${tx.amount.toFixed(2)}</td>
|
||
<td><span className="badge badge-success">{tx.status}</span></td>
|
||
</tr>
|
||
))}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* SETTINGS TAB */}
|
||
{dashTab === 'settings' && (
|
||
<div className="form-card" style={{ margin: 0, maxWidth: '640px' }}>
|
||
<h2 className="form-card-title" style={{ textAlign: 'left', marginBottom: '2rem' }}>Store Configurations</h2>
|
||
|
||
<form onSubmit={e => { e.preventDefault(); alert('Store configurations updated successfully!'); }}>
|
||
<div className="form-group">
|
||
<label htmlFor="set-store">Store Display Name *</label>
|
||
<input
|
||
id="set-store"
|
||
type="text"
|
||
className="form-control"
|
||
value={storeName}
|
||
onChange={e => setStoreName(e.target.value)}
|
||
required
|
||
/>
|
||
</div>
|
||
|
||
<div className="form-group">
|
||
<label htmlFor="set-logo">Store Logo</label>
|
||
<div className="upload-btn-wrapper">
|
||
<div className="upload-preview-box">
|
||
{storeLogo ? (
|
||
<img src={storeLogo} alt="Logo" />
|
||
) : (
|
||
<span style={{ fontSize: '1.5rem', color: 'var(--text-muted)' }}>🖼️</span>
|
||
)}
|
||
</div>
|
||
<input
|
||
id="set-logo"
|
||
type="file"
|
||
accept="image/*"
|
||
onChange={handleLogoChange}
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="form-group">
|
||
<label htmlFor="set-bio">About Your Craft / Business</label>
|
||
<textarea
|
||
id="set-bio"
|
||
className="form-control"
|
||
rows={3}
|
||
value={businessBio}
|
||
onChange={e => setBusinessBio(e.target.value)}
|
||
></textarea>
|
||
</div>
|
||
|
||
<div className="form-group">
|
||
<label>Pickup Location Address *</label>
|
||
<div style={{ display: 'grid', gridTemplateColumns: '2fr 1fr', gap: '0.75rem', marginBottom: '0.75rem' }}>
|
||
<input
|
||
type="text"
|
||
className="form-control"
|
||
placeholder="Street Address"
|
||
value={address.street}
|
||
onChange={e => setAddress({ ...address, street: e.target.value })}
|
||
required
|
||
/>
|
||
<input
|
||
type="text"
|
||
className="form-control"
|
||
placeholder="City"
|
||
value={address.city}
|
||
onChange={e => setAddress({ ...address, city: e.target.value })}
|
||
required
|
||
/>
|
||
</div>
|
||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '0.75rem' }}>
|
||
<input
|
||
type="text"
|
||
className="form-control"
|
||
placeholder="State"
|
||
value={address.state}
|
||
onChange={e => setAddress({ ...address, state: e.target.value })}
|
||
required
|
||
/>
|
||
<input
|
||
type="text"
|
||
className="form-control"
|
||
placeholder="Pincode"
|
||
value={address.pincode}
|
||
onChange={e => setAddress({ ...address, pincode: e.target.value })}
|
||
required
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="form-group">
|
||
<label htmlFor="set-gstin">GSTIN Number</label>
|
||
<input
|
||
id="set-gstin"
|
||
type="text"
|
||
className="form-control"
|
||
value={gstin}
|
||
onChange={e => setGstin(e.target.value)}
|
||
disabled
|
||
/>
|
||
<p style={{ fontSize: '0.85rem', color: 'var(--text-muted)', marginTop: '0.25rem' }}>GSTIN cannot be modified after verification.</p>
|
||
</div>
|
||
|
||
<button type="submit" className="btn btn-dark" style={{ width: '100%', marginTop: '2rem' }}>
|
||
Save Store Configurations
|
||
</button>
|
||
</form>
|
||
</div>
|
||
)}
|
||
|
||
</section>
|
||
</div>
|
||
)}
|
||
|
||
{/* Footer Section */}
|
||
<footer className="app-footer">
|
||
<div className="footer-content">
|
||
<div className="footer-logo-text">{CONFIG.companyName}</div>
|
||
<div className="footer-links">
|
||
<a href="#about" className="footer-link" onClick={(e) => { e.preventDefault(); navigateTo('about'); }}>About Us</a>
|
||
<a href="#careers" className="footer-link" onClick={(e) => { e.preventDefault(); alert('Careers section coming soon.'); }}>Careers</a>
|
||
<a href="#press" className="footer-link" onClick={(e) => { e.preventDefault(); alert('Press details coming soon.'); }}>Press</a>
|
||
<a href="#terms" className="footer-link" onClick={(e) => { e.preventDefault(); alert('Terms of Service.'); }}>Terms</a>
|
||
<a href="#privacy" className="footer-link" onClick={(e) => { e.preventDefault(); alert('Privacy Policy.'); }}>Privacy</a>
|
||
</div>
|
||
</div>
|
||
<div className="footer-copyright">
|
||
© {new Date().getFullYear()} {CONFIG.companyName}. All rights reserved.
|
||
</div>
|
||
</footer>
|
||
</>
|
||
)
|
||
}
|
||
|
||
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 (
|
||
<div className="form-card" style={{ maxWidth: '640px', margin: '2rem auto', textAlign: 'center', padding: '2.5rem' }}>
|
||
<div style={{ fontSize: '4rem', marginBottom: '1rem' }}>{current.icon}</div>
|
||
<h2 className="form-card-title">{current.title}</h2>
|
||
<p style={{ color: 'var(--text-muted)', fontSize: '1.05rem', margin: '1.5rem 0 2rem' }}>
|
||
{current.description}
|
||
</p>
|
||
|
||
<div style={{ display: 'flex', justifyContent: 'center', gap: '0.5rem', marginBottom: '2rem' }}>
|
||
{steps.map((_, idx) => (
|
||
<div
|
||
key={idx}
|
||
style={{
|
||
width: '12px',
|
||
height: '12px',
|
||
borderRadius: '50%',
|
||
backgroundColor: tourStep === idx + 1 ? 'var(--primary)' : 'var(--border)',
|
||
transition: 'background-color 0.2s'
|
||
}}
|
||
/>
|
||
))}
|
||
</div>
|
||
|
||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||
{tourStep > 1 ? (
|
||
<button type="button" className="btn btn-outline-dark" onClick={() => setTourStep(prev => prev - 1)}>
|
||
Back
|
||
</button>
|
||
) : (
|
||
<div />
|
||
)}
|
||
<button
|
||
type="button"
|
||
className="btn btn-primary"
|
||
style={{ backgroundColor: 'var(--accent)', color: 'var(--primary)', fontWeight: 'bold' }}
|
||
onClick={() => {
|
||
if (tourStep < steps.length) {
|
||
setTourStep(prev => prev + 1)
|
||
} else {
|
||
onComplete()
|
||
}
|
||
}}
|
||
>
|
||
{current.action}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|