// Sidebar and HeaderBar are now loaded from src/components via index.html
function App() {
const [email, setEmail] = React.useState('');
const [password, setPassword] = React.useState('');
const [token, setToken] = React.useState(localStorage.getItem('sales_token') || '');
const [hasAccess, setHasAccess] = React.useState(null); // null=unknown, true/false
const [loading, setLoading] = React.useState(false);
const [error, setError] = React.useState('');
const [view, setView] = React.useState((location.hash || '#bank').slice(1));
const [planId, setPlanId] = React.useState('');
const [branchLimit, setBranchLimit] = React.useState(0);
const [branchUser, setBranchUser] = React.useState(null);
// Device detection for responsive layout
const { isMobile } = useDeviceDetection();
const SALES_URL = window.ENV_CONFIG?.SALES_API_URL || 'http://127.0.0.1:9000';
const decodeJwt = (tk) => {
try {
const base64 = tk.split('.')[1];
const json = atob(base64.replace(/-/g, '+').replace(/_/g, '/'));
return JSON.parse(json);
} catch { return null; }
};
const login = async (e) => {
e.preventDefault();
setError('');
setLoading(true);
try {
const res = await fetch(SALES_URL + '/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password })
});
const data = await res.json();
if (!res.ok) throw new Error(data.error || data.message || 'Login failed');
if (!data.token) throw new Error('No token returned');
// Ensure branch token is removed so only one token type exists in this browser
try { localStorage.removeItem('branch_token'); } catch (e) {}
localStorage.setItem('sales_token', data.token);
setToken(data.token);
setHasAccess(true);
setBranchUser(null);
try { window.dispatchEvent(new Event('sales-login')); } catch (__) {}
const payload = data.payload || decodeJwt(data.token);
if (payload?.mongoPlanId) setPlanId(payload.mongoPlanId);
if (Number.isFinite(payload?.branchLimit)) setBranchLimit(payload.branchLimit);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
const checkAccess = async (tk = token) => {
setError('');
setLoading(true);
try {
const res = await fetch(SALES_URL + '/auth/verify', { headers: { Authorization: 'Bearer ' + tk } });
const data = await res.json();
if (!res.ok || !data.valid) throw new Error(data.message || 'Token invalid');
setHasAccess(true);
if (data.token) {
localStorage.setItem('sales_token', data.token);
setToken(data.token);
}
const decoded = data.decoded || data.payload || decodeJwt(data.token || tk);
if (decoded?.mongoPlanId) setPlanId(decoded.mongoPlanId);
if (Number.isFinite(decoded?.branchLimit)) setBranchLimit(decoded.branchLimit);
} catch (err) {
setError(err.message);
setHasAccess(false);
} finally {
setLoading(false);
}
};
React.useEffect(() => {
if (localStorage.getItem('token') && !localStorage.getItem('sales_token')) {
localStorage.removeItem('token');
}
if (token && hasAccess === null) checkAccess(token);
const onHash = () => setView((location.hash || '#bank').slice(1));
window.addEventListener('hashchange', onHash);
// Handle branch-login events dispatched from BranchLogin so same-tab updates work
const onBranchLoginEvent = () => {
const bt = localStorage.getItem('branch_token');
if (bt) {
const d = decodeJwt(bt);
setBranchUser(d || null);
// Ensure sales token isn't used
setToken('');
setHasAccess(false);
try { location.hash = '#branch'; } catch (_) {}
}
};
window.addEventListener('branch-login', onBranchLoginEvent);
const onSalesLoginEvent = () => {
// sales login happened elsewhere in-app/tab: clear branchUser
setBranchUser(null);
};
window.addEventListener('sales-login', onSalesLoginEvent);
// update branchUser state when branch_token changes elsewhere
const syncBranchUser = () => {
const bt = localStorage.getItem('branch_token');
if (bt) {
const d = decodeJwt(bt);
setBranchUser(d || null);
} else setBranchUser(null);
};
syncBranchUser();
window.addEventListener('storage', syncBranchUser);
return () => {
window.removeEventListener('hashchange', onHash);
window.removeEventListener('storage', syncBranchUser);
window.removeEventListener('branch-login', onBranchLoginEvent);
window.removeEventListener('sales-login', onSalesLoginEvent);
};
}, []);
if (loading && hasAccess === null) return
Loading…
;
// If we're a branch user, render the app immediately (use branch_token as effective token)
const effectiveToken = branchUser ? (localStorage.getItem('branch_token') || '') : token;
// If we're not a branch user and we don't have a valid sales token, show auth screens
if (!branchUser && ((!token) || hasAccess === false)) {
// Allow visiting public branch-login route even without sales token
if ((location.hash || '#bank').slice(1) === 'branch-login') {
if (isMobile) {
return (
{}}
active="branch-login"
onSelect={() => {}}
planId=""
branchLimit={0}
branchUser={null}
>
);
}
return ;
}
// No sales token and not a branch user -> show sales login
if (isMobile) {
return (
checkAccess()}
loading={loading}
error={error}
token={token}
/>
);
}
return (
🚀
Welcome Back
Sign in to your SalesPro account
{error && (
)}
);
}
const logout = () => {
localStorage.removeItem('sales_token');
localStorage.removeItem('branch_token');
setToken('');
setBranchUser(null);
setHasAccess(false);
location.hash = '#bank';
};
const branchLogout = () => {
// Only remove branch token, keep sales token for admin
localStorage.removeItem('branch_token');
setBranchUser(null);
// Restore admin access by checking existing sales token
const salesToken = localStorage.getItem('sales_token');
if (salesToken) {
setToken(salesToken);
checkAccess(salesToken);
// Trigger feature reload for admin account
try {
window.dispatchEvent(new Event('sales-login'));
} catch (e) {
console.log('Event dispatch failed:', e);
}
}
location.hash = '#bank';
};
// Helper function to get page title
const getTitle = () => {
if (branchUser) return 'Branch Dashboard';
switch (view) {
case 'bank': return 'Payment Methods';
case 'bank-history': return 'Payment History';
case 'supplier': return 'Supplier Management';
case 'branch': return 'Branch Management';
case 'whatsapp-contact': return 'WhatsApp Contacts';
case 'whatsapp-stock': return 'WhatsApp Inventory';
case 'product-sales': return 'Point of Sale';
case 'sales-track': return 'Sales Analytics';
case 'branch-expense': return 'Expenses';
case 'seconds-sales': return 'Quick Sales';
case 'offer': return 'Promotions';
default: return 'Master Inventory';
}
};
// Helper function to get page subtitle
const getSubtitle = () => {
if (branchUser) return 'Manage your branch operations and sales';
switch (view) {
case 'bank': return 'Set up payment methods for your business';
case 'bank-history': return 'View all payment transactions and balances';
case 'supplier': return 'Manage your suppliers and vendors';
case 'branch': return 'Create and manage branch locations';
case 'whatsapp-contact': return 'Manage WhatsApp customer contacts';
case 'whatsapp-stock': return 'Track WhatsApp-specific inventory';
case 'product-sales': return 'Process customer sales and transactions';
case 'sales-track': return 'Monitor sales performance and trends';
case 'branch-expense': return 'Record branch expenses and costs';
case 'seconds-sales': return 'Quick sale processing for busy periods';
case 'offer': return 'Create and manage promotional offers';
default: return 'Track and manage your complete inventory';
}
};
// Main content component
const MainContent = () => {
// Show mobile dashboard only on default view when on mobile
if (isMobile && view === 'bank') {
return (
{
setView(actionId);
try { location.hash = '#' + actionId; } catch {}
}}
/>
);
}
return (
<>
{branchUser ? (
// branch-specific welcome screen as the first nav item
view === 'branch-welcome' || view === '' || view === 'branch' ? (
Welcome Welcome, {branchUser.name || 'Branch User'}!
) : null
) : null}
{view === 'bank' ? (
) : view === 'bank-history' ? (
window.BankHistory ? React.createElement(window.BankHistory, {
salesUrl: SALES_URL,
token: effectiveToken
}) : React.createElement('div', { style: { padding: '20px' } }, 'Loading Payment History...')
) : (!branchUser && view === 'gst-calculator') ? (
(window.GstCalculatorView ? React.createElement(window.GstCalculatorView) : (
))
) : (view === 'supplier') ? (
) : (!branchUser && view === 'branch') ? (
(planId === 'sales-gold' || planId === 'sales-premium') ? (
) : (
🔒
Upgrade Required
Branch management is available on Sales Gold and Premium plans.
)
) : view === 'branch-login' ? (
) : view === 'branch-supply' ? (
) : (!branchUser && view === 'whatsapp-stock') ? (
(window.WhatsappStock ? React.createElement(window.WhatsappStock, { salesUrl: SALES_URL, token: effectiveToken }) : (
))
) : (!branchUser && view === 'whatsapp-contact') ? (
(window.WhatsappContact ? React.createElement(window.WhatsappContact, { salesUrl: SALES_URL, token: effectiveToken }) : (
))
) : (!branchUser && view === 'seconds-sales') ? (
(window.SecondsSales ? React.createElement(window.SecondsSales, { salesUrl: SALES_URL, token: effectiveToken }) : (
))
) : (branchUser && view === 'seconds-sales') ? (
(window.SecondsSales ? React.createElement(window.SecondsSales, { salesUrl: SALES_URL, token: effectiveToken }) : (
))
) : (branchUser && (view || '').startsWith('seconds-sales-view-')) ? (
// extract id after prefix
(() => {
const id = (view || '').replace('seconds-sales-view-', '');
return (window.SecondsSalesView ? React.createElement(window.SecondsSalesView, { salesUrl: SALES_URL, token: effectiveToken, id }) : (
));
})()
) : view === 'branch-supply-history' ? (
(window.BranchSupplyHistory ? React.createElement(window.BranchSupplyHistory, { salesUrl: SALES_URL, token: effectiveToken }) : (
📦
Loading…
Branch Supply History component not loaded yet.
))
) : view === 'stock-history' ? (
(window.StockHistory ? React.createElement(window.StockHistory, { salesUrl: SALES_URL, token: effectiveToken, branchUser }) : (
📚
Loading…
Stock History component not loaded yet.
))
) : view === 'branch-expense' ? (
(window.BranchNewExpense ? React.createElement(window.BranchNewExpense, { salesUrl: SALES_URL, token: effectiveToken, branchUser }) : (
))
) : view === 'sales-track' ? (
(window.SalesTrack ? React.createElement(window.SalesTrack, { salesUrl: SALES_URL, token: effectiveToken }) : (
))
) : view === 'product-sales' ? (
(window.ProductSales ? React.createElement(window.ProductSales, { salesUrl: SALES_URL, token: effectiveToken }) : (
))
) : (
view === 'instock' && branchUser ? (
) : (
)
)}
>
);
};
// Render mobile or desktop layout based on device detection
if (isMobile) {
return (
);
}
return (
);
}
// Modern CreateBranch component with enhanced UI
function CreateBranch({ salesUrl, token, planId, branchLimit = 0 }) {
const [form, setForm] = React.useState({
name: '', address: '', gstNo: '', phoneNumber: '', email: '', password: '', confirmPassword: ''
});
const [rows, setRows] = React.useState([]);
const [saving, setSaving] = React.useState(false);
const [error, setError] = React.useState('');
const [page, setPage] = React.useState(1);
const [pageSize] = React.useState(10);
const onChange = (e) => {
const { name, value } = e.target;
setForm((f) => ({ ...f, [name]: value }));
};
const loadBranches = async () => {
try {
setError('');
const res = await fetch(salesUrl + '/api/branches', { headers: { Authorization: 'Bearer ' + token } });
const data = await res.json();
if (!res.ok) throw new Error(data.message || 'Failed to load branches');
setRows(Array.isArray(data.branches) ? data.branches : []);
setPage(1);
} catch (e) { setError(e.message); }
};
React.useEffect(() => { loadBranches(); }, []);
const submit = async (e) => {
e.preventDefault();
setError('');
if (form.password !== form.confirmPassword) return setError('Passwords do not match');
if (rows.length >= branchLimit) return setError('Branch limit reached for your plan');
setSaving(true);
try {
const res = await fetch(salesUrl + '/api/branches', {
method: 'POST',
headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + token },
body: JSON.stringify({
name: form.name,
address: form.address,
gstNo: form.gstNo, // Added GST No field
phoneNumber: form.phoneNumber,
email: form.email,
password: form.password,
}),
});
const data = await res.json();
if (!res.ok || !data.success) throw new Error(data.message || 'Create failed');
setForm({ name: '', address: '', gstNo: '', phoneNumber: '', email: '', password: '', confirmPassword: '' });
await loadBranches();
} catch (e) {
setError(e.message);
} finally {
setSaving(false);
}
};
// pagination
const total = rows.length;
const startIndex = total === 0 ? 0 : (page - 1) * pageSize + 1;
const endIndex = Math.min(page * pageSize, total);
const totalPages = Math.max(1, Math.ceil(total / pageSize));
const visible = rows.slice((page - 1) * pageSize, (page - 1) * pageSize + pageSize);
return (
{/* Statistics Cards */}
🏪
Branch Limit
{Number.isFinite(branchLimit) ? branchLimit : 0}
Plan: {planId || 'Basic'}
🌟
Active Branches
{rows.length}
{rows.length > 0 ? 'Operational' : 'Getting Started'}
📊
Available Slots
{Math.max(0, branchLimit - rows.length)}
{branchLimit - rows.length > 0 ? 'Ready to expand' : 'Limit reached'}
{/* Create Branch Form */}
Create New Branch
Add a new branch location to expand your business
{/* Branches Table */}
Branch Locations
Manage all your business locations
{visible.length === 0 ? (
�
No Branches Yet
Create your first branch location to get started
document.querySelector('input[name="name"]')?.focus()}>
🏪 Create First Branch
) : (
<>
No.
Branch Name
Address
Contact
Email
GST No
Status
{visible.map((r, i) => (
{startIndex + i}
{r.name || '-'}
{r.address || '-'}
{r.phoneNumber || '-'}
{r.email || '-'}
{r.gstNo || '-'}
Active
))}
{total === 0 ? 'No branches found' : `Showing ${startIndex} to ${endIndex} of ${total} branches`}
setPage(p => Math.max(1, p - 1))}
disabled={page <= 1}
>
← Previous
Page {page} of {totalPages}
setPage(p => Math.min(totalPages, p + 1))}
disabled={page >= totalPages}
>
Next →
>
)}
);
}
const rootEl = document.getElementById('root');
const root = ReactDOM.createRoot(rootEl);
// Wrap App with SalesFeatureProvider
const AppWithFeatures = () => {
return React.createElement(window.SalesFeatureProvider, {}, React.createElement(App));
};
root.render(React.createElement(AppWithFeatures));