From ecbae9d8fce0da67c6d41e547bd3fec00b0e0d79 Mon Sep 17 00:00:00 2001 From: Suriya Date: Thu, 16 Jul 2026 20:46:07 +0530 Subject: [PATCH] Add Super Admin login routing and wire tenant onboarding to auto-create a location MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit super_admin is now a real LoginRole, derived from the server's issuperadmin flag (not a client-guessable roleid) — routes exclusively to a new minimal SuperAdminPage, never the merchant console. That page reuses the existing tenant/store/rider onboarding wizard (AdminConsole) rather than duplicating it — its Tenant tab was already calling the right composite endpoint but had nowhere to send location data, and was never actually reachable from anywhere in the app. Now it collects a primary outlet name + business category and sends a nested tenantlocations object, so one submit provisions tenant + active location + admin user instead of leaving the tenant locationless. createTenantUser also moves off the mob API base onto the web one, matching where this is actually called from. Co-Authored-By: Claude Sonnet 5 --- src/App.tsx | 12 +++++++ src/components/AdminConsole.tsx | 59 ++++++++++++++++++++++++++++--- src/components/SuperAdminPage.tsx | 45 +++++++++++++++++++++++ src/services/auth.ts | 16 +++++++-- src/services/fiestaApi.ts | 20 +++++++++-- 5 files changed, 143 insertions(+), 9 deletions(-) create mode 100644 src/components/SuperAdminPage.tsx diff --git a/src/App.tsx b/src/App.tsx index 23d4bdb..f51c333 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -54,6 +54,7 @@ import StoreDetailView from './components/StoreDetailView'; import DispatchHubView from './components/DispatchHubView'; import LoginView from './components/LoginView'; import UserStorePage from './components/UserStorePage'; +import SuperAdminPage from './components/SuperAdminPage'; import AwaitingApi from './components/AwaitingApi'; import ComparisonModal from './components/ComparisonModal'; import CatalogueBrowser from './components/CatalogueBrowser'; @@ -568,6 +569,17 @@ export default function App() { ); } + // Platform operator, not scoped to any tenant — never sees the merchant + // console (dashboard/inventory/catalogue/etc), only tenant onboarding. + if (authRole === 'super_admin' && authUser) { + return ( + + } /> + } /> + + ); + } + const AdminConsole = (
{/* Navbar segment */} diff --git a/src/components/AdminConsole.tsx b/src/components/AdminConsole.tsx index 5f126b4..234b889 100644 --- a/src/components/AdminConsole.tsx +++ b/src/components/AdminConsole.tsx @@ -51,7 +51,7 @@ export default function AdminConsole({ activeTab: propActiveTab, showHeader = tr // ---------------------------------------------------- // Form State: Tenant Onboarding // ---------------------------------------------------- - const [tenantForm, setTenantForm] = useState({ + const TENANT_FORM_INITIAL = { tenantname: '', companyname: '', primarycontact: '', @@ -61,7 +61,15 @@ export default function AdminConsole({ activeTab: propActiveTab, showHeader = tr city: 'Coimbatore', state: 'Tamil Nadu', postcode: '', - }); + // Primary outlet — created in the same call as the tenant, so a fresh + // tenant is never left without a location to actually operate from. + locationname: '', + // 1 Food & Dining, 2 Grocery & Daily, 3 Pharmacy, 4 Retail — the same + // categories categoryName() in fiestaMappers.ts already displays. + categoryid: 2, + applocationid: 1, + }; + const [tenantForm, setTenantForm] = useState(TENANT_FORM_INITIAL); const [tenantSuccess, setTenantSuccess] = useState(null); // ---------------------------------------------------- @@ -124,7 +132,7 @@ export default function AdminConsole({ activeTab: propActiveTab, showHeader = tr // ---------------------------------------------------- const handleTenantSubmit = async (e: React.FormEvent) => { e.preventDefault(); - if (!tenantForm.tenantname || !tenantForm.companyname || !tenantForm.primarycontact || !tenantForm.primaryemail) { + if (!tenantForm.tenantname || !tenantForm.companyname || !tenantForm.primarycontact || !tenantForm.primaryemail || !tenantForm.locationname) { alert('Kindly fill in all required fields.'); return; } @@ -133,6 +141,20 @@ export default function AdminConsole({ activeTab: propActiveTab, showHeader = tr ...tenantForm, approved: 1, status: 'Active', + // Reuses the tenant's own address for its primary location — a + // freshly onboarded single-location merchant's outlet address is + // the same as their registered one. + tenantlocations: { + locationname: tenantForm.locationname, + email: tenantForm.primaryemail, + contactno: tenantForm.primarycontact, + address: tenantForm.address, + suburb: tenantForm.suburb, + city: tenantForm.city, + state: tenantForm.state, + postcode: tenantForm.postcode, + applocationid: tenantForm.applocationid, + }, }); setTenantSuccess(res); alert('Tenant Onboarded successfully!'); @@ -244,7 +266,7 @@ VALUES (${newUserId}, 1, 'Active', NOW());

+
+
+ + setTenantForm({ ...tenantForm, locationname: e.target.value })} + className="w-full border border-slate-250 rounded-xl p-3 bg-slate-50/40 hover:bg-slate-100 focus:bg-white outline-none focus:border-purple-500 transition-all font-semibold text-xs text-slate-800" + required + /> +
+
+ + +
+
+
{tenantForm.tenantname} has been provisioned. An administrator account has been dispatched with credentials tied to {tenantForm.primaryemail}.