2326 lines
105 KiB
TypeScript
2326 lines
105 KiB
TypeScript
import { useState, useEffect } 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 [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 visibility, confirm password and policy states ---
|
||
const [showLoginPass, setShowLoginPass] = useState(false)
|
||
const [showSignupPass, setShowSignupPass] = useState(false)
|
||
const [showSignupConfirmPass, setShowSignupConfirmPass] = useState(false)
|
||
const [confirmPassword, setConfirmPassword] = useState('')
|
||
const [policyAccepted, setPolicyAccepted] = useState(false)
|
||
|
||
// --- 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('')
|
||
|
||
// Load data from backend on mount or when profile is complete / logged in
|
||
useEffect(() => {
|
||
if (isProfileComplete || currentPage === 'dashboard') {
|
||
// Fetch Products
|
||
fetch(`${CONFIG.apiBaseUrl}/api/products/`)
|
||
.then(res => res.json())
|
||
.then(data => {
|
||
if (Array.isArray(data)) setProducts(data);
|
||
})
|
||
.catch(err => console.error('Error fetching products:', err));
|
||
|
||
// Fetch Orders
|
||
fetch(`${CONFIG.apiBaseUrl}/api/orders/`)
|
||
.then(res => res.json())
|
||
.then(data => {
|
||
if (Array.isArray(data)) setOrders(data);
|
||
})
|
||
.catch(err => console.error('Error fetching orders:', err));
|
||
|
||
// Fetch Returns
|
||
fetch(`${CONFIG.apiBaseUrl}/api/returns/`)
|
||
.then(res => res.json())
|
||
.then(data => {
|
||
if (Array.isArray(data)) setReturns(data);
|
||
})
|
||
.catch(err => console.error('Error fetching returns:', err));
|
||
|
||
// Fetch Wallet
|
||
fetch(`${CONFIG.apiBaseUrl}/api/wallet/`)
|
||
.then(res => res.json())
|
||
.then(data => {
|
||
if (data) setWallet(data);
|
||
})
|
||
.catch(err => console.error('Error fetching wallet:', err));
|
||
}
|
||
}, [isProfileComplete, currentPage]);
|
||
|
||
const handleAcceptOrder = (id: string) => {
|
||
fetch(`${CONFIG.apiBaseUrl}/api/orders/${id}/accept/`, {
|
||
method: 'POST'
|
||
})
|
||
.then(res => res.json())
|
||
.then(updatedOrder => {
|
||
setOrders(orders.map(o => o.id === updatedOrder.id ? updatedOrder : o))
|
||
alert(`Order accepted successfully!`)
|
||
})
|
||
.catch(err => console.error(err))
|
||
}
|
||
|
||
const handleRejectOrder = (id: string) => {
|
||
fetch(`${CONFIG.apiBaseUrl}/api/orders/${id}/reject/`, {
|
||
method: 'POST'
|
||
})
|
||
.then(res => res.json())
|
||
.then(updatedOrder => {
|
||
setOrders(orders.map(o => o.id === updatedOrder.id ? updatedOrder : o))
|
||
alert(`Order rejected.`)
|
||
})
|
||
.catch(err => console.error(err))
|
||
}
|
||
|
||
// 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()
|
||
|
||
// Email Validation
|
||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||
if (!emailRegex.test(email)) {
|
||
alert('Please enter a valid email address.');
|
||
return;
|
||
}
|
||
|
||
// Phone Validation
|
||
const phoneRegex = /^[6-9]\d{9}$/;
|
||
if (!phoneRegex.test(phone)) {
|
||
alert('Please enter a valid 10-digit mobile number starting with 6, 7, 8, or 9.');
|
||
return;
|
||
}
|
||
|
||
// Password Validation
|
||
if (password.length < 8) {
|
||
alert('Password must be at least 8 characters long.');
|
||
return;
|
||
}
|
||
if (password !== confirmPassword) {
|
||
alert('Passwords do not match.');
|
||
return;
|
||
}
|
||
|
||
// Policy Validation
|
||
if (!policyAccepted) {
|
||
alert('You must accept the Terms of Service and Privacy Policy.');
|
||
return;
|
||
}
|
||
|
||
navigateTo('profile-completion')
|
||
}
|
||
|
||
const handleLoginSubmit = (e: React.FormEvent) => {
|
||
e.preventDefault()
|
||
|
||
// Email Validation
|
||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||
if (!emailRegex.test(email)) {
|
||
alert('Please enter a valid email address.');
|
||
return;
|
||
}
|
||
|
||
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()
|
||
const url = isEditingProduct
|
||
? `${CONFIG.apiBaseUrl}/api/products/${productForm.id}/`
|
||
: `${CONFIG.apiBaseUrl}/api/products/`
|
||
const method = isEditingProduct ? 'PUT' : 'POST'
|
||
|
||
fetch(url, {
|
||
method: method,
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({
|
||
title: productForm.title,
|
||
category: productForm.category,
|
||
price: String(productForm.price),
|
||
stock: Number(productForm.stock),
|
||
sku: productForm.sku || `PROD-${Date.now().toString().slice(-6)}`,
|
||
image: productForm.image
|
||
})
|
||
})
|
||
.then(res => res.json())
|
||
.then(() => {
|
||
// Refresh products from backend
|
||
fetch(`${CONFIG.apiBaseUrl}/api/products/`)
|
||
.then(r => r.json())
|
||
.then(prods => setProducts(prods))
|
||
setIsEditingProduct(false)
|
||
alert(isEditingProduct ? 'Product modified successfully!' : 'Product uploaded successfully!')
|
||
})
|
||
.catch(err => console.error(err))
|
||
|
||
// 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?')) {
|
||
fetch(`${CONFIG.apiBaseUrl}/api/products/${id}/`, {
|
||
method: 'DELETE'
|
||
})
|
||
.then(() => {
|
||
setProducts(products.filter(p => p.id !== id))
|
||
})
|
||
.catch(err => console.error(err))
|
||
}
|
||
}
|
||
|
||
// 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...'])
|
||
|
||
const mockBulkProducts = [
|
||
{ title: 'Hand-Carved Walnut Bowl', category: 'Kitchenware', price: '65.00', stock: 15, sku: 'BOWL-WAL-12' },
|
||
{ title: 'Organic Cotton Tablecloth', category: 'Linens', price: '48.00', stock: 30, sku: 'LINE-COT-15' }
|
||
]
|
||
|
||
setTimeout(() => {
|
||
fetch(`${CONFIG.apiBaseUrl}/api/products/bulk-upload/`, {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ products: mockBulkProducts })
|
||
})
|
||
.then(res => res.json())
|
||
.then(() => {
|
||
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!'
|
||
])
|
||
// Refresh products
|
||
fetch(`${CONFIG.apiBaseUrl}/api/products/`)
|
||
.then(r => r.json())
|
||
.then(prods => {
|
||
setProducts(prods)
|
||
setIsParsingBulk(false)
|
||
})
|
||
})
|
||
.catch(err => {
|
||
setBulkLog(prev => [...prev, 'Error during upload to server.'])
|
||
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
|
||
}
|
||
|
||
fetch(`${CONFIG.apiBaseUrl}/api/wallet/withdraw/`, {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ amount: String(amount) })
|
||
})
|
||
.then(res => {
|
||
if (!res.ok) throw new Error('Insufficient funds or invalid request');
|
||
return res.json();
|
||
})
|
||
.then(updatedWallet => {
|
||
setWallet(updatedWallet)
|
||
setWithdrawAmount('')
|
||
alert(`Payout of $${amount} successfully transferred!`)
|
||
})
|
||
.catch(err => {
|
||
alert(err.message)
|
||
})
|
||
}
|
||
|
||
// Returns actions
|
||
const handleReturnAction = (id: string, action: 'Approved' | 'Rejected') => {
|
||
fetch(`${CONFIG.apiBaseUrl}/api/returns/${id}/action/`, {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ action: action })
|
||
})
|
||
.then(res => res.json())
|
||
.then(updatedReturn => {
|
||
setReturns(returns.map(ret => ret.id === id ? updatedReturn : ret))
|
||
alert(`Return request ${action.toLowerCase()}!`)
|
||
})
|
||
.catch(err => console.error(err))
|
||
}
|
||
|
||
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>
|
||
<div style={{ position: 'relative' }}>
|
||
<input
|
||
id="login-password"
|
||
type={showLoginPass ? 'text' : 'password'}
|
||
className="form-control"
|
||
placeholder="Enter your password"
|
||
value={password}
|
||
onChange={(e) => setPassword(e.target.value)}
|
||
required
|
||
style={{ paddingRight: '45px' }}
|
||
/>
|
||
<button
|
||
type="button"
|
||
onClick={() => setShowLoginPass(!showLoginPass)}
|
||
style={{
|
||
position: 'absolute',
|
||
right: '12px',
|
||
top: '50%',
|
||
transform: 'translateY(-50%)',
|
||
background: 'none',
|
||
border: 'none',
|
||
cursor: 'pointer',
|
||
fontSize: '1.1rem',
|
||
color: 'var(--text-muted)'
|
||
}}
|
||
>
|
||
{showLoginPass ? '👁️' : '🙈'}
|
||
</button>
|
||
</div>
|
||
</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>
|
||
<div style={{ position: 'relative' }}>
|
||
<input
|
||
id="reg-pass"
|
||
type={showSignupPass ? 'text' : 'password'}
|
||
className="form-control"
|
||
placeholder="Minimum 8 characters"
|
||
value={password}
|
||
onChange={(e) => setPassword(e.target.value)}
|
||
required
|
||
style={{ paddingRight: '45px' }}
|
||
/>
|
||
<button
|
||
type="button"
|
||
onClick={() => setShowSignupPass(!showSignupPass)}
|
||
style={{
|
||
position: 'absolute',
|
||
right: '12px',
|
||
top: '50%',
|
||
transform: 'translateY(-50%)',
|
||
background: 'none',
|
||
border: 'none',
|
||
cursor: 'pointer',
|
||
fontSize: '1.1rem',
|
||
color: 'var(--text-muted)'
|
||
}}
|
||
>
|
||
{showSignupPass ? '👁️' : '🙈'}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="form-group">
|
||
<label htmlFor="reg-confirm-pass">Confirm Password *</label>
|
||
<div style={{ position: 'relative' }}>
|
||
<input
|
||
id="reg-confirm-pass"
|
||
type={showSignupConfirmPass ? 'text' : 'password'}
|
||
className="form-control"
|
||
placeholder="Re-enter password"
|
||
value={confirmPassword}
|
||
onChange={(e) => setConfirmPassword(e.target.value)}
|
||
required
|
||
style={{ paddingRight: '45px' }}
|
||
/>
|
||
<button
|
||
type="button"
|
||
onClick={() => setShowSignupConfirmPass(!showSignupConfirmPass)}
|
||
style={{
|
||
position: 'absolute',
|
||
right: '12px',
|
||
top: '50%',
|
||
transform: 'translateY(-50%)',
|
||
background: 'none',
|
||
border: 'none',
|
||
cursor: 'pointer',
|
||
fontSize: '1.1rem',
|
||
color: 'var(--text-muted)'
|
||
}}
|
||
>
|
||
{showSignupConfirmPass ? '👁️' : '🙈'}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="form-group" style={{ display: 'flex', gap: '0.5rem', alignItems: 'center', margin: '1.25rem 0' }}>
|
||
<input
|
||
id="reg-policy"
|
||
type="checkbox"
|
||
checked={policyAccepted}
|
||
onChange={(e) => setPolicyAccepted(e.target.checked)}
|
||
required
|
||
/>
|
||
<label htmlFor="reg-policy" style={{ fontSize: '0.85rem', color: 'var(--text-muted)', marginBottom: 0, cursor: 'pointer' }}>
|
||
I accept the <a href="#terms" onClick={(e) => e.preventDefault()}>Terms of Service</a> and <a href="#privacy" onClick={(e) => e.preventDefault()}>Privacy Policy</a> *
|
||
</label>
|
||
</div>
|
||
|
||
<button type="submit" className="btn btn-primary" style={{ width: '100%', padding: '0.75rem', marginTop: '0.5rem' }}>
|
||
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={isGstinVerified}
|
||
style={{ minWidth: '120px' }}
|
||
>
|
||
{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>${Number(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 }}>${Number(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={() => handleAcceptOrder(o.id)}
|
||
>
|
||
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={() => handleRejectOrder(o.id)}
|
||
>
|
||
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)' }}>${Number(wallet.outstanding).toFixed(2)}</div>
|
||
</div>
|
||
<div className="metric-data-card">
|
||
<div className="metric-title">Total Payouts Withdrawn</div>
|
||
<div className="metric-value">${Number(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 || (wallet as any).transactions || []).map((tx: any) => (
|
||
<tr key={tx.id || tx.tx_id}>
|
||
<td>{tx.id || tx.tx_id}</td>
|
||
<td>{tx.date}</td>
|
||
<td style={{ fontWeight: 600 }}>${Number(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: "Supplier Account Under Review ⏳",
|
||
description: `Thank you for completing your profile! Your GSTIN, PAN, and Aadhaar card details have been submitted. Our compliance team is verifying your documents. This review is typically completed within the next 24 hours. While we verify your credentials, let's take a quick animated tour to get you familiar with your dashboard!`,
|
||
icon: "⏳",
|
||
action: "Start Guided 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: "Explore Dashboard 🚀"
|
||
}
|
||
]
|
||
|
||
const current = steps[tourStep - 1]
|
||
|
||
return (
|
||
<div key={tourStep} className="form-card tour-card-animated" style={{ maxWidth: '640px', margin: '2rem auto', textAlign: 'center', padding: '2.5rem' }}>
|
||
<div className="tour-icon-animated" style={{ fontSize: '4.5rem', 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', lineHeight: '1.6' }}>
|
||
{current.description}
|
||
</p>
|
||
|
||
<div style={{ display: 'flex', justifyContent: 'center', gap: '0.5rem', marginBottom: '2rem' }}>
|
||
{steps.map((_, idx) => (
|
||
<div
|
||
key={idx}
|
||
className={tourStep === idx + 1 ? 'tour-dot-active' : ''}
|
||
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>
|
||
)
|
||
}
|