supplier_central_frontend/src/context/SellerContext.tsx
vickytechkey 114fa4706c
All checks were successful
Beta CI/CD Pipeline / build-and-test (push) Successful in 58s
Beta CI/CD Pipeline / deploy-beta (push) Successful in 9s
Fix unit tests to match new dashboard layout and onboarding flow
2026-08-21 10:55:03 +05:30

822 lines
30 KiB
TypeScript

import React, { createContext, useContext, useState, useEffect } from 'react';
import { CONFIG, apiFetch } from '../config';
type Page = 'home' | 'about' | 'contact' | 'login' | 'signup' | 'profile-completion' | 'confirmation' | 'dashboard' | 'forgot-password' | 'login-otp' | 'welcome-tour';
type DashboardTab = 'overview' | 'products' | 'orders' | 'returns' | 'wallet' | 'settings' | 'barcode-generator' | '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;
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 const SellerContext = createContext<any>(null);
export const useSeller = () => useContext(SellerContext);
export const SellerProvider: React.FC<{children: React.ReactNode}> = ({ children }) => {
const [currentPage, setCurrentPage] = useState<Page>('home')
const [isSidebarOpen, setIsSidebarOpen] = useState(false)
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false)
const [activeTab, setActiveTab] = useState<'login' | 'register'>('login')
// Onboarding Status
const [isProfileComplete, setIsProfileComplete] = useState(false)
const [profileStep, setProfileStep] = useState(1)
// Registration / Onboarding Form States
const [email, setEmail] = useState('')
const [phone, setPhone] = useState('')
const [password, setPassword] = useState('')
// Step 1: Verification
const [phoneVerified, setPhoneVerified] = useState(false)
const [emailVerified, setEmailVerified] = useState(false)
const [phoneOtpSent, setPhoneOtpSent] = useState(false)
const [emailOtpSent, setEmailOtpSent] = useState(false)
const [enteredPhoneOtp, setEnteredPhoneOtp] = useState('')
const [enteredEmailOtp, setEnteredEmailOtp] = useState('')
// Step 2: Business details (moved GSTIN here)
const [gstin, setGstin] = useState('29AAAAA1111A1Z1')
const [isGstinVerified, setIsGstinVerified] = useState(false)
const [aadharFile, setAadharFile] = useState<string | null>(null)
const [panFile, setPanFile] = useState<string | null>(null)
const [aadharS3Key, setAadharS3Key] = useState<string | null>(null)
const [panS3Key, setPanS3Key] = useState<string | null>(null)
// 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[]>([])
// Step 3: Store and Location details
const [storeName, setStoreName] = useState('My Artisan Handloom')
const [storeLogo, setStoreLogo] = useState<string | null>(null)
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'
})
const [mapCoordinates, setMapCoordinates] = useState({ lat: 12.9716, lng: 77.5946 })
// --- Password visibility, confirm password and policy states ---
const [showLoginPass, setShowLoginPass] = useState(false)
const [showSignupPass, setShowSignupPass] = useState(false)
const [showSignupConfirmPass, setShowSignupConfirmPass] = useState(false)
const [confirmPassword, setConfirmPassword] = useState('')
const [policyAccepted, setPolicyAccepted] = useState(false)
// --- Password Reset Page States ---
const [resetEmail, setResetEmail] = useState('')
const [resetOtpSent, setResetOtpSent] = useState(false)
const [resetOtp, setResetOtp] = useState('')
const [resetPassword, setResetPassword] = useState('')
// --- OTP Login States ---
const [otpLoginPhone, setOtpLoginPhone] = useState('')
const [otpLoginSent, setOtpLoginSent] = useState(false)
const [otpLoginCode, setOtpLoginCode] = useState('')
// --- Barcode Generator States ---
const [barcodeProductSku, setBarcodeProductSku] = useState('')
const [barcodeGenerated, setBarcodeGenerated] = useState(false)
// --- Active Dashboard States ---
const [dashTab, setDashTab] = useState<DashboardTab>('overview')
const [dateFilter, setDateFilter] = useState<DateFilter>('year')
const [customDates, setCustomDates] = useState({ start: '2026-06-01', end: '2026-06-15' })
// Lists
const [products, setProducts] = useState<Product[]>([])
const [orders, setOrders] = useState<Order[]>([])
const [returns, setReturns] = useState<ReturnRequest[]>([])
// Forms & Editing
const [productForm, setProductForm] = useState({
id: '',
title: '',
category: 'Apparel',
price: 0,
stock: 0,
sku: '',
image: 'https://images.unsplash.com/photo-1544022613-e87ca75a784a?auto=format&fit=crop&q=80&w=100'
})
const [isEditingProduct, setIsEditingProduct] = useState(false)
// 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
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 || []);
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);
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]);
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 = () => {
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
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)
const s3Key = await uploadDocument(file, 'logo');
setLogoS3Key(s3Key);
}
}
// Route/Navigation Guard: If profile is not complete, redirect to profile-completion
const navigateTo = (page: Page, forceComplete: boolean = false) => {
const publicPages: Page[] = ['home', 'about', 'contact', 'login', 'signup', 'forgot-password', 'login-otp']
const complete = isProfileComplete || forceComplete
if (!complete && !publicPages.includes(page) && page !== 'profile-completion') {
alert('Access Denied: Please complete your supplier profile first!')
setCurrentPage('profile-completion')
} else {
setCurrentPage(page)
}
window.scrollTo({ top: 0, behavior: 'smooth' })
}
const handleLogout = () => {
apiFetch(`${CONFIG.apiBaseUrl}/api/auth/logout/`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' }
})
.catch(err => console.error('Error logging out:', err))
.finally(() => {
localStorage.removeItem('access_token')
// 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)
setAadharS3Key(null)
setPanS3Key(null)
setStoreName('My Artisan Handloom')
setStoreLogo(null)
setLogoS3Key(null)
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,
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 })
})
.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();
})
.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);
});
}
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,
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;
saveProfileBackend(isLastStep)
.then(() => {
if (!isLastStep) {
setProfileStep(prev => prev + 1)
} else {
setProfileStep(7);
}
})
.catch(err => {
alert(err.message);
});
}
// --- Dashboard Logic Actions ---
// Product creation/modification
const handleSaveProduct = (e: React.FormEvent) => {
e.preventDefault()
const url = isEditingProduct
? `${CONFIG.apiBaseUrl}/api/products/${productForm.id}/`
: `${CONFIG.apiBaseUrl}/api/products/`
const method = isEditingProduct ? 'PUT' : 'POST'
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...']);
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()
const contextValue = {
setBusinessBio, email, resetPassword, setLogoS3Key, setStoreName, setShowLoginPass, storeName, otpLoginSent, setOtpLoginSent, aadharS3Key, logoS3Key, customDates, setProductFormDetails, handleBulkUploadSubmit, setMapCoordinates, setAadharFile, gstin, mapCoordinates, setIsGstinVerified, enteredPhoneOtp, address, setEmailVerified, dashTab, selectedOrderDetail, setShowSignupConfirmPass, handleRegisterSubmit, setBulkCsvFile, confirmPassword, navigateTo, setResetPassword, orderNotes, setOrders, handleRejectOrder, barcodeProductSku, setEnteredEmailOtp, setIsEditingProduct, setBulkLog, isMobileMenuOpen, resetOtp, productWizardStep, setCustomDates, setConfirmPassword, isGstinVerified, bulkCsvFile, setAddress, storeLogo, handleProfileSubmit, setPhoneOtpSent, setOrderNotes, handleDeleteProduct, setWallet, policyAccepted, emailOtpSent, setResetOtpSent, setGstin, phoneOtpSent, showLoginPass, showSignupConfirmPass, setPhoneVerified, aadharFile, setDateFilter, activeTab, computeRealtimeMetrics, setIsParsingBulk, handleLogout, panS3Key, setCurrentPage, selectedCategories, setEmailOtpSent, showSignupPass, resetEmail, handleLogoChange, dateFilter, setShowSignupPass, businessBio, setWithdrawAmount, phoneVerified, currentPage, setResetOtp, enteredEmailOtp, setEnteredPhoneOtp, setPanFile, setIsSidebarOpen, setSelectedOrderDetail, handleAcceptOrder, saveProfileBackend, supportPhone, handleEditClick, setStoreLogo, setEmail, password, setOtpLoginPhone, bulkLog, businessType, setPanS3Key, setProducts, setSupportEmail, setOtpLoginCode, withdrawAmount, handleSaveProduct, setStoreSlug, setBarcodeProductSku, setProductForm, setBulkZipFile, productFormDetails, setIsProfileComplete, otpLoginPhone, orders, setActiveTab, bulkZipFile, isSidebarOpen, setBarcodeGenerated, setBusinessType, handleReturnAction, setSupportPhone, barcodeGenerated, products, productForm, setProductWizardStep, resetOtpSent, emailVerified, panFile, isParsingBulk, setDashTab, setReturns, setPassword, handleWithdrawRequest, uploadDocument, setPolicyAccepted, isEditingProduct, setIsMobileMenuOpen, profileStep, isProfileComplete, setSelectedCategories, storeSlug, phone, setPhone, setAadharS3Key, handleVerifyGstin, setProfileStep, returns, handleLoginSubmit, setResetEmail, wallet, otpLoginCode, supportEmail
};
return (
<SellerContext.Provider value={contextValue}>
{children}
</SellerContext.Provider>
);
};