supplier_central_frontend/src/App.tsx

3186 lines
166 KiB
TypeScript
Raw Normal View History

import { useState, useEffect } from 'react'
import { CONFIG, apiFetch } from './config'
2026-08-09 05:29:03 +00:00
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' | 'analytics'
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
2026-08-09 05:29:03 +00:00
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')
2026-08-09 01:53:57 +00:00
2026-08-09 05:29:03 +00:00
// 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('')
2026-08-09 05:29:03 +00:00
// 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')
2026-08-09 05:29:03 +00:00
const [isGstinVerified, setIsGstinVerified] = useState(false)
const [aadharFile, setAadharFile] = useState<string | null>(null)
const [panFile, setPanFile] = useState<string | null>(null)
2026-08-12 12:48:47 +00:00
const [aadharS3Key, setAadharS3Key] = useState<string | null>(null)
const [panS3Key, setPanS3Key] = useState<string | null>(null)
2026-08-09 01:53:57 +00:00
// New onboarding customization states
const [businessType, setBusinessType] = useState<'registered_company' | 'self_help_group' | 'individual_maker'>('registered_company')
const [storeSlug, setStoreSlug] = useState('')
const [supportEmail, setSupportEmail] = useState('')
const [supportPhone, setSupportPhone] = useState('')
const [selectedCategories, setSelectedCategories] = useState<string[]>([])
2026-08-09 05:29:03 +00:00
// Step 3: Store and Location details
const [storeName, setStoreName] = useState('My Artisan Handloom')
const [storeLogo, setStoreLogo] = useState<string | null>(null)
2026-08-12 12:48:47 +00:00
const [logoS3Key, setLogoS3Key] = 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'
})
2026-08-09 05:29:03 +00:00
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)
2026-08-09 05:29:03 +00:00
// --- 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 [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' })
2026-08-09 01:53:57 +00:00
// Lists
const [products, setProducts] = useState<Product[]>([])
const [orders, setOrders] = useState<Order[]>([])
const [returns, setReturns] = useState<ReturnRequest[]>([])
2026-08-09 01:53:57 +00:00
// 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)
2026-08-09 01:53:57 +00:00
// New Heritage UI wizard states
const [productWizardStep, setProductWizardStep] = useState<number>(0) // 0: Catalog list, 1-5: Product upload wizard steps
const [productFormDetails, setProductFormDetails] = useState({
id: '',
name: '',
category: 'Textiles & Apparel',
description: '',
isGiTagged: false,
primaryImage: 'https://images.unsplash.com/photo-1544022613-e87ca75a784a?auto=format&fit=crop&q=80&w=300',
additionalViews: [] as string[],
videoUrl: '',
view360Url: '',
basePrice: '',
compareAtPrice: '',
trackInventory: true,
sku: '',
initialStock: '',
shippingProfile: 'Standard Fragile',
processingDays: 3,
packageWeight: 1.0
})
const [selectedOrderDetail, setSelectedOrderDetail] = useState<any | null>(null)
const [orderNotes, setOrderNotes] = useState<string>('')
// 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
2026-08-12 12:48:47 +00:00
useEffect(() => {
const token = localStorage.getItem('access_token');
if (token) {
apiFetch(`${CONFIG.apiBaseUrl}/api/profile/`)
.then(res => {
if (res.ok) return res.json();
throw new Error('Session expired');
})
.then(user => {
if (user && user.profile) {
const profile = user.profile;
setPhone(profile.phone || '');
setPhoneVerified(profile.phone_verified || false);
setEmail(user.email || '');
setEmailVerified(profile.email_verified || false);
setGstin(profile.gstin || '29AAAAA1111A1Z1');
setIsGstinVerified(profile.is_gstin_verified || false);
setAadharFile(profile.aadhar_file || null);
setPanFile(profile.pan_file || null);
setAadharS3Key(profile.aadhar_s3_key || null);
setPanS3Key(profile.pan_s3_key || null);
setBusinessType(profile.business_type || 'registered_company');
setStoreSlug(profile.store_slug || '');
setSupportEmail(profile.support_email || '');
setSupportPhone(profile.support_phone || '');
setSelectedCategories(profile.categories || []);
2026-08-12 12:48:47 +00:00
setStoreName(profile.store_name || 'My Artisan Handloom');
setStoreLogo(profile.store_logo || null);
setLogoS3Key(profile.logo_s3_key || null);
setBusinessBio(profile.business_bio || 'Traditional weaving and local sustainable designs.');
setAddress({
street: profile.street || '123 Handloom Lane',
city: profile.city || 'Textile Town',
state: profile.state || 'Karnataka',
pincode: profile.pincode || '560001'
});
if (profile.latitude && profile.longitude) {
setMapCoordinates({ lat: Number(profile.latitude), lng: Number(profile.longitude) });
}
const step = profile.onboarding_step;
if (step >= 7) {
2026-08-12 12:48:47 +00:00
setIsProfileComplete(true);
setCurrentPage('dashboard');
} else {
setProfileStep(step);
setIsProfileComplete(false);
setCurrentPage('profile-completion');
}
}
})
.catch(err => {
console.error('Session restore failed:', err);
localStorage.removeItem('access_token');
});
}
}, []);
useEffect(() => {
if (isProfileComplete || currentPage === 'dashboard') {
// Fetch Products
apiFetch(`${CONFIG.apiBaseUrl}/api/products/`)
.then(res => res.json())
.then(data => {
if (Array.isArray(data)) setProducts(data);
else if (data && Array.isArray(data.results)) setProducts(data.results);
})
.catch(err => console.error('Error fetching products:', err));
// Fetch Orders
apiFetch(`${CONFIG.apiBaseUrl}/api/orders/`)
.then(res => res.json())
.then(data => {
if (Array.isArray(data)) setOrders(data);
else if (data && Array.isArray(data.results)) setOrders(data.results);
})
.catch(err => console.error('Error fetching orders:', err));
// Fetch Returns
apiFetch(`${CONFIG.apiBaseUrl}/api/returns/`)
.then(res => res.json())
.then(data => {
if (Array.isArray(data)) setReturns(data);
else if (data && Array.isArray(data.results)) setReturns(data.results);
})
.catch(err => console.error('Error fetching returns:', err));
// Fetch Wallet
apiFetch(`${CONFIG.apiBaseUrl}/api/wallet/`)
.then(res => res.json())
.then(data => {
if (data) setWallet(data);
})
.catch(err => console.error('Error fetching wallet:', err));
}
}, [isProfileComplete, currentPage]);
2026-08-12 12:48:47 +00:00
const uploadDocument = async (file: File, fileType: string) => {
try {
const response = await apiFetch(`${CONFIG.apiBaseUrl}/api/profile/presigned-url/`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ file_type: fileType, content_type: file.type })
});
if (!response.ok) throw new Error('Failed to get presigned URL');
const data = await response.json();
// Simulate file upload PUT to mock presigned_url
await fetch(data.presigned_url, {
method: 'PUT',
body: file,
headers: { 'Content-Type': file.type }
}).catch(err => console.log('Mock S3 upload:', err));
return data.s3_key;
} catch (err) {
console.error(err);
return `suppliers/default/${fileType}.jpg`;
}
};
const handleAcceptOrder = (id: string) => {
apiFetch(`${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) => {
apiFetch(`${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
const [bulkLog, setBulkLog] = useState<string[]>([])
const [isParsingBulk, setIsParsingBulk] = useState(false)
const [bulkCsvFile, setBulkCsvFile] = useState<File | null>(null)
const [bulkZipFile, setBulkZipFile] = useState<File | null>(null)
// GSTIN verification simulator
const handleVerifyGstin = () => {
2026-08-12 12:48:47 +00:00
if (!gstin.trim() || gstin.length !== 15) {
alert('Invalid GSTIN length. Must be 15 chars.');
return;
}
apiFetch(`${CONFIG.apiBaseUrl}/api/profile/submit-gstin/`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ gstin })
})
.then(res => {
if (!res.ok) throw new Error('Failed to verify GSTIN.');
return res.json();
})
.then(data => {
if (data.verified) {
setIsGstinVerified(true);
}
})
.catch(err => {
alert(err.message);
});
}
// Handle Logo Upload simulation
2026-08-12 12:48:47 +00:00
const handleLogoChange = async (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)
2026-08-12 12:48:47 +00:00
const s3Key = await uploadDocument(file, 'logo');
setLogoS3Key(s3Key);
}
}
2026-08-09 05:29:03 +00:00
// 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' })
}
2026-08-10 12:32:20 +00:00
const handleLogout = () => {
apiFetch(`${CONFIG.apiBaseUrl}/api/auth/logout/`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' }
})
.catch(err => console.error('Error logging out:', err))
.finally(() => {
2026-08-12 12:48:47 +00:00
localStorage.removeItem('access_token')
2026-08-10 12:32:20 +00:00
// Reset all onboarding & profile states to their defaults
setIsProfileComplete(false)
setProfileStep(1)
setEmail('')
setPhone('')
setPassword('')
setConfirmPassword('')
setPolicyAccepted(false)
setPhoneVerified(false)
setEmailVerified(false)
setPhoneOtpSent(false)
setEmailOtpSent(false)
setEnteredPhoneOtp('')
setEnteredEmailOtp('')
setGstin('29AAAAA1111A1Z1')
setIsGstinVerified(false)
setAadharFile(null)
setPanFile(null)
2026-08-12 12:48:47 +00:00
setAadharS3Key(null)
setPanS3Key(null)
2026-08-10 12:32:20 +00:00
setStoreName('My Artisan Handloom')
setStoreLogo(null)
2026-08-12 12:48:47 +00:00
setLogoS3Key(null)
2026-08-10 12:32:20 +00:00
setBusinessBio('Traditional weaving and local sustainable designs.')
setAddress({
street: '123 Handloom Lane',
city: 'Textile Town',
state: 'Karnataka',
pincode: '560001'
})
setMapCoordinates({ lat: 12.9716, lng: 77.5946 })
navigateTo('home', true)
})
}
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;
}
apiFetch(`${CONFIG.apiBaseUrl}/api/auth/register/`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
username: email,
email: email,
phone: phone,
2026-08-12 12:48:47 +00:00
password: password,
confirm_password: confirmPassword,
policy_accepted: policyAccepted
})
})
.then(res => {
if (!res.ok) throw new Error('Registration failed. Username/email might already be taken.');
return res.json();
})
.then(() => {
// Auto login after signup
apiFetch(`${CONFIG.apiBaseUrl}/api/auth/login/`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username: email, password: password })
})
2026-08-12 12:48:47 +00:00
.then(res => {
if (!res.ok) throw new Error('Auto-login failed.');
return res.json();
})
.then(data => {
localStorage.setItem('access_token', data.access_token);
navigateTo('profile-completion')
})
})
.catch(err => {
alert(err.message);
});
}
const handleLoginSubmit = (e: React.FormEvent) => {
e.preventDefault()
if (!email.trim()) {
alert('Please enter your email or phone number.');
return;
}
apiFetch(`${CONFIG.apiBaseUrl}/api/auth/login/`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username: email, password: password })
})
.then(res => {
if (!res.ok) throw new Error('Invalid credentials.');
return res.json();
})
2026-08-12 12:48:47 +00:00
.then(data => {
localStorage.setItem('access_token', data.access_token);
if (data.user && data.user.profile) {
const profile = data.user.profile;
setPhone(profile.phone || '');
setPhoneVerified(profile.phone_verified || false);
setEmail(data.user.email || '');
setEmailVerified(profile.email_verified || false);
setGstin(profile.gstin || '29AAAAA1111A1Z1');
setIsGstinVerified(profile.is_gstin_verified || false);
setAadharFile(profile.aadhar_file || null);
setPanFile(profile.pan_file || null);
setAadharS3Key(profile.aadhar_s3_key || null);
setPanS3Key(profile.pan_s3_key || null);
setStoreName(profile.store_name || 'My Artisan Handloom');
setStoreLogo(profile.store_logo || null);
setLogoS3Key(profile.logo_s3_key || null);
setBusinessBio(profile.business_bio || 'Traditional weaving and local sustainable designs.');
setAddress({
street: profile.street || '123 Handloom Lane',
city: profile.city || 'Textile Town',
state: profile.state || 'Karnataka',
pincode: profile.pincode || '560001'
});
if (profile.latitude && profile.longitude) {
setMapCoordinates({ lat: Number(profile.latitude), lng: Number(profile.longitude) });
}
const step = profile.onboarding_step;
if (step >= 4) {
setIsProfileComplete(true);
navigateTo('dashboard', true);
} else {
setProfileStep(step);
setIsProfileComplete(false);
navigateTo('profile-completion');
}
} else {
setIsProfileComplete(false);
navigateTo('profile-completion');
}
})
.catch(err => {
alert(err.message);
});
}
2026-08-12 12:48:47 +00:00
const saveProfileBackend = (isComplete: boolean) => {
return apiFetch(`${CONFIG.apiBaseUrl}/api/profile/`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
phone: phone,
phone_verified: phoneVerified,
email_verified: emailVerified,
gstin: gstin,
is_gstin_verified: isGstinVerified,
business_type: businessType,
store_slug: storeSlug,
support_email: supportEmail,
support_phone: supportPhone,
categories: selectedCategories,
2026-08-12 12:48:47 +00:00
aadhar_file: aadharFile,
pan_file: panFile,
aadhar_s3_key: aadharS3Key || 'suppliers/default/aadhar.pdf',
pan_s3_key: panS3Key || 'suppliers/default/pan.pdf',
store_name: storeName,
store_logo: storeLogo || 'https://images.unsplash.com/photo-1513519245088-0e12902e5a38?auto=format&fit=crop&q=80&w=800',
logo_s3_key: logoS3Key || 'suppliers/default/logo.jpg',
business_bio: businessBio,
street: address.street,
city: address.city,
state: address.state,
pincode: address.pincode,
latitude: mapCoordinates.lat,
longitude: mapCoordinates.lng,
is_profile_complete: isComplete
})
})
.then(res => {
if (!res.ok) throw new Error('Failed to update profile.');
return res.json();
});
};
const handleProfileSubmit = (e: React.FormEvent) => {
e.preventDefault()
if (profileStep === 2) {
setIsGstinVerified(true)
}
const isLastStep = profileStep === 6;
2026-08-12 12:48:47 +00:00
saveProfileBackend(isLastStep)
.then(() => {
if (!isLastStep) {
setProfileStep(prev => prev + 1)
} else {
setProfileStep(7);
2026-08-12 12:48:47 +00:00
}
})
.catch(err => {
alert(err.message);
});
}
// --- Dashboard Logic Actions ---
2026-08-09 01:53:57 +00:00
// 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'
apiFetch(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
apiFetch(`${CONFIG.apiBaseUrl}/api/products/`)
.then(r => r.json())
.then(prods => {
if (Array.isArray(prods)) setProducts(prods);
else if (prods && Array.isArray(prods.results)) setProducts(prods.results);
})
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?')) {
apiFetch(`${CONFIG.apiBaseUrl}/api/products/${id}/`, {
method: 'DELETE'
})
.then(() => {
setProducts(products.filter(p => p.id !== id))
})
.catch(err => console.error(err))
}
}
const handleBulkUploadSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!bulkCsvFile) {
alert("Please select a CSV file to upload.");
return;
}
setIsParsingBulk(true);
setBulkLog(['Uploading files to server...', 'Parsing CSV data and extracting ZIP images...']);
2026-08-09 01:53:57 +00:00
const formData = new FormData();
formData.append('csv_file', bulkCsvFile);
if (bulkZipFile) {
formData.append('zip_file', bulkZipFile);
}
try {
const token = localStorage.getItem('access_token');
const response = await fetch(`${CONFIG.apiBaseUrl}/api/products/bulk-upload/`, {
method: 'POST',
headers: {
...(token ? { 'Authorization': `Bearer ${token}` } : {})
},
body: formData
});
const data = await response.json();
if (!response.ok) {
throw new Error(data.error || 'Failed to complete bulk upload.');
}
setBulkLog([
'CSV file parsed successfully.',
`Extracted and matched images for SKUs from ZIP file.`,
`Successfully added ${data.products?.length || 0} product listings!`,
]);
// Refresh product list
const prodRes = await apiFetch(`${CONFIG.apiBaseUrl}/api/products/`);
const prods = await prodRes.json();
if (Array.isArray(prods)) {
setProducts(prods);
} else if (prods && Array.isArray(prods.results)) {
setProducts(prods.results);
}
setBulkCsvFile(null);
setBulkZipFile(null);
} catch (err: any) {
setBulkLog(prev => [...prev, `Error: ${err.message}`]);
} finally {
setIsParsingBulk(false);
}
};
// 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
}
apiFetch(`${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') => {
apiFetch(`${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))
}
// Compute realtime metrics based on state loaded from backend
const computeRealtimeMetrics = () => {
const totalSales = orders
.filter(o => o.status !== 'Cancelled' && o.status !== 'Rejected')
.reduce((sum, o) => sum + Number(o.total), 0)
const totalEarned = Number(wallet.outstanding) + Number(wallet.withdrawn)
const totalStock = products.reduce((sum, p) => sum + Number(p.stock), 0)
const totalReturns = returns.length
const isLoggedIn = currentPage === 'dashboard' || currentPage === 'profile-completion' || currentPage === 'welcome-tour' || isProfileComplete
if (isLoggedIn) {
return {
totalSales,
totalEarned,
stockDetails: totalStock,
returnedItems: totalReturns,
chartValues: orders.length > 0
? orders.map(o => Number(o.total))
: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
}
}
return {
totalSales: 45280.00,
totalEarned: 38488.00,
stockDetails: 342,
returnedItems: 12,
chartValues: CONFIG.dashboardData.filters[dateFilter]?.chartValues || [30, 45, 35, 60, 50, 75, 65, 80, 70, 95, 90, 110]
}
}
const selectedMetrics = computeRealtimeMetrics()
return (
<>
{/* Dynamic Header */}
<nav className="w-full top-0 sticky z-50 bg-white border-b border-[#D9C5B2] transition-all duration-300">
<div className="flex justify-between items-center max-w-7xl mx-auto px-8 py-4">
<button className="font-caslon text-2xl font-bold text-primary bg-transparent border-none cursor-pointer" onClick={() => navigateTo('home')}>
Tradhox
</button>
<div className="hidden md:flex gap-8 items-center text-sm font-semibold">
<button className="text-secondary hover:text-primary bg-transparent border-none cursor-pointer" onClick={() => navigateTo('home')}>Shop</button>
<button className="text-secondary hover:text-primary bg-transparent border-none cursor-pointer" onClick={() => navigateTo('about')}>Artisans</button>
<button className="text-secondary hover:text-primary bg-transparent border-none cursor-pointer" onClick={() => navigateTo('about')}>Our Story</button>
<button className="text-secondary hover:text-primary bg-transparent border-none cursor-pointer" onClick={() => navigateTo('contact')}>Support</button>
</div>
<div className="flex items-center gap-4 text-sm font-semibold">
{currentPage === 'dashboard' ? (
<button className="text-secondary hover:text-primary bg-transparent border-none cursor-pointer" onClick={handleLogout}>
Logout
</button>
) : (
<>
<button
className="text-secondary hover:text-primary bg-transparent border-none cursor-pointer"
onClick={() => { setActiveTab('login'); navigateTo('login'); }}
>
Login
</button>
<button
className="bg-primary text-white px-4 py-2 rounded font-semibold transition-opacity hover:opacity-90 cursor-pointer"
onClick={() => { setActiveTab('register'); navigateTo('signup'); }}
>
Get Started
</button>
</>
)}
</div>
</div>
</nav>
{/* Main Content Area */}
{currentPage !== 'dashboard' ? (
<main className={`main-content ${currentPage === 'home' ? 'full-width' : ''}`}>
2026-08-09 01:53:57 +00:00
{/* HOMEPAGE VIEW */}
{currentPage === 'home' && (
<div className="animate-fadeIn">
{/* Hero Section */}
<section className="relative w-full min-h-[450px] sm:min-h-[550px] md:h-[600px] flex items-center justify-center overflow-hidden py-12 sm:py-0">
<div className="absolute inset-0 z-0">
<img src="/artisan_weaving.jpg" className="w-full h-full object-cover" />
<div className="absolute inset-0 bg-black/55"></div>
</div>
<div className="relative z-10 text-center max-w-3xl px-4 sm:px-8 mx-auto text-white space-y-4 sm:space-y-6">
<h1 className="font-caslon text-3xl sm:text-5xl md:text-6xl font-bold leading-tight">Sell Globally. Celebrate Craft.</h1>
<p className="text-sm sm:text-lg md:text-xl max-w-2xl mx-auto opacity-90 leading-relaxed">Discover authentic Indian craftsmanship, preserved for generations and handcrafted for you.</p>
<div className="flex flex-col sm:flex-row gap-3 sm:gap-4 justify-center pt-2 sm:pt-4 max-w-md mx-auto sm:max-w-none">
<button className="bg-primary text-white px-6 py-2.5 sm:px-8 sm:py-3 rounded font-semibold hover:opacity-90 transition-opacity flex items-center justify-center gap-2 cursor-pointer text-sm sm:text-base" onClick={() => { setActiveTab('register'); navigateTo('signup'); }}>
Shop the Collection
<span className="material-symbols-outlined text-lg">arrow_forward</span>
</button>
<button className="bg-transparent border border-white text-white px-6 py-2.5 sm:px-8 sm:py-3 rounded font-semibold hover:bg-white/10 transition-colors cursor-pointer text-sm sm:text-base" onClick={() => navigateTo('about')}>
Meet our Artisans
</button>
</div>
</div>
</section>
{/* Discover Heritage Cards */}
<section className="max-w-7xl mx-auto px-4 sm:px-8 py-12 sm:py-16">
<h2 className="font-caslon text-2xl sm:text-3xl font-bold text-center text-primary mb-8 sm:mb-12">Discover Heritage</h2>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 sm:gap-6">
{/* Category 1 */}
<div className="group relative overflow-hidden rounded border border-[#D9C5B2] bg-white hover:border-primary transition-colors block aspect-[4/5] cursor-pointer">
<img className="w-full h-full object-cover transition-transform duration-500 group-hover:scale-105" src="/terracotta_vases.jpg" />
<div className="absolute inset-0 bg-gradient-to-t from-black/60 to-transparent"></div>
<div className="absolute bottom-0 left-0 p-4 w-full text-white">
<h3 className="font-caslon text-lg font-bold mb-1">Hand-Thrown Pottery</h3>
<p className="text-xs opacity-90">Explore Collection</p>
</div>
</div>
{/* Category 2 */}
<div className="group relative overflow-hidden rounded border border-[#D9C5B2] bg-white hover:border-primary transition-colors block aspect-[4/5] cursor-pointer">
<img className="w-full h-full object-cover transition-transform duration-500 group-hover:scale-105" src="/kanchipuram_silk.jpg" />
<div className="absolute inset-0 bg-gradient-to-t from-black/60 to-transparent"></div>
<div className="absolute bottom-0 left-0 p-4 w-full text-white">
<h3 className="font-caslon text-lg font-bold mb-1">Traditional Textiles</h3>
<p className="text-xs opacity-90">Explore Collection</p>
</div>
</div>
{/* Category 3 */}
<div className="group relative overflow-hidden rounded border border-[#D9C5B2] bg-white hover:border-primary transition-colors block aspect-[4/5] cursor-pointer">
<img className="w-full h-full object-cover transition-transform duration-500 group-hover:scale-105" src="/terracotta_vases.jpg" />
<div className="absolute inset-0 bg-gradient-to-t from-black/60 to-transparent"></div>
<div className="absolute bottom-0 left-0 p-4 w-full text-white">
<h3 className="font-caslon text-lg font-bold mb-1">Metallic Arts</h3>
<p className="text-xs opacity-90">Explore Collection</p>
</div>
</div>
{/* Category 4 */}
<div className="group relative overflow-hidden rounded border border-[#D9C5B2] bg-white hover:border-primary transition-colors block aspect-[4/5] cursor-pointer">
<img className="w-full h-full object-cover transition-transform duration-500 group-hover:scale-105" src="/kanchipuram_silk.jpg" />
<div className="absolute inset-0 bg-gradient-to-t from-black/60 to-transparent"></div>
<div className="absolute bottom-0 left-0 p-4 w-full text-white">
<h3 className="font-caslon text-lg font-bold mb-1">Wooden Heritage</h3>
<p className="text-xs opacity-90">Explore Collection</p>
</div>
</div>
</div>
</section>
{/* The Tradhox Promise */}
<section className="bg-surface-container-low py-12 sm:py-16 border-y border-outline-variant/30">
<div className="max-w-7xl mx-auto px-4 sm:px-8">
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 sm:gap-8 text-center">
<div className="flex flex-col items-center p-4">
<span className="material-symbols-outlined text-primary text-4xl mb-2">verified</span>
<h3 className="font-caslon text-xl font-bold text-primary mb-2">Expand Your Reach</h3>
<p className="text-sm text-secondary max-w-xs mx-auto">Every piece is verified for its origin and traditional crafting methods, ensuring you receive true heritage art.</p>
</div>
<div className="flex flex-col items-center p-4">
<span className="material-symbols-outlined text-primary text-4xl mb-2">handshake</span>
<h3 className="font-caslon text-xl font-bold text-primary mb-2">Easy Inventory Tools</h3>
<p className="text-sm text-secondary max-w-xs mx-auto">We empower creators directly, bypassing intermediaries to foster sustainable livelihoods for artisanal communities.</p>
</div>
<div className="flex flex-col items-center p-4">
<span className="material-symbols-outlined text-primary text-4xl mb-2">eco</span>
<h3 className="font-caslon text-xl font-bold text-primary mb-2">Secure Payments</h3>
<p className="text-sm text-secondary max-w-xs mx-auto">Committed to eco-friendly materials and ethical production processes that respect both people and the planet.</p>
</div>
</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>
2026-08-09 01:53:57 +00:00
<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>
2026-08-09 01:53:57 +00:00
<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>
2026-08-09 01:53:57 +00:00
<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">
2026-08-09 01:53:57 +00:00
<button
className={`tab-btn ${activeTab === 'login' ? 'active' : ''}`}
onClick={() => { setActiveTab('login'); navigateTo('login'); }}
>
Login
</button>
2026-08-09 01:53:57 +00:00
<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 or Phone Number</label>
2026-08-09 01:53:57 +00:00
<input
id="login-email"
type="text"
2026-08-09 01:53:57 +00:00
className="form-control"
placeholder="Enter your email or phone number"
value={email}
onChange={(e) => setEmail(e.target.value)}
2026-08-09 01:53:57 +00:00
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' }}>
2026-08-09 05:29:03 +00:00
<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' }}>
2026-08-09 05:29:03 +00:00
<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>
2026-08-09 01:53:57 +00:00
<input
id="reg-email"
2026-08-09 01:53:57 +00:00
type="email"
className="form-control"
placeholder="Enter email address"
value={email}
onChange={(e) => setEmail(e.target.value)}
2026-08-09 01:53:57 +00:00
required
/>
</div>
<div className="form-group">
<label htmlFor="reg-phone">Mobile Number *</label>
2026-08-09 01:53:57 +00:00
<input
id="reg-phone"
2026-08-09 01:53:57 +00:00
type="tel"
className="form-control"
placeholder="Enter 10-digit number"
value={phone}
onChange={(e) => setPhone(e.target.value)}
2026-08-09 01:53:57 +00:00
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' }}>
2026-08-09 01:53:57 +00:00
<input
id="reg-policy"
type="checkbox"
checked={policyAccepted}
onChange={(e) => setPolicyAccepted(e.target.checked)}
2026-08-09 01:53:57 +00:00
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>
)}
2026-08-09 05:29:03 +00:00
{/* 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>
2026-08-09 01:53:57 +00:00
<input
2026-08-09 05:29:03 +00:00
id="reset-email"
2026-08-09 01:53:57 +00:00
type="text"
className="form-control"
2026-08-09 05:29:03 +00:00
placeholder="name@example.com or 10-digit number"
value={resetEmail}
onChange={(e) => setResetEmail(e.target.value)}
2026-08-09 01:53:57 +00:00
required
/>
2026-08-09 05:29:03 +00:00
</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>
2026-08-09 01:53:57 +00:00
<input
2026-08-09 05:29:03 +00:00
id="reset-otp"
2026-08-09 01:53:57 +00:00
type="text"
className="form-control"
2026-08-09 05:29:03 +00:00
placeholder="Enter 123456"
value={resetOtp}
onChange={(e) => setResetOtp(e.target.value)}
2026-08-09 01:53:57 +00:00
required
/>
</div>
2026-08-09 05:29:03 +00:00
<div className="form-group">
<label htmlFor="reset-new-pass">Create New Password *</label>
2026-08-09 01:53:57 +00:00
<input
2026-08-09 05:29:03 +00:00
id="reset-new-pass"
type="password"
2026-08-09 01:53:57 +00:00
className="form-control"
2026-08-09 05:29:03 +00:00
placeholder="Minimum 8 characters"
value={resetPassword}
onChange={(e) => setResetPassword(e.target.value)}
2026-08-09 01:53:57 +00:00
required
/>
2026-08-09 05:29:03 +00:00
</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>
2026-08-09 01:53:57 +00:00
<input
2026-08-09 05:29:03 +00:00
id="otp-login-phone"
type="tel"
2026-08-09 01:53:57 +00:00
className="form-control"
2026-08-09 05:29:03 +00:00
placeholder="Enter 10-digit number"
value={otpLoginPhone}
onChange={(e) => setOtpLoginPhone(e.target.value)}
2026-08-09 01:53:57 +00:00
required
/>
</div>
2026-08-09 05:29:03 +00:00
<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>
2026-08-09 01:53:57 +00:00
<input
2026-08-09 05:29:03 +00:00
id="otp-login-code"
2026-08-09 01:53:57 +00:00
type="text"
className="form-control"
2026-08-09 05:29:03 +00:00
placeholder="Enter 123456"
value={otpLoginCode}
onChange={(e) => setOtpLoginCode(e.target.value)}
2026-08-09 01:53:57 +00:00
required
/>
2026-08-09 05:29:03 +00:00
</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 7: {
profileStep === 1 ? 'Welcome' :
profileStep === 2 ? 'Business Details' :
profileStep === 3 ? 'Identity Verification' :
profileStep === 4 ? 'Contact Verification' :
profileStep === 5 ? 'Choose Categories' :
profileStep === 6 ? 'Store Setup' :
'Verification'
2026-08-09 05:29:03 +00:00
}</span>
<div className="step-bar-container">
<div className="step-bar-fill" style={{ width: `${(profileStep / 7) * 100}%` }}></div>
2026-08-09 05:29:03 +00:00
</div>
</div>
<h2 className="form-card-title" style={{ marginBottom: '1.5rem' }}>Complete Your Supplier Profile</h2>
<form onSubmit={handleProfileSubmit}>
{profileStep === 1 && (
<div style={{ textAlign: 'center', padding: '1rem' }}>
<div style={{ fontSize: '3rem', marginBottom: '1.5rem' }}>👋</div>
<h3 style={{ fontSize: '1.25rem', marginBottom: '1rem', color: 'var(--primary)' }}>Welcome to Tradhox Onboarding</h3>
<p style={{ color: 'var(--text-muted)', marginBottom: '2rem', fontSize: '0.95rem', lineHeight: '1.6' }}>
We bridge the gap between traditional Indian artistry and a global marketplace.
Let's set up your supplier profile in a few simple steps.
</p>
<button
type="button"
className="btn btn-primary"
style={{ width: '100%', padding: '0.8rem', fontWeight: 'bold' }}
onClick={() => setProfileStep(2)}
>
Begin Setup
</button>
</div>
)}
{profileStep === 2 && (
2026-08-09 05:29:03 +00:00
<div>
<h3 style={{ fontSize: '1.1rem', marginBottom: '1rem', color: 'var(--primary)' }}>Step 2: Business Details</h3>
<div className="form-group">
<label>Business Type *</label>
<div style={{ display: 'flex', flexDirection: 'column', gap: '0.5rem', marginBottom: '1.5rem' }}>
<label style={{ display: 'flex', alignItems: 'center', gap: '0.5rem', cursor: 'pointer', padding: '0.75rem', border: '1px solid var(--border)', borderRadius: '6px' }}>
<input type="radio" name="business_type" value="registered_company" checked={businessType === 'registered_company'} onChange={() => setBusinessType('registered_company')} />
Registered Company
</label>
<label style={{ display: 'flex', alignItems: 'center', gap: '0.5rem', cursor: 'pointer', padding: '0.75rem', border: '1px solid var(--border)', borderRadius: '6px' }}>
<input type="radio" name="business_type" value="self_help_group" checked={businessType === 'self_help_group'} onChange={() => setBusinessType('self_help_group')} />
Self Help Group
</label>
<label style={{ display: 'flex', alignItems: 'center', gap: '0.5rem', cursor: 'pointer', padding: '0.75rem', border: '1px solid var(--border)', borderRadius: '6px' }}>
<input type="radio" name="business_type" value="individual_maker" checked={businessType === 'individual_maker'} onChange={() => setBusinessType('individual_maker')} />
Individual Maker
</label>
</div>
</div>
<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 ? 'Submitted ✓' : 'Submit GSTIN'}
</button>
</div>
{isGstinVerified && (
<p style={{ color: 'var(--success)', fontSize: '0.85rem', marginTop: '0.5rem', fontWeight: 600 }}>
GSTIN verification will be completed within the next 24 hrs
</p>
)}
</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(1)}>
Back
</button>
<button
type="submit"
className="btn btn-primary"
2026-08-20 12:53:21 +00:00
style={{ flex: 1, backgroundColor: 'var(--accent)', color: '#ffffff', fontWeight: 'bold' }}
disabled={!gstin || !isGstinVerified}
>
Next Step
</button>
</div>
</div>
)}
{profileStep === 3 && (
<div>
<h3 style={{ fontSize: '1.1rem', marginBottom: '1rem', color: 'var(--primary)' }}>Step 3: Identity Verification</h3>
{/* 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={async (e) => {
if (e.target.files?.[0]) {
const file = e.target.files[0];
setAadharFile(file.name);
alert(`Aadhaar card selected: ${file.name}. Uploading...`);
const s3Key = await uploadDocument(file, 'aadhar');
setAadharS3Key(s3Key);
alert(`Aadhaar card uploaded successfully!`);
}
}}
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={async (e) => {
if (e.target.files?.[0]) {
const file = e.target.files[0];
setPanFile(file.name);
alert(`PAN card selected: ${file.name}. Uploading...`);
const s3Key = await uploadDocument(file, 'pan');
setPanS3Key(s3Key);
alert(`PAN card uploaded successfully!`);
}
}}
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(2)}>
Back
</button>
<button
type="submit"
className="btn btn-primary"
2026-08-20 12:53:21 +00:00
style={{ flex: 1, backgroundColor: 'var(--accent)', color: '#ffffff', fontWeight: 'bold' }}
disabled={!aadharFile || !panFile}
>
Next Step
</button>
</div>
</div>
)}
{profileStep === 4 && (
<div>
<h3 style={{ fontSize: '1.1rem', marginBottom: '1rem', color: 'var(--primary)' }}>Step 4: Verify Contacts</h3>
2026-08-09 05:29:03 +00:00
{/* 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={() => {
2026-08-12 12:48:47 +00:00
apiFetch(`${CONFIG.apiBaseUrl}/api/auth/verify-otp/`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ type: 'phone', otp: enteredPhoneOtp })
})
.then(res => {
if (!res.ok) throw new Error('Incorrect code. Enter 123456');
return res.json();
})
.then(data => {
if (data.verified) {
setPhoneVerified(true);
alert('Phone verified successfully!');
} else {
alert('Incorrect code. Enter 123456');
}
})
.catch(err => {
alert(err.message);
});
2026-08-09 05:29:03 +00:00
}}
>
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={() => {
2026-08-12 12:48:47 +00:00
apiFetch(`${CONFIG.apiBaseUrl}/api/auth/verify-otp/`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ type: 'email', otp: enteredEmailOtp })
})
.then(res => {
if (!res.ok) throw new Error('Incorrect code. Enter 123456');
return res.json();
})
.then(data => {
if (data.verified) {
setEmailVerified(true);
alert('Email verified successfully!');
} else {
alert('Incorrect code. Enter 123456');
}
})
.catch(err => {
alert(err.message);
});
2026-08-09 05:29:03 +00:00
}}
>
Verify Code
</button>
</div>
)}
{emailVerified && (
<div style={{ color: 'var(--success)', fontWeight: 600, fontSize: '0.9rem' }}>
Email Verified
</div>
)}
</div>
<div style={{ display: 'flex', justifyContent: 'space-between', gap: '1rem' }}>
<button type="button" className="btn btn-outline-dark" style={{ flex: 1 }} onClick={() => setProfileStep(3)}>
Back
</button>
<button
type="submit"
className="btn btn-primary"
2026-08-20 12:53:21 +00:00
style={{ flex: 1, backgroundColor: 'var(--accent)', color: '#ffffff', fontWeight: 'bold' }}
disabled={!phoneVerified || !emailVerified}
>
Next Step
</button>
</div>
</div>
2026-08-09 05:29:03 +00:00
)}
{profileStep === 5 && (
2026-08-09 05:29:03 +00:00
<div>
<h3 style={{ fontSize: '1.1rem', marginBottom: '0.5rem', color: 'var(--primary)' }}>Step 5: Choose Product Categories</h3>
<p style={{ color: 'var(--text-muted)', marginBottom: '1.5rem', fontSize: '0.9rem' }}>
Select the categories that apply to your artisanal goods.
</p>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '0.75rem', marginBottom: '2rem' }}>
{['Sustainable Products', 'Home Decor', 'Eco-Friendly', 'OPOD Products', 'GI Tagged', 'Textiles & Apparel'].map(cat => {
const isSelected = selectedCategories.includes(cat);
return (
<div
key={cat}
onClick={() => {
if (isSelected) {
setSelectedCategories(selectedCategories.filter(c => c !== cat));
} else {
setSelectedCategories([...selectedCategories, cat]);
}
}}
style={{
padding: '1rem',
border: isSelected ? '2px solid var(--primary)' : '1px solid var(--border)',
backgroundColor: isSelected ? 'rgba(107, 26, 44, 0.05)' : 'transparent',
borderRadius: '8px',
cursor: 'pointer',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontWeight: isSelected ? '600' : 'normal',
transition: 'all 0.2s ease',
textAlign: 'center'
}}
>
{cat}
</div>
);
})}
2026-08-09 05:29:03 +00:00
</div>
<div style={{ display: 'flex', justifyContent: 'space-between', gap: '1rem' }}>
<button type="button" className="btn btn-outline-dark" style={{ flex: 1 }} onClick={() => setProfileStep(4)}>
2026-08-09 05:29:03 +00:00
Back
</button>
<button
type="submit"
className="btn btn-primary"
2026-08-20 12:53:21 +00:00
style={{ flex: 1, backgroundColor: 'var(--accent)', color: '#ffffff', fontWeight: 'bold' }}
disabled={selectedCategories.length === 0}
2026-08-09 05:29:03 +00:00
>
Next Step
2026-08-09 05:29:03 +00:00
</button>
</div>
</div>
)}
{profileStep === 6 && (
2026-08-09 05:29:03 +00:00
<div>
<h3 style={{ fontSize: '1.1rem', marginBottom: '1rem', color: 'var(--primary)' }}>Step 6: Store Details & Pickup Location</h3>
2026-08-09 05:29:03 +00:00
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '0.75rem' }} className="form-group">
<div>
<label htmlFor="store-name">Store Display Name *</label>
<input
id="store-name"
type="text"
className="form-control"
value={storeName}
onChange={(e) => setStoreName(e.target.value)}
required
/>
</div>
<div>
<label htmlFor="store-slug">Store Slug / Custom URL *</label>
<input
id="store-slug"
type="text"
className="form-control"
placeholder="e.g. my-artisan-shop"
value={storeSlug}
onChange={(e) => setStoreSlug(e.target.value.toLowerCase().replace(/[^a-z0-9\-]/g, ''))}
required
/>
</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '0.75rem' }} className="form-group">
<div>
<label htmlFor="support-email">Support Email *</label>
<input
id="support-email"
type="email"
className="form-control"
placeholder="hello@shop.com"
value={supportEmail}
onChange={(e) => setSupportEmail(e.target.value)}
required
/>
</div>
<div>
<label htmlFor="support-phone">Support Phone *</label>
<input
id="support-phone"
type="tel"
className="form-control"
placeholder="+91..."
value={supportPhone}
onChange={(e) => setSupportPhone(e.target.value)}
required
/>
</div>
2026-08-09 05:29:03 +00:00
</div>
<div className="form-group">
<label htmlFor="store-bio">Store Description / Bio *</label>
2026-08-09 05:29:03 +00:00
<textarea
id="store-bio"
className="form-control"
rows={2}
value={businessBio}
onChange={(e) => setBusinessBio(e.target.value)}
required
></textarea>
</div>
<div className="form-group">
<label>Store Location on Map *</label>
<div
style={{
height: '150px',
2026-08-09 05:29:03 +00:00
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`
}));
}}
>
<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'
2026-08-09 05:29:03 +00:00
}}
>
📍
</div>
<span style={{ fontSize: '0.85rem', color: '#475569', pointerEvents: 'none', fontWeight: 600 }}>Click map to pin location</span>
2026-08-09 05:29:03 +00:00
</div>
</div>
<div className="form-group">
<label htmlFor="location-text">Pickup Address *</label>
2026-08-09 05:29:03 +00:00
<input
id="location-text"
type="text"
className="form-control"
value={address.street}
onChange={(e) => setAddress({ ...address, street: e.target.value })}
required
/>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: '0.5rem' }} className="form-group">
2026-08-09 05:29:03 +00:00
<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-state">State *</label>
<input
id="loc-state"
type="text"
className="form-control"
value={address.state}
onChange={(e) => setAddress({ ...address, state: e.target.value })}
required
/>
</div>
2026-08-09 05:29:03 +00:00
<div>
<label htmlFor="loc-pincode">Pincode *</label>
<input
id="loc-pincode"
type="text"
className="form-control"
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(5)}>
2026-08-09 05:29:03 +00:00
Back
</button>
<button
type="submit"
className="btn btn-primary"
2026-08-20 12:53:21 +00:00
style={{ flex: 1, backgroundColor: 'var(--accent)', color: '#ffffff', fontWeight: 'bold' }}
disabled={!storeName || !storeSlug || !supportEmail || !supportPhone || !address.street || !address.city || !address.state || !address.pincode}
2026-08-09 05:29:03 +00:00
>
Submit Supplier Profile
</button>
</div>
</div>
)}
2026-08-09 01:53:57 +00:00
{profileStep === 7 && (
<div style={{ textAlign: 'center', padding: '1rem' }}>
<div style={{ fontSize: '4rem', marginBottom: '1.5rem' }}>🎉</div>
<h3 style={{ fontSize: '1.25rem', marginBottom: '1rem', color: 'var(--primary)' }}>Setup Complete!</h3>
<p style={{ color: 'var(--text-muted)', marginBottom: '2rem', fontSize: '0.95rem' }}>
Your supplier profile has been successfully submitted and is under review. You can now access your preview dashboard.
</p>
<button
type="button"
className="btn btn-dark"
style={{ width: '100%', padding: '0.8rem', fontWeight: 'bold' }}
onClick={() => {
setIsProfileComplete(true);
navigateTo('welcome-tour', true);
}}
>
Go to Dashboard
</button>
</div>
)}
</form>
</div>
)}
2026-08-09 05:29:03 +00:00
{/* WELCOME PAGE & SETUP TOUR FOR NEW SELLERS */}
{currentPage === 'welcome-tour' && (
<WelcomeTourWizard onComplete={() => navigateTo('dashboard')} />
2026-08-09 05:29:03 +00:00
)}
</main>
) : (
/* --- FULL SERVICE SUPPLIER ACTIVE DASHBOARD PAGE --- */
<div className="flex min-h-screen bg-background text-on-background font-sans antialiased">
2026-08-09 01:53:57 +00:00
{/* Sidebar */}
<aside className="hidden md:flex flex-col h-screen w-64 fixed left-0 top-0 bg-white border-r border-[#D9C5B2] py-stack-lg z-50 justify-between">
<div>
<div className="px-6 mb-12">
<h1 className="font-caslon text-2xl font-bold text-primary">Tradhox</h1>
</div>
<div className="px-4 mb-6 flex items-center gap-4">
<img src="/artisan_weaving.jpg" className="w-12 h-12 rounded-full object-cover border border-[#D9C5B2]" alt="Store Owner Profile" />
<div>
<p className="font-button text-sm font-semibold text-on-surface">{storeName || 'Tradhox Seller'}</p>
<p className="text-xs text-secondary uppercase tracking-wider">Artisan Partner</p>
</div>
</div>
<nav className="flex flex-col gap-1 w-full">
2026-08-20 12:53:21 +00:00
<button
className={`w-full flex items-center gap-3 px-6 py-3.5 text-sm transition-all duration-200 border-r-4 ${dashTab === 'overview' ? 'bg-primary-container/5 text-primary font-bold border-primary' : 'text-secondary border-transparent hover:bg-secondary-container/10 hover:text-on-surface'}`}
2026-08-20 12:53:21 +00:00
onClick={() => { setDashTab('overview'); setSelectedOrderDetail(null); }}
>
<span className="material-symbols-outlined text-[20px]" style={{ fontVariationSettings: dashTab === 'overview' ? "'FILL' 1" : "" }}>home</span>
<span>Home</span>
2026-08-20 12:53:21 +00:00
</button>
<button
className={`w-full flex items-center gap-3 px-6 py-3.5 text-sm transition-all duration-200 border-r-4 ${dashTab === 'orders' ? 'bg-primary-container/5 text-primary font-bold border-primary' : 'text-secondary border-transparent hover:bg-secondary-container/10 hover:text-on-surface'}`}
2026-08-20 12:53:21 +00:00
onClick={() => { setDashTab('orders'); setSelectedOrderDetail(null); }}
>
<span className="material-symbols-outlined text-[20px]" style={{ fontVariationSettings: dashTab === 'orders' ? "'FILL' 1" : "" }}>shopping_cart</span>
<span>Orders</span>
2026-08-20 12:53:21 +00:00
</button>
<button
className={`w-full flex items-center gap-3 px-6 py-3.5 text-sm transition-all duration-200 border-r-4 ${dashTab === 'products' ? 'bg-primary-container/5 text-primary font-bold border-primary' : 'text-secondary border-transparent hover:bg-secondary-container/10 hover:text-on-surface'}`}
2026-08-20 12:53:21 +00:00
onClick={() => { setDashTab('products'); setProductWizardStep(0); setSelectedOrderDetail(null); }}
>
<span className="material-symbols-outlined text-[20px]" style={{ fontVariationSettings: dashTab === 'products' ? "'FILL' 1" : "" }}>inventory_2</span>
<span>Products</span>
2026-08-20 12:53:21 +00:00
</button>
<button
className={`w-full flex items-center gap-3 px-6 py-3.5 text-sm transition-all duration-200 border-r-4 ${dashTab === 'wallet' ? 'bg-primary-container/5 text-primary font-bold border-primary' : 'text-secondary border-transparent hover:bg-secondary-container/10 hover:text-on-surface'}`}
2026-08-20 12:53:21 +00:00
onClick={() => { setDashTab('wallet'); setSelectedOrderDetail(null); }}
>
<span className="material-symbols-outlined text-[20px]" style={{ fontVariationSettings: dashTab === 'wallet' ? "'FILL' 1" : "" }}>payments</span>
<span>Payments</span>
2026-08-20 12:53:21 +00:00
</button>
<button
className={`w-full flex items-center gap-3 px-6 py-3.5 text-sm transition-all duration-200 border-r-4 ${dashTab === 'analytics' ? 'bg-primary-container/5 text-primary font-bold border-primary' : 'text-secondary border-transparent hover:bg-secondary-container/10 hover:text-on-surface'}`}
2026-08-20 12:53:21 +00:00
onClick={() => { setDashTab('analytics'); setSelectedOrderDetail(null); }}
>
<span className="material-symbols-outlined text-[20px]" style={{ fontVariationSettings: dashTab === 'analytics' ? "'FILL' 1" : "" }}>leaderboard</span>
<span>Analytics</span>
2026-08-20 12:53:21 +00:00
</button>
<button
className={`w-full flex items-center gap-3 px-6 py-3.5 text-sm transition-all duration-200 border-r-4 ${dashTab === 'settings' ? 'bg-primary-container/5 text-primary font-bold border-primary' : 'text-secondary border-transparent hover:bg-secondary-container/10 hover:text-on-surface'}`}
2026-08-20 12:53:21 +00:00
onClick={() => { setDashTab('settings'); setSelectedOrderDetail(null); }}
>
<span className="material-symbols-outlined text-[20px]" style={{ fontVariationSettings: dashTab === 'settings' ? "'FILL' 1" : "" }}>settings</span>
<span>Settings</span>
2026-08-20 12:53:21 +00:00
</button>
</nav>
</div>
2026-08-20 12:53:21 +00:00
<div className="px-4">
<button
className="w-full border border-[#D9C5B2] text-on-background py-2 rounded text-sm font-semibold hover:bg-background transition-colors"
onClick={() => window.open(storeSlug ? `/store/${storeSlug}` : '#', '_blank')}
>
View Store
</button>
</div>
</aside>
{/* Main Workspace Frame */}
<main className="flex-grow md:pl-64 flex flex-col min-h-screen bg-background">
{/* Top Workspace Header */}
<header className="h-16 border-b border-[#D9C5B2] bg-white flex items-center justify-between px-8 sticky top-0 z-30">
<div className="flex items-center gap-2 max-w-md w-full">
<span className="material-symbols-outlined text-secondary">search</span>
<input
type="text"
placeholder="Search products, orders, transactions..."
className="w-full bg-transparent border-none focus:outline-none focus:ring-0 text-sm placeholder:text-secondary-fixed-dim"
/>
</div>
<div className="flex items-center gap-4">
<button className="text-secondary hover:text-on-surface relative">
<span className="material-symbols-outlined">notifications</span>
<span className="absolute top-1 right-1 w-2 h-2 bg-primary rounded-full"></span>
</button>
<button className="text-secondary hover:text-on-surface">
<span className="material-symbols-outlined">help_outline</span>
</button>
<div className="w-8 h-8 rounded-full bg-primary/10 text-primary flex items-center justify-center font-bold text-sm">
{CONFIG.logoLetter}
</div>
</div>
</header>
{/* Inner Dashboard Tabs Area */}
<div className="p-8 max-w-7xl w-full mx-auto flex-1">
2026-08-09 01:53:57 +00:00
{/* OVERVIEW PANEL / HOME TAB */}
{dashTab === 'overview' && !selectedOrderDetail && (
<div className="space-y-6 animate-fadeIn">
<div>
<h2 className="font-caslon text-3xl font-bold text-primary mb-1">Welcome back, Artisan!</h2>
<p className="text-secondary text-sm">Your store is looking great. Here is what's happening today.</p>
</div>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
{/* Left Column (Metrics + Orders) */}
<div className="lg:col-span-2 space-y-6">
{/* Metric Cards */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
{/* Card 1 */}
<div className="bg-white border border-[#D9C5B2] rounded-xl p-5 shadow-[0_4px_20px_rgba(107,26,44,0.02)] flex flex-col justify-between h-36">
<div>
<span className="text-xs font-semibold text-secondary uppercase tracking-wider">Total Sales ()</span>
<h3 className="font-caslon text-2xl font-bold text-primary mt-2"> 42,500</h3>
</div>
<svg viewBox="0 0 100 20" className="w-full h-8 mt-2">
<path d="M0,15 Q15,5 30,12 T60,8 T90,14 T100,10 L100,20 L0,20 Z" fill="rgba(107, 26, 44, 0.08)" stroke="var(--primary)" strokeWidth="1.5" />
</svg>
</div>
2026-08-09 05:29:03 +00:00
{/* Card 2 */}
<div className="bg-white border border-[#D9C5B2] rounded-xl p-5 shadow-[0_4px_20px_rgba(107,26,44,0.02)] flex flex-col justify-between h-36">
<div>
<span className="text-xs font-semibold text-secondary uppercase tracking-wider">Active Orders</span>
<h3 className="font-caslon text-2xl font-bold text-primary mt-2">14</h3>
</div>
<svg viewBox="0 0 100 20" className="w-full h-8 mt-2">
<path d="M0,18 Q20,10 40,15 T80,5 T100,8 L100,20 L0,20 Z" fill="rgba(107, 26, 44, 0.08)" stroke="var(--primary)" strokeWidth="1.5" />
</svg>
</div>
{/* Card 3 */}
<div className="bg-white border border-[#D9C5B2] rounded-xl p-5 shadow-[0_4px_20px_rgba(107,26,44,0.02)] flex flex-col justify-between h-36">
<div>
<span className="text-xs font-semibold text-secondary uppercase tracking-wider">Store Views</span>
<h3 className="font-caslon text-2xl font-bold text-primary mt-2">1,204</h3>
</div>
<svg viewBox="0 0 100 20" className="w-full h-8 mt-2">
<path d="M0,15 Q25,18 50,8 T80,14 T100,5 L100,20 L0,20 Z" fill="rgba(107, 26, 44, 0.08)" stroke="var(--primary)" strokeWidth="1.5" />
</svg>
</div>
</div>
{/* Recent Orders Queue Card */}
<div className="bg-white border border-[#D9C5B2] rounded-xl p-6 shadow-[0_4px_20px_rgba(107,26,44,0.02)]">
<div className="flex justify-between items-center pb-4 border-b border-[#D9C5B2] mb-4">
<h3 className="font-semibold text-on-surface">Recent Orders</h3>
<button onClick={() => setDashTab('orders')} className="text-primary hover:underline text-sm font-semibold flex items-center gap-1">
View All <span className="material-symbols-outlined text-sm">arrow_forward</span>
</button>
</div>
<div className="overflow-x-auto">
<table className="w-full text-left border-collapse">
<thead>
<tr className="bg-surface-container-low border-b border-[#D9C5B2]">
<th className="p-3 text-xs font-semibold text-secondary uppercase tracking-wider">Order ID</th>
<th className="p-3 text-xs font-semibold text-secondary uppercase tracking-wider">Date</th>
<th className="p-3 text-xs font-semibold text-secondary uppercase tracking-wider">Product Name</th>
<th className="p-3 text-xs font-semibold text-secondary uppercase tracking-wider">Status</th>
</tr>
</thead>
<tbody>
{orders.slice(0, 5).map(o => (
<tr key={o.id} className="border-b border-[#D9C5B2]/30 hover:bg-background/40 transition-colors">
<td className="p-3">
<button onClick={() => { setSelectedOrderDetail(o); setDashTab('orders'); }} className="text-primary hover:underline font-bold">
#{o.id}
</button>
</td>
<td className="p-3 text-sm text-secondary">{o.date}</td>
<td className="p-3 text-sm font-medium">{o.item}</td>
<td className="p-3">
<span className={`inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium border ${o.status === 'Delivered' ? 'bg-green-50 text-green-700 border-green-200' : 'bg-primary-container/10 text-primary-container border-primary-container/20'}`}>
{o.status === 'Pending Acceptance' ? 'Pending' : o.status}
</span>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
{/* Right Column (Live Preview) */}
<div className="lg:col-span-1">
<div className="bg-white rounded-xl border border-[#D9C5B2] shadow-[0_4px_20px_rgba(107,26,44,0.02)] p-6 h-full flex flex-col justify-between">
<div>
<div className="flex justify-between items-center mb-6">
<h3 className="font-caslon text-xl font-bold text-primary">Live Preview</h3>
<button className="text-secondary hover:text-primary">
<span className="material-symbols-outlined text-lg">open_in_new</span>
</button>
</div>
{/* Store Preview Mockup */}
<div className="border border-[#D9C5B2] rounded bg-surface-container-low p-2 space-y-2">
<div className="h-24 bg-surface-container-high rounded relative overflow-hidden flex items-center justify-center">
<img src="/artisan_banner.jpg" className="absolute inset-0 w-full h-full object-cover opacity-50" />
<div className="bg-white px-3 py-1 rounded shadow-sm relative z-10">
<p className="text-xs font-bold text-center text-primary">{storeName || 'Tradhox Artisan Co.'}</p>
</div>
</div>
<div className="grid grid-cols-2 gap-2">
<div className="bg-white p-1.5 rounded border border-[#D9C5B2]">
<div className="aspect-square bg-background rounded overflow-hidden mb-1">
<img src="/kanchipuram_silk.jpg" className="w-full h-full object-cover" />
</div>
<div className="h-1.5 w-3/4 bg-surface-container-high rounded mb-1"></div>
<div className="h-1.5 w-1/2 bg-surface-container-high rounded"></div>
</div>
<div className="bg-white p-1.5 rounded border border-[#D9C5B2]">
<div className="aspect-square bg-background rounded overflow-hidden mb-1">
<img src="/terracotta_vases.jpg" className="w-full h-full object-cover" />
</div>
<div className="h-1.5 w-3/4 bg-surface-container-high rounded mb-1"></div>
<div className="h-1.5 w-1/2 bg-surface-container-high rounded"></div>
</div>
</div>
</div>
</div>
<div className="mt-6 pt-6 border-t border-[#D9C5B2]/30 space-y-3">
<p className="text-xs text-secondary text-center">Your store is currently live and looking great.</p>
<button
className="w-full bg-primary text-white py-2 rounded text-sm font-semibold hover:bg-primary/95 transition-colors flex justify-center items-center gap-2 cursor-pointer"
onClick={() => alert(`Store Link: https://tradhox.com/store/${storeSlug || 'artisan'}`)}
>
Share Store Link <span className="material-symbols-outlined text-sm">share</span>
</button>
</div>
2026-08-09 05:29:03 +00:00
</div>
</div>
</div>
</div>
)}
{/* PRODUCTS TAB */}
{dashTab === 'products' && (
<div className="animate-fadeIn">
{productWizardStep === 0 ? (
<div className="space-y-6">
<div className="flex justify-between items-center">
<div>
<h2 className="font-caslon text-3xl font-bold text-primary">Products</h2>
<p className="text-secondary text-sm">Manage your listings, catalog collections, and stock values.</p>
</div>
<button
className="bg-primary text-white px-6 py-2.5 rounded text-sm font-semibold hover:bg-primary/95 transition-colors flex items-center gap-2"
onClick={() => {
setProductFormDetails({
id: '',
name: '',
category: 'Textiles & Apparel',
description: '',
isGiTagged: false,
primaryImage: '',
additionalViews: [],
videoUrl: '',
view360Url: '',
basePrice: '',
compareAtPrice: '',
trackInventory: true,
sku: '',
initialStock: '',
shippingProfile: 'Standard Fragile',
processingDays: 3,
packageWeight: 1.0
});
setProductWizardStep(1);
}}
>
+ Add New Product
</button>
</div>
{/* Filters */}
<div className="flex gap-4">
<select className="bg-white border border-[#D9C5B2] rounded px-4 py-2 text-sm focus:outline-none focus:ring-1 focus:ring-primary focus:border-primary">
<option>All Categories</option>
<option>Textiles & Apparel</option>
<option>Pottery & Ceramics</option>
<option>Wood Carving</option>
</select>
<select className="bg-white border border-[#D9C5B2] rounded px-4 py-2 text-sm focus:outline-none focus:ring-1 focus:ring-primary focus:border-primary">
<option>All Status</option>
<option>In Stock</option>
<option>Low Stock</option>
<option>Out of Stock</option>
</select>
</div>
{/* Products Cards Grid */}
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
{products.map(p => {
const isOut = p.stock === 0;
const isLow = p.stock > 0 && p.stock <= 5;
return (
<div key={p.id} className="bg-white border border-[#D9C5B2] rounded-xl overflow-hidden flex flex-col justify-between shadow-[0_4px_20px_rgba(107,26,44,0.01)] hover:shadow-diffused transition-shadow">
<div>
<div className="aspect-video bg-background relative overflow-hidden border-b border-[#D9C5B2]/30">
{p.image ? (
<img src={p.image} className="w-full h-full object-cover" />
) : (
<div className="w-full h-full flex items-center justify-center text-secondary">🖼 No Image</div>
)}
<span className={`absolute top-3 right-3 px-2 py-0.5 rounded text-xs font-semibold border ${isOut ? 'bg-red-50 text-red-700 border-red-200' : isLow ? 'bg-amber-50 text-amber-700 border-amber-200' : 'bg-green-50 text-green-700 border-green-200'}`}>
{isOut ? 'Out of Stock' : isLow ? `Low Stock (${p.stock})` : `In Stock (${p.stock})`}
</span>
</div>
<div className="p-5 space-y-2">
<div className="flex items-center gap-2">
<span className="text-[10px] font-bold text-secondary uppercase tracking-widest bg-background px-2 py-0.5 rounded">{p.category}</span>
{(p as any).is_gi_tagged && (
<span className="text-[10px] font-bold text-primary-container uppercase tracking-widest bg-primary-container/10 px-2 py-0.5 rounded">GI Tagged</span>
)}
</div>
<h4 className="font-caslon text-lg font-bold text-primary line-clamp-1">{p.title}</h4>
<p className="text-sm font-semibold text-on-surface"> {Number(p.price).toFixed(2)}</p>
</div>
</div>
<div className="p-5 border-t border-[#D9C5B2]/30 flex gap-2">
<button
className="flex-grow border border-[#D9C5B2] hover:bg-background py-1.5 rounded text-xs font-semibold transition-colors flex items-center justify-center gap-1.5"
onClick={() => {
setProductFormDetails({
id: String(p.id),
name: p.title,
category: p.category,
description: (p as any).description || 'Premium heritage quality.',
isGiTagged: (p as any).is_gi_tagged || false,
primaryImage: p.image || '',
additionalViews: (p as any).additional_images || [],
videoUrl: (p as any).video_url || '',
view360Url: (p as any).view_360_url || '',
basePrice: String(p.price),
compareAtPrice: (p as any).compare_at_price ? String((p as any).compare_at_price) : '',
trackInventory: (p as any).track_inventory !== false,
sku: p.sku,
initialStock: String(p.stock),
shippingProfile: (p as any).shipping_profile || 'Standard Fragile',
processingDays: (p as any).processing_days || 3,
packageWeight: (p as any).package_weight || 1.0
});
setProductWizardStep(1);
}}
>
<span className="material-symbols-outlined text-sm">edit</span> Edit
</button>
<button
className="border border-red-200 hover:bg-red-50 text-red-600 px-3 py-1.5 rounded text-xs font-semibold transition-colors flex items-center justify-center"
onClick={() => handleDeleteProduct(p.id)}
>
<span className="material-symbols-outlined text-sm">delete</span>
</button>
</div>
</div>
);
})}
</div>
</div>
) : (
/* Multi-Step Wizard */
<div className="bg-white border border-[#D9C5B2] rounded-xl p-8 max-w-3xl mx-auto shadow-diffused">
<div className="flex justify-between items-center mb-6">
<div>
<h3 className="font-caslon text-2xl font-bold text-primary">{productFormDetails.id ? 'Edit Product Details' : 'Add New Product'}</h3>
<p className="text-secondary text-xs">Fill out the heritage marketplace listing form.</p>
</div>
<button className="text-secondary hover:text-on-surface text-sm font-semibold" onClick={() => setProductWizardStep(0)}>Exit Wizard</button>
</div>
{/* Step lines progress */}
<div className="flex items-center justify-between border-b border-[#D9C5B2]/30 pb-6 mb-8">
{['General', 'Media', 'Pricing', 'Shipping', 'Review'].map((tabLabel, idx) => {
const sNum = idx + 1;
const active = productWizardStep === sNum;
const done = productWizardStep > sNum;
return (
<div key={tabLabel} className={`flex items-center gap-2 ${active || done ? 'opacity-100' : 'opacity-40'}`}>
<div className={`w-6 h-6 rounded-full flex items-center justify-center text-xs font-bold ${done || active ? 'bg-primary text-white' : 'border border-on-surface'}`}>
{done ? '✓' : sNum}
</div>
<span className={`text-xs ${active ? 'font-semibold text-primary' : 'text-secondary'}`}>{tabLabel}</span>
</div>
);
})}
</div>
<form onSubmit={(e) => {
e.preventDefault();
if (productWizardStep < 5) {
setProductWizardStep(prev => prev + 1);
} else {
const url = productFormDetails.id
? `${CONFIG.apiBaseUrl}/api/products/${productFormDetails.id}/`
: `${CONFIG.apiBaseUrl}/api/products/`;
const method = productFormDetails.id ? 'PUT' : 'POST';
apiFetch(url, {
method,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
title: productFormDetails.name,
category: productFormDetails.category,
price: String(productFormDetails.basePrice),
stock: Number(productFormDetails.initialStock),
sku: productFormDetails.sku || `PROD-${Date.now().toString().slice(-6)}`,
image: productFormDetails.primaryImage || '/kanchipuram_silk.jpg',
description: productFormDetails.description,
is_gi_tagged: productFormDetails.isGiTagged,
additional_images: productFormDetails.additionalViews,
video_url: productFormDetails.videoUrl,
view_360_url: productFormDetails.view360Url,
compare_at_price: productFormDetails.compareAtPrice ? String(productFormDetails.compareAtPrice) : null,
track_inventory: productFormDetails.trackInventory,
shipping_profile: productFormDetails.shippingProfile,
processing_days: Number(productFormDetails.processingDays),
package_weight: Number(productFormDetails.packageWeight)
})
})
.then(res => res.json())
.then(() => {
apiFetch(`${CONFIG.apiBaseUrl}/api/products/`)
.then(r => r.json())
.then(prods => {
if (Array.isArray(prods)) setProducts(prods);
else if (prods && Array.isArray(prods.results)) setProducts(prods.results);
});
setProductWizardStep(0);
alert('Product details successfully processed!');
})
.catch(err => alert(err.message));
}
}} className="space-y-6">
{/* Step 1 */}
{productWizardStep === 1 && (
<div className="space-y-4">
<div>
<label className="text-xs font-semibold text-secondary uppercase tracking-wider block mb-1">Product Name *</label>
<input
type="text"
className="w-full bg-background border border-[#D9C5B2] rounded px-4 py-2.5 text-sm focus:outline-none focus:ring-1 focus:ring-primary focus:border-primary"
value={productFormDetails.name}
onChange={e => setProductFormDetails({ ...productFormDetails, name: e.target.value })}
required
/>
</div>
<div>
<label className="text-xs font-semibold text-secondary uppercase tracking-wider block mb-1">Category *</label>
<select
className="w-full bg-background border border-[#D9C5B2] rounded px-4 py-2.5 text-sm focus:outline-none focus:ring-1 focus:ring-primary focus:border-primary"
value={productFormDetails.category}
onChange={e => setProductFormDetails({ ...productFormDetails, category: e.target.value })}
>
<option>Textiles & Apparel</option>
<option>Pottery & Ceramics</option>
<option>Wood Carving</option>
<option>Home Decor</option>
</select>
</div>
<div>
<label className="text-xs font-semibold text-secondary uppercase tracking-wider block mb-1">Description *</label>
<textarea
className="w-full bg-background border border-[#D9C5B2] rounded px-4 py-2.5 text-sm focus:outline-none focus:ring-1 focus:ring-primary focus:border-primary"
rows={4}
value={productFormDetails.description}
onChange={e => setProductFormDetails({ ...productFormDetails, description: e.target.value })}
required
/>
</div>
<div className="flex items-center justify-between border border-[#D9C5B2]/50 p-4 rounded-lg">
<div>
<h5 className="text-sm font-semibold">Geographical Indication (GI) Tagged</h5>
<p className="text-xs text-secondary">Does this craft hold government-registered GI tag certification?</p>
</div>
<input
type="checkbox"
className="w-4 h-4 text-primary focus:ring-primary border-[#D9C5B2] rounded"
checked={productFormDetails.isGiTagged}
onChange={e => setProductFormDetails({ ...productFormDetails, isGiTagged: e.target.checked })}
/>
</div>
</div>
)}
{/* Step 2 */}
{productWizardStep === 2 && (
<div className="space-y-4">
<div>
<label className="text-xs font-semibold text-secondary uppercase tracking-wider block mb-1">Primary Image URL</label>
<div className="flex gap-2">
<input
type="text"
className="flex-1 bg-background border border-[#D9C5B2] rounded px-4 py-2.5 text-sm focus:outline-none focus:ring-1 focus:ring-primary"
value={productFormDetails.primaryImage}
onChange={e => setProductFormDetails({ ...productFormDetails, primaryImage: e.target.value })}
placeholder="Leave empty for auto heritage placeholder"
/>
<button
type="button"
className="border border-[#D9C5B2] hover:bg-background px-4 py-2 rounded text-sm font-semibold"
onClick={() => setProductFormDetails({ ...productFormDetails, primaryImage: '/kanchipuram_silk.jpg' })}
>
Use Silk Saree
</button>
<button
type="button"
className="border border-[#D9C5B2] hover:bg-background px-4 py-2 rounded text-sm font-semibold"
onClick={() => setProductFormDetails({ ...productFormDetails, primaryImage: '/terracotta_vases.jpg' })}
>
Use Vases
</button>
</div>
</div>
<div>
<label className="text-xs font-semibold text-secondary uppercase tracking-wider block mb-1">Video Asset Link (Optional)</label>
<input
type="url"
className="w-full bg-background border border-[#D9C5B2] rounded px-4 py-2.5 text-sm focus:outline-none focus:ring-1 focus:ring-primary"
value={productFormDetails.videoUrl}
onChange={e => setProductFormDetails({ ...productFormDetails, videoUrl: e.target.value })}
/>
</div>
<div>
<label className="text-xs font-semibold text-secondary uppercase tracking-wider block mb-1">360° Asset Link (Optional)</label>
<input
type="url"
className="w-full bg-background border border-[#D9C5B2] rounded px-4 py-2.5 text-sm focus:outline-none focus:ring-1 focus:ring-primary"
value={productFormDetails.view360Url}
onChange={e => setProductFormDetails({ ...productFormDetails, view360Url: e.target.value })}
/>
</div>
</div>
)}
2026-08-09 01:53:57 +00:00
{/* Step 3 */}
{productWizardStep === 3 && (
<div className="space-y-4">
<div className="grid grid-cols-2 gap-4">
<div>
<label className="text-xs font-semibold text-secondary uppercase tracking-wider block mb-1">Base Price (INR) *</label>
<input
type="number"
className="w-full bg-background border border-[#D9C5B2] rounded px-4 py-2.5 text-sm focus:outline-none"
value={productFormDetails.basePrice}
onChange={e => setProductFormDetails({ ...productFormDetails, basePrice: e.target.value })}
required
/>
</div>
<div>
<label className="text-xs font-semibold text-secondary uppercase tracking-wider block mb-1">Compare-at Price (INR)</label>
<input
type="number"
className="w-full bg-background border border-[#D9C5B2] rounded px-4 py-2.5 text-sm focus:outline-none"
value={productFormDetails.compareAtPrice}
onChange={e => setProductFormDetails({ ...productFormDetails, compareAtPrice: e.target.value })}
/>
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="text-xs font-semibold text-secondary uppercase tracking-wider block mb-1">SKU *</label>
<input
type="text"
className="w-full bg-background border border-[#D9C5B2] rounded px-4 py-2.5 text-sm focus:outline-none"
value={productFormDetails.sku}
onChange={e => setProductFormDetails({ ...productFormDetails, sku: e.target.value })}
placeholder="e.g. SILK-SAR-01"
/>
</div>
<div>
<label className="text-xs font-semibold text-secondary uppercase tracking-wider block mb-1">Initial Stock Level *</label>
<input
type="number"
className="w-full bg-background border border-[#D9C5B2] rounded px-4 py-2.5 text-sm focus:outline-none"
value={productFormDetails.initialStock}
onChange={e => setProductFormDetails({ ...productFormDetails, initialStock: e.target.value })}
required
/>
</div>
</div>
<div className="flex items-center justify-between border border-[#D9C5B2]/50 p-4 rounded-lg">
<div>
<h5 className="text-sm font-semibold">Track Inventory</h5>
<p className="text-xs text-secondary">Automatically track and deplete inventory upon order creation.</p>
</div>
<input
type="checkbox"
className="w-4 h-4 text-primary"
checked={productFormDetails.trackInventory}
onChange={e => setProductFormDetails({ ...productFormDetails, trackInventory: e.target.checked })}
/>
</div>
</div>
)}
{/* Step 4 */}
{productWizardStep === 4 && (
<div className="space-y-4">
<div>
<label className="text-xs font-semibold text-secondary uppercase tracking-wider block mb-1">Shipping Profile *</label>
<select
className="w-full bg-background border border-[#D9C5B2] rounded px-4 py-2.5 text-sm focus:outline-none"
value={productFormDetails.shippingProfile}
onChange={e => setProductFormDetails({ ...productFormDetails, shippingProfile: e.target.value })}
>
<option>Standard Fragile</option>
<option>Standard Soft Goods</option>
<option>Heavy Goods</option>
</select>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="text-xs font-semibold text-secondary uppercase tracking-wider block mb-1">Processing Time (Days) *</label>
<input
type="number"
className="w-full bg-background border border-[#D9C5B2] rounded px-4 py-2.5 text-sm focus:outline-none"
value={productFormDetails.processingDays}
onChange={e => setProductFormDetails({ ...productFormDetails, processingDays: Number(e.target.value) })}
required
/>
</div>
<div>
<label className="text-xs font-semibold text-secondary uppercase tracking-wider block mb-1">Weight (kg) *</label>
<input
type="number"
step="0.1"
className="w-full bg-background border border-[#D9C5B2] rounded px-4 py-2.5 text-sm focus:outline-none"
value={productFormDetails.packageWeight}
onChange={e => setProductFormDetails({ ...productFormDetails, packageWeight: Number(e.target.value) })}
required
/>
</div>
</div>
</div>
)}
{/* Step 5 */}
{productWizardStep === 5 && (
<div className="space-y-6">
<div className="border border-outline-variant rounded-lg p-6 bg-background/50 grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="aspect-square rounded bg-white overflow-hidden border border-outline-variant">
<img src={productFormDetails.primaryImage || '/kanchipuram_silk.jpg'} className="w-full h-full object-cover" />
</div>
<div className="space-y-4 flex flex-col justify-between">
<div>
<div className="flex gap-2">
<span className="text-[10px] font-bold bg-primary/10 text-primary px-2.5 py-0.5 rounded-full">{productFormDetails.category}</span>
{productFormDetails.isGiTagged && (
<span className="text-[10px] font-bold bg-primary-container text-white px-2.5 py-0.5 rounded-full">GI Tagged</span>
)}
</div>
<h4 className="font-caslon text-xl font-bold text-primary mt-2">{productFormDetails.name || 'Untitled Craft Item'}</h4>
<p className="text-sm text-secondary mt-2 line-clamp-4">{productFormDetails.description}</p>
</div>
<div className="pt-4 border-t border-[#D9C5B2]/30 flex justify-between items-end">
<div>
<span className="text-xs text-secondary">Price</span>
<p className="text-lg font-bold text-primary"> {Number(productFormDetails.basePrice || 0).toFixed(2)}</p>
</div>
<div className="text-right">
<span className="text-xs text-secondary">SKU / Stock</span>
<p className="text-sm font-semibold">{productFormDetails.sku || 'AUTO'} / {productFormDetails.initialStock} Units</p>
</div>
</div>
</div>
</div>
</div>
)}
<div className="flex justify-between items-center pt-6 border-t border-[#D9C5B2]/30 mt-6">
{productWizardStep > 1 ? (
<button type="button" className="border border-outline-variant px-6 py-2 rounded text-sm font-semibold hover:bg-background" onClick={() => setProductWizardStep(prev => prev - 1)}>Back</button>
) : (
<div></div>
)}
<button type="submit" className="bg-primary text-white px-8 py-2 rounded text-sm font-semibold hover:bg-primary/95 transition-colors">
{productWizardStep === 5 ? 'Publish Craft Listing' : 'Continue'}
</button>
</div>
</form>
</div>
)}
</div>
)}
{/* ORDERS TAB */}
{dashTab === 'orders' && (
<div className="animate-fadeIn">
{!selectedOrderDetail ? (
<div className="space-y-6">
<div className="flex justify-between items-center">
<div>
<h2 className="font-caslon text-3xl font-bold text-primary">Orders Management</h2>
<p className="text-secondary text-sm font-medium">Review and process purchase tickets placed by global customers.</p>
</div>
<div className="flex gap-2">
<button className="border border-[#D9C5B2] bg-white px-4 py-2 rounded text-xs font-semibold hover:bg-background transition-colors">Export CSV</button>
<button className="bg-primary text-white px-4 py-2 rounded text-xs font-semibold hover:bg-primary/95 transition-colors">+ Create Order</button>
</div>
</div>
{/* Tabs */}
<div className="flex gap-6 border-b border-outline-variant/40 pb-1">
{['All Orders', 'Pending', 'Processing', 'Shipped', 'Delivered', 'Cancelled'].map(oTab => {
const active = oTab === 'All Orders';
return (
<button key={oTab} className={`text-sm font-medium pb-2 border-b-2 transition-colors ${active ? 'border-primary text-primary font-bold' : 'border-transparent text-secondary hover:text-on-surface'}`}>
{oTab}
</button>
);
})}
</div>
{/* Orders table */}
<div className="bg-white border border-[#D9C5B2] rounded-xl overflow-hidden shadow-[0_4px_20px_rgba(107,26,44,0.02)]">
<div className="overflow-x-auto">
<table className="w-full text-left border-collapse">
<thead>
<tr className="bg-surface-container-low border-b border-[#D9C5B2]">
<th className="p-4 text-xs font-semibold text-secondary uppercase tracking-wider"><input type="checkbox" className="rounded border-[#D9C5B2]" /></th>
<th className="p-4 text-xs font-semibold text-secondary uppercase tracking-wider">Order ID</th>
<th className="p-4 text-xs font-semibold text-secondary uppercase tracking-wider">Date</th>
<th className="p-4 text-xs font-semibold text-secondary uppercase tracking-wider">Customer</th>
<th className="p-4 text-xs font-semibold text-secondary uppercase tracking-wider">Total</th>
<th className="p-4 text-xs font-semibold text-secondary uppercase tracking-wider">Status</th>
<th className="p-4 text-xs font-semibold text-secondary uppercase tracking-wider">Action</th>
</tr>
</thead>
<tbody>
{orders.map(o => (
<tr key={o.id} className="border-b border-[#D9C5B2]/30 hover:bg-background/20 transition-colors">
<td className="p-4"><input type="checkbox" className="rounded border-[#D9C5B2]" /></td>
<td className="p-4">
<button onClick={() => setSelectedOrderDetail(o)} className="text-primary hover:underline font-bold">
#{o.id}
</button>
</td>
<td className="p-4 text-sm text-secondary">{o.date}</td>
<td className="p-4 flex items-center gap-3">
<div className="w-8 h-8 rounded-full bg-secondary-container text-on-secondary-container flex items-center justify-center font-bold text-xs">
{o.customer.split(' ').map((n: string) => n[0]).join('')}
</div>
<span className="text-sm font-semibold">{o.customer}</span>
</td>
<td className="p-4 text-sm font-bold text-on-surface"> {Number(o.total).toFixed(2)}</td>
<td className="p-4">
<span className={`inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium border ${o.status === 'Delivered' ? 'bg-green-50 text-green-700 border-green-200' : 'bg-primary-container/10 text-primary-container border-primary-container/20'}`}>
{o.status === 'Pending Acceptance' ? 'Pending' : o.status === 'Ready to Ship' ? 'Processing' : o.status}
</span>
</td>
<td className="p-4">
<button onClick={() => setSelectedOrderDetail(o)} className="border border-[#D9C5B2] hover:bg-background px-3 py-1 rounded text-xs font-semibold">
👁 Details
</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
2026-08-09 05:29:03 +00:00
</div>
) : (
/* Detailed Order View */
<div className="space-y-6">
<button onClick={() => setSelectedOrderDetail(null)} className="text-primary font-semibold text-sm hover:underline flex items-center gap-1.5">
<span className="material-symbols-outlined text-sm">arrow_back</span> Back to Orders queue
</button>
2026-08-09 05:29:03 +00:00
<div className="flex justify-between items-center">
<div>
<h2 className="font-caslon text-3xl font-bold text-primary">Order #{selectedOrderDetail.id}</h2>
<p className="text-xs text-secondary">Registered on {selectedOrderDetail.date} at 10:45 AM</p>
</div>
<div className="flex gap-2">
<button className="border border-[#D9C5B2] bg-white px-5 py-2.5 rounded text-sm font-semibold hover:bg-background" onClick={() => alert('Printing shipping slip...')}>Print Shipping Slip</button>
{selectedOrderDetail.status === 'Pending Acceptance' && (
<button
className="bg-primary text-white px-6 py-2.5 rounded text-sm font-semibold hover:bg-primary/95 transition-colors"
onClick={() => {
handleAcceptOrder(selectedOrderDetail.id);
setSelectedOrderDetail({ ...selectedOrderDetail, status: 'Ready to Ship' });
}}
>
Confirm Shipment Dispatch
</button>
)}
</div>
</div>
2026-08-09 05:29:03 +00:00
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
{/* Order info columns */}
<div className="lg:col-span-2 space-y-6">
<div className="bg-white border border-[#D9C5B2] rounded-xl p-6 shadow-diffused space-y-4">
<h4 className="font-caslon text-lg font-bold text-primary border-b border-[#D9C5B2]/30 pb-2">Order Status History</h4>
<div className="relative pl-6 space-y-6 border-l-2 border-primary-container/20">
<div className="relative">
<span className="absolute -left-[30px] top-1.5 w-3 h-3 rounded-full bg-primary border-2 border-white"></span>
<p className="text-sm font-bold">Transaction Created & Paid</p>
<p className="text-xs text-secondary">{selectedOrderDetail.date} - 10:45 AM</p>
</div>
<div className="relative">
<span className={`absolute -left-[30px] top-1.5 w-3 h-3 rounded-full border-2 border-white ${selectedOrderDetail.status !== 'Pending Acceptance' ? 'bg-primary' : 'bg-outline-variant'}`}></span>
<p className="text-sm font-bold">Seller Confirmation & Packing</p>
<p className="text-xs text-secondary">{selectedOrderDetail.status !== 'Pending Acceptance' ? 'Processed' : 'Awaiting confirmation'}</p>
</div>
</div>
</div>
<div className="bg-white border border-[#D9C5B2] rounded-xl p-6 shadow-diffused">
<h4 className="font-caslon text-lg font-bold text-primary mb-4">Items Purchased</h4>
<div className="flex items-center gap-4 py-2 border-b border-[#D9C5B2]/20 last:border-b-0">
<div className="w-16 h-16 rounded bg-background overflow-hidden border border-[#D9C5B2]">
<img src="/kanchipuram_silk.jpg" className="w-full h-full object-cover" />
</div>
<div className="flex-grow">
<h5 className="text-sm font-semibold text-on-surface">{selectedOrderDetail.item}</h5>
<p className="text-xs text-secondary">SKU: IND-CRAFT-9012 GI Tagged</p>
</div>
<div className="text-right">
<p className="text-sm font-bold"> {Number(selectedOrderDetail.total).toFixed(2)}</p>
<p className="text-xs text-secondary">Qty: {selectedOrderDetail.quantity}</p>
</div>
</div>
</div>
</div>
{/* Customer Column */}
<div className="space-y-6">
<div className="bg-white border border-[#D9C5B2] rounded-xl p-6 shadow-diffused space-y-4">
<h4 className="font-semibold text-primary">Customer Account</h4>
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-full bg-secondary-container text-on-secondary-container flex items-center justify-center font-bold text-sm">
{selectedOrderDetail.customer.split(' ').map((n: string) => n[0]).join('')}
</div>
<div>
<p className="text-sm font-semibold">{selectedOrderDetail.customer}</p>
<p className="text-xs text-secondary">customer@heritage.in</p>
</div>
</div>
<div className="text-xs text-secondary space-y-2 pt-2 border-t border-[#D9C5B2]/30">
<p><strong>Shipping Address:</strong> 12 Handloom Arcade, Kanchipuram, Tamil Nadu 631502</p>
</div>
</div>
<div className="bg-white border border-[#D9C5B2] rounded-xl p-6 shadow-diffused space-y-4">
<h4 className="font-semibold text-primary">Internal Notes</h4>
<textarea
className="w-full bg-background border border-[#D9C5B2] rounded p-3 text-sm focus:outline-none"
rows={3}
placeholder="Add private staff note..."
value={orderNotes}
onChange={e => setOrderNotes(e.target.value)}
/>
<button
className="w-full bg-primary text-white py-2 rounded text-xs font-semibold hover:bg-primary/95 transition-colors"
onClick={() => { alert('Staff note logged!'); setOrderNotes(''); }}
>
Save Note
</button>
</div>
</div>
2026-08-09 05:29:03 +00:00
</div>
</div>
)}
</div>
)}
{/* PAYMENTS TAB */}
{dashTab === 'wallet' && (
<div className="space-y-8 animate-fadeIn">
<div>
<h2 className="font-caslon text-3xl font-bold text-primary">Payments Overview</h2>
<p className="text-secondary text-sm">Monitor outstanding balances and process direct bank transfers.</p>
2026-08-09 05:29:03 +00:00
</div>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
<div className="lg:col-span-2 space-y-6">
<div className="bg-white border border-[#D9C5B2] rounded-xl p-8 shadow-diffused flex flex-col justify-between h-64">
<div>
<span className="text-xs font-semibold text-secondary uppercase tracking-wider">Total Earned (All Time)</span>
<h3 className="font-caslon text-4xl font-bold text-primary mt-2"> 1,45,200</h3>
</div>
<div className="flex gap-8 border-t border-[#D9C5B2]/30 pt-4">
<div>
<span className="text-xs text-secondary">Pending Balance</span>
<p className="text-lg font-bold text-on-surface"> 12,450</p>
</div>
<div>
<span className="text-xs text-secondary">Next Release Date</span>
<p className="text-lg font-semibold text-on-surface">Oct 24, 2026</p>
</div>
</div>
<button
className="w-full bg-primary text-white py-2.5 rounded text-sm font-semibold hover:bg-primary/95 transition-colors mt-4"
onClick={() => alert('Earnings withdrawal triggered!')}
>
Withdraw Funds
</button>
</div>
<div className="bg-white border border-[#D9C5B2] rounded-xl p-6 shadow-diffused">
<h4 className="font-caslon text-lg font-bold text-primary mb-4">Transaction History</h4>
<div className="overflow-x-auto">
<table className="w-full text-left border-collapse">
<thead>
<tr className="bg-surface-container-low border-b border-[#D9C5B2]/30">
<th className="p-3 text-xs font-semibold text-secondary">Transfer Date</th>
<th className="p-3 text-xs font-semibold text-secondary">ID</th>
<th className="p-3 text-xs font-semibold text-secondary">Amount</th>
<th className="p-3 text-xs font-semibold text-secondary">Status</th>
</tr>
</thead>
<tbody>
{orders.slice(0, 3).map(o => (
<tr key={o.id} className="border-b border-[#D9C5B2]/10 last:border-b-0 hover:bg-background/20 transition-colors">
<td className="p-3 text-sm text-secondary">{o.date}</td>
<td className="p-3 text-sm font-mono">#TXN-90{o.id}</td>
<td className="p-3 text-sm font-bold"> {Number(o.total).toFixed(2)}</td>
<td className="p-3"><span className="bg-green-50 text-green-700 px-2 py-0.5 rounded text-[10px] font-semibold border border-green-200">Completed</span></td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
{/* Bank side column */}
<div className="bg-white border border-[#D9C5B2] rounded-xl p-6 shadow-diffused space-y-4 h-fit">
<div className="flex justify-between items-center pb-2 border-b border-[#D9C5B2]/30">
<h4 className="font-semibold text-primary">Verified Bank Account</h4>
<span className="material-symbols-outlined text-primary">account_balance</span>
</div>
<div className="space-y-3 text-sm">
<div>
<span className="text-xs text-secondary">Bank Entity</span>
<p className="font-bold">State Bank of India</p>
</div>
<div>
<span className="text-xs text-secondary">Account Number</span>
<p className="font-mono font-bold"> 4589</p>
</div>
<div className="pt-2">
<span className="bg-green-50 text-green-700 px-2.5 py-0.5 rounded text-xs font-semibold border border-green-200 flex items-center gap-1 w-fit">
<span className="material-symbols-outlined text-xs">verified</span> Verified
</span>
</div>
</div>
</div>
</div>
</div>
)}
{/* ANALYTICS TAB */}
{dashTab === 'analytics' && (
<div className="space-y-8 animate-fadeIn">
<div>
<h2 className="font-caslon text-3xl font-bold text-primary">Performance Analytics</h2>
<p className="text-secondary text-sm font-medium">Verify your shop growth indicators and user conversion rates.</p>
</div>
2026-08-09 01:53:57 +00:00
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
<div className="bg-white border border-[#D9C5B2] rounded-xl p-4 shadow-diffused">
<span className="text-xs text-secondary font-semibold uppercase">Total Revenue</span>
<p className="font-caslon text-2xl font-bold text-primary mt-1"> 1,24,500</p>
<span className="text-[10px] text-green-600 font-semibold"> +12.4% vs last mo</span>
</div>
<div className="bg-white border border-[#D9C5B2] rounded-xl p-4 shadow-diffused">
<span className="text-xs text-secondary font-semibold uppercase">Total Orders</span>
<p className="font-caslon text-2xl font-bold text-primary mt-1">342</p>
<span className="text-[10px] text-green-600 font-semibold"> +5.2% vs last mo</span>
</div>
<div className="bg-white border border-[#D9C5B2] rounded-xl p-4 shadow-diffused">
<span className="text-xs text-secondary font-semibold uppercase">Average Ticket</span>
<p className="font-caslon text-2xl font-bold text-primary mt-1"> 364</p>
<span className="text-[10px] text-secondary font-semibold"> Stable</span>
</div>
<div className="bg-white border border-[#D9C5B2] rounded-xl p-4 shadow-diffused">
<span className="text-xs text-secondary font-semibold uppercase">Conv. Rate</span>
<p className="font-caslon text-2xl font-bold text-primary mt-1">3.2%</p>
<span className="text-[10px] text-green-600 font-semibold"> +0.4% vs last wk</span>
</div>
</div>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Simulated bar chart */}
<div className="lg:col-span-2 bg-white border border-[#D9C5B2] rounded-xl p-6 shadow-diffused space-y-4">
<div className="flex justify-between items-center">
<h4 className="font-semibold text-primary">Sales Over Time</h4>
<select className="border border-[#D9C5B2] rounded px-2 py-1 text-xs bg-white focus:outline-none">
<option>Last 7 Days</option>
<option>Last 30 Days</option>
</select>
</div>
<div className="flex items-end justify-between h-48 pt-4">
{[{ day: 'Mon', val: 60 }, { day: 'Tue', val: 90 }, { day: 'Wed', val: 70 }, { day: 'Thu', val: 120 }, { day: 'Fri', val: 110 }, { day: 'Sat', val: 160 }, { day: 'Sun', val: 140 }].map(bar => (
<div key={bar.day} className="flex flex-col items-center flex-1 space-y-2">
<div className="w-8 bg-primary rounded-t-sm transition-all" style={{ height: `${bar.val}px` }}></div>
<span className="text-xs text-secondary">{bar.day}</span>
</div>
))}
</div>
</div>
<div className="bg-white border border-[#D9C5B2] rounded-xl p-6 shadow-diffused space-y-6">
<h4 className="font-semibold text-primary">Top Category Breakdown</h4>
<div className="space-y-4">
{[
{ name: 'Handloom Textiles', pct: '45%' },
{ name: 'Pottery & Ceramics', pct: '30%' },
{ name: 'Wood Carving', pct: '15%' },
{ name: 'Jewelry', pct: '10%' }
].map(c => (
<div key={c.name} className="space-y-1.5">
<div className="flex justify-between text-xs font-semibold">
<span>{c.name}</span>
<span className="text-primary">{c.pct}</span>
</div>
<div className="h-2 bg-background rounded-full overflow-hidden border border-[#D9C5B2]/30">
<div className="h-full bg-primary" style={{ width: c.pct }}></div>
</div>
</div>
))}
</div>
</div>
</div>
2026-08-09 01:53:57 +00:00
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
<div className="bg-white border border-[#D9C5B2] rounded-xl p-6 shadow-diffused flex flex-col justify-between h-64">
<h4 className="font-semibold text-primary">Customer Demographics</h4>
<div className="flex items-center justify-center gap-8 flex-1">
<div className="relative w-32 h-16 border-[16px] border-[#D9C5B2]/30 border-b-0 rounded-t-full flex items-center justify-center">
<div className="absolute inset-0 border-[16px] border-primary border-b-0 rounded-t-full -left-[16px] -top-[16px] clip-half"></div>
<span className="text-xl font-bold text-primary mt-4">68%</span>
2026-08-20 12:53:21 +00:00
</div>
<div className="text-xs space-y-1 font-medium">
<p><span className="text-primary text-base"></span> Domestic Sales (68%)</p>
<p><span className="text-outline-variant text-base"></span> Global Exports (32%)</p>
2026-08-20 12:53:21 +00:00
</div>
</div>
</div>
<div className="bg-white border border-[#D9C5B2] rounded-xl p-6 shadow-diffused space-y-4 h-64">
<h4 className="font-semibold text-primary">Top Shipping Regions</h4>
<div className="bg-background border border-[#D9C5B2] rounded-lg p-6 flex flex-col justify-center items-center text-center space-y-2 h-44">
<p className="text-xs font-bold text-secondary">Where your crafts are loved most</p>
<div className="flex gap-4 text-xs font-bold text-primary">
<span>📍 Bengaluru</span>
<span>📍 Mumbai</span>
<span>📍 New Delhi</span>
</div>
2026-08-20 12:53:21 +00:00
</div>
</div>
</div>
</div>
)}
{/* SETTINGS TAB */}
{dashTab === 'settings' && (
<div className="bg-white border border-[#D9C5B2] rounded-xl p-8 max-w-2xl shadow-diffused">
<h2 className="font-caslon text-2xl font-bold text-primary mb-6">Store Configurations</h2>
<form onSubmit={e => {
e.preventDefault();
saveProfileBackend(true)
.then(() => alert('Store configurations successfully committed!'))
.catch(err => alert(err.message));
}} className="space-y-6">
<div className="grid grid-cols-2 gap-4">
<div>
<label className="text-xs font-semibold text-secondary uppercase block mb-1">Store Name *</label>
<input
type="text"
className="w-full bg-background border border-[#D9C5B2] rounded px-4 py-2 text-sm focus:outline-none"
value={storeName}
onChange={e => setStoreName(e.target.value)}
required
/>
</div>
<div>
<label className="text-xs font-semibold text-secondary uppercase block mb-1">Store Slug URL *</label>
<input
type="text"
className="w-full bg-background border border-[#D9C5B2] rounded px-4 py-2 text-sm focus:outline-none"
value={storeSlug}
onChange={e => setStoreSlug(e.target.value.toLowerCase().replace(/[^a-z0-9\-]/g, ''))}
required
/>
2026-08-20 12:53:21 +00:00
</div>
</div>
<div>
<label className="text-xs font-semibold text-secondary uppercase block mb-1">Public Support Email *</label>
<input
type="email"
className="w-full bg-background border border-[#D9C5B2] rounded px-4 py-2 text-sm focus:outline-none"
value={supportEmail}
onChange={e => setSupportEmail(e.target.value)}
required
/>
</div>
<div>
<label className="text-xs font-semibold text-secondary uppercase block mb-1">Store Bio description</label>
<textarea
className="w-full bg-background border border-[#D9C5B2] rounded px-4 py-2 text-sm focus:outline-none"
rows={3}
value={businessBio}
onChange={e => setBusinessBio(e.target.value)}
/>
</div>
<button type="submit" className="w-full bg-primary text-white py-3 rounded text-sm font-semibold hover:bg-primary/95 transition-colors">
Save Store Configurations
</button>
</form>
</div>
)}
</div>
{/* Footer */}
<footer className="border-t border-[#D9C5B2] bg-white py-6 text-center text-xs text-secondary mt-auto">
<div className="max-w-7xl mx-auto px-8 flex justify-between items-center">
<span>© {new Date().getFullYear()} {CONFIG.companyName}. All rights reserved.</span>
<div className="flex gap-4">
<a href="#about" className="hover:underline">About</a>
<a href="#terms" className="hover:underline">Terms</a>
<a href="#privacy" className="hover:underline">Privacy</a>
</div>
</div>
</footer>
</main>
</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="#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>
</>
)
}
2026-08-09 05:29:03 +00:00
function WelcomeTourWizard({ onComplete }: { onComplete: () => void }) {
2026-08-09 05:29:03 +00:00
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 🎬"
2026-08-09 05:29:03 +00:00
},
{
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 🚀"
2026-08-09 05:29:03 +00:00
}
]
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>
2026-08-09 05:29:03 +00:00
<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' }}>
2026-08-09 05:29:03 +00:00
{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' : ''}
2026-08-09 05:29:03 +00:00
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>
)
}