Add Super Admin login routing and wire tenant onboarding to auto-create a location
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 <noreply@anthropic.com>
This commit is contained in:
@@ -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<any>(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());
|
||||
</p>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => { setTenantSuccess(null); setTenantForm({ tenantname: '', companyname: '', primarycontact: '', primaryemail: '', address: '', suburb: '', city: 'Coimbatore', state: 'Tamil Nadu', postcode: '' }); }}
|
||||
onClick={() => { setTenantSuccess(null); setTenantForm(TENANT_FORM_INITIAL); }}
|
||||
className="bg-emerald-600 hover:bg-emerald-700 text-white font-bold text-xs uppercase tracking-wider px-5 py-2.5 rounded-lg border-none cursor-pointer"
|
||||
>
|
||||
Onboard Another Tenant
|
||||
@@ -300,6 +322,33 @@ VALUES (${newUserId}, 1, 'Active', NOW());
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div className="space-y-1">
|
||||
<label className="text-[10px] font-bold text-slate-450 uppercase tracking-widest">Primary Outlet Name (*)</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="e.g. Kaveri Groceries - Main Branch"
|
||||
value={tenantForm.locationname}
|
||||
onChange={(e) => 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
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<label className="text-[10px] font-bold text-slate-450 uppercase tracking-widest">Business Category</label>
|
||||
<select
|
||||
value={tenantForm.categoryid}
|
||||
onChange={(e) => setTenantForm({ ...tenantForm, categoryid: Number(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 cursor-pointer"
|
||||
>
|
||||
<option value={1}>Food & Dining</option>
|
||||
<option value={2}>Grocery & Daily</option>
|
||||
<option value={3}>Pharmacy</option>
|
||||
<option value={4}>Retail</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1">
|
||||
<label className="text-[10px] font-bold text-slate-450 uppercase tracking-widest">HQ Street Address</label>
|
||||
<input
|
||||
@@ -972,7 +1021,7 @@ VALUES (${newUserId}, 1, 'Active', NOW());
|
||||
Tenant <strong>{tenantForm.tenantname}</strong> has been provisioned. An administrator account has been dispatched with credentials tied to <strong>{tenantForm.primaryemail}</strong>.
|
||||
</p>
|
||||
<button
|
||||
onClick={() => { setTenantSuccess(null); setTenantForm({ tenantname: '', companyname: '', primarycontact: '', primaryemail: '', address: '', suburb: '', city: 'Coimbatore', state: 'Tamil Nadu', postcode: '' }); }}
|
||||
onClick={() => { setTenantSuccess(null); setTenantForm(TENANT_FORM_INITIAL); }}
|
||||
className="bg-emerald-600 hover:bg-emerald-700 text-white font-bold text-xs uppercase tracking-wider px-5 py-2.5 rounded-lg border-none cursor-pointer"
|
||||
>
|
||||
Onboard Another Tenant
|
||||
|
||||
Reference in New Issue
Block a user