Files
daily_merchant_web/src/components/AdminConsole.tsx
Suriya f029c09dd7 Fix missing Primary Outlet Name field on the Super Admin tenant form
AdminConsole renders the Tenant Onboarding form twice (embedded vs.
showHeader standalone) from two separate JSX blocks. The Primary
Outlet Name / Business Category fields added earlier only landed in
the embedded block, so the showHeader block SuperAdminPage actually
renders had no way to fill tenantForm.locationname — the required-
field check on submit failed unconditionally, no matter what was
filled in.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-21 16:56:02 +05:30

1624 lines
92 KiB
TypeScript

/**
* @license
* SPDX-License-Identifier: Apache-2.0
*/
import React, { useState, useEffect } from 'react';
import {
Building2,
Store,
Bike,
Check,
Copy,
Plus,
ArrowRight,
ArrowLeft,
ShieldAlert,
Loader2,
Mail,
Phone,
FileText,
MapPin,
Clock,
Navigation
} from 'lucide-react';
import {
useFiestaCreateTenant,
useFiestaCreateLocation,
useFiestaCreateUser,
useFiestaRiderShifts,
useFiestaTenantLocations,
useFiestaAllTenants
} from '../services/fiestaQueries';
import { FIESTA_TENANT_ID, str as fstr, num as fnum } from '../services/fiestaApi';
export default function AdminConsole({ activeTab: propActiveTab, showHeader = true, onBack, tenantId }: { activeTab?: 'tenant' | 'store' | 'rider', showHeader?: boolean, onBack?: () => void, tenantId?: number }) {
const [activeTab, setActiveTab] = useState<'tenant' | 'store' | 'rider'>(propActiveTab || 'tenant');
useEffect(() => {
if (propActiveTab) {
setActiveTab(propActiveTab);
}
}, [propActiveTab]);
// Mutations & Queries
const createTenantMut = useFiestaCreateTenant();
const createLocationMut = useFiestaCreateLocation();
const createUserMut = useFiestaCreateUser();
const shiftsQ = useFiestaRiderShifts();
const tenantsQ = useFiestaAllTenants({ pagesize: 100 });
// ----------------------------------------------------
// Form State: Tenant Onboarding
// ----------------------------------------------------
const TENANT_FORM_INITIAL = {
tenantname: '',
companyname: '',
primarycontact: '',
primaryemail: '',
address: '',
suburb: '',
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);
// ----------------------------------------------------
// Form State: Store Onboarding
// ----------------------------------------------------
const [storeForm, setStoreForm] = useState({
tenantid: tenantId || FIESTA_TENANT_ID,
locationname: '',
address: '',
suburb: '',
city: 'Coimbatore',
state: 'Tamil Nadu',
postcode: '',
contactno: '',
email: '',
opentime: '06:00:00',
closetime: '22:00:00',
deliverymins: 45,
deliveryradius: 5000, // in meters
});
useEffect(() => {
if (tenantId) {
setStoreForm(prev => ({ ...prev, tenantid: tenantId }));
}
}, [tenantId]);
const [storeSuccess, setStoreSuccess] = useState<any>(null);
const currentTenantObj = (tenantsQ.data || []).find((t) => Number(t.tenantid) === Number(storeForm.tenantid));
const tenantNameDisplay = currentTenantObj ? `${fstr(currentTenantObj.tenantname)} (#${fstr(currentTenantObj.tenantid)})` : `Tenant #${storeForm.tenantid}`;
// ----------------------------------------------------
// Form State: Rider Onboarding
// ----------------------------------------------------
const [riderForm, setRiderForm] = useState({
firstname: '',
lastname: '',
email: '',
contactno: '',
password: 'Rider@123',
locationid: 0,
shiftid: 0,
vehiclename: '',
vehiclemodel: '',
licensenumber: '',
});
const [riderSuccess, setRiderSuccess] = useState<any>(null);
const [copiedSql, setCopiedSql] = useState(false);
const handleCopySql = () => {
if (!riderSuccess?.sql) return;
navigator.clipboard.writeText(riderSuccess.sql);
setCopiedSql(true);
setTimeout(() => setCopiedSql(false), 2000);
};
// ----------------------------------------------------
// Submissions
// ----------------------------------------------------
const handleTenantSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!tenantForm.tenantname || !tenantForm.companyname || !tenantForm.primarycontact || !tenantForm.primaryemail || !tenantForm.locationname) {
alert('Kindly fill in all required fields.');
return;
}
try {
const res = await createTenantMut.mutateAsync({
...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!');
} catch (err: any) {
alert(err.message || 'Failed to onboard tenant.');
}
};
const handleStoreSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!storeForm.locationname) {
alert('Location Name is required.');
return;
}
try {
const res = await createLocationMut.mutateAsync({
...storeForm,
status: 'Active',
moduleid: 2,
applocationid: 1,
partnerid: 1,
roleid: 4,
});
setStoreSuccess(res);
alert('Store Location created successfully!');
} catch (err: any) {
alert(err.message || 'Failed to create store location.');
}
};
const handleRiderSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!riderForm.firstname || !riderForm.email || !riderForm.contactno || !riderForm.locationid) {
alert('Please fill out the Rider personal details and assign a store.');
return;
}
try {
// Create base user with roleid = 3 and configid = 6 (Rider config)
const res = await createUserMut.mutateAsync({
firstname: riderForm.firstname,
lastname: riderForm.lastname,
email: riderForm.email,
contactno: riderForm.contactno,
password: riderForm.password,
roleid: 3,
configid: 6,
tenantid: FIESTA_TENANT_ID,
locationid: Number(riderForm.locationid),
applocationid: 1,
status: 'active',
});
const newUserId = res.userid || Math.floor(Math.random() * 9000) + 1000;
const vehicleName = riderForm.vehiclename || 'Standard delivery bike';
const licenseNum = riderForm.licensenumber || 'TN-37-XX-XXXX';
const vehicleModel = riderForm.vehiclemodel || 'Electric Scooter';
const shiftId = Number(riderForm.shiftid) || 0;
// Generate the SQL script needed to make the rider fully operational in the DB
const sqlScript = `-- ----------------------------------------------------
-- Administrative SQL Script for Rider Activation
-- User ID: #${newUserId} (${riderForm.firstname})
-- ----------------------------------------------------
-- 1. Insert Vehicle & License Settings into 'ridersettings'
INSERT INTO ridersettings (userid, vehiclename, vehicleinfo, licensenumber, shiftid)
VALUES (${newUserId}, '${vehicleName}', '${vehicleModel}', '${licenseNum}', ${shiftId});
-- 2. Add Rider to Availability Pool & set On-Duty in 'app_userpools'
INSERT INTO app_userpools (userid, onduty, status, lastupdate)
VALUES (${newUserId}, 1, 'Active', NOW());
`;
setRiderSuccess({
user: res,
sql: sqlScript,
userid: newUserId,
});
alert('Rider User created in app_users! SQL Script generated below.');
} catch (err: any) {
alert(err.message || 'Failed to create rider user.');
}
};
// Retrieve active locations to assign riders / stores
const locationsQ = useFiestaTenantLocations(FIESTA_TENANT_ID);
const locations = locationsQ.data || [];
if (!showHeader) {
return (
<div className="space-y-gutter w-full">
{/* TAB 1: Tenant Onboarding */}
{activeTab === 'tenant' && (
<div className="bg-white border border-slate-200/60 p-6 rounded-2xl shadow-sm space-y-6">
<div>
<h3 className="text-lg font-bold text-slate-900">Provision New Merchant Tenant</h3>
<p className="text-slate-500 text-xs mt-1">
Onboard a new merchant group. This registers their enterprise, defaults the order serialization, and spawns the primary Administrator account.
</p>
</div>
{tenantSuccess ? (
<div className="bg-emerald-50 border border-emerald-100 rounded-2xl p-6 text-center space-y-4">
<div className="w-12 h-12 rounded-full bg-emerald-550 text-white flex items-center justify-center mx-auto text-xl font-bold"></div>
<h4 className="font-bold text-emerald-900 text-base">Onboarding Complete!</h4>
<p className="text-xs text-emerald-700 max-w-md mx-auto">
Tenant <strong>{tenantForm.tenantname}</strong> has been provisioned. An administrator account has been dispatched with credentials tied to <strong>{tenantForm.primaryemail}</strong>.
</p>
<button
type="button"
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
</button>
</div>
) : (
<form onSubmit={handleTenantSubmit} className="space-y-4">
<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">Merchant Name (*)</label>
<input
type="text"
placeholder="e.g. Kaveri Groceries"
value={tenantForm.tenantname}
onChange={(e) => setTenantForm({ ...tenantForm, tenantname: 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">Company Registered Name (*)</label>
<input
type="text"
placeholder="e.g. Kaveri Retail Pvt. Ltd."
value={tenantForm.companyname}
onChange={(e) => setTenantForm({ ...tenantForm, companyname: 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">Primary Phone Number (*)</label>
<input
type="text"
maxLength={10}
placeholder="e.g. 9876543210"
value={tenantForm.primarycontact}
onChange={(e) => setTenantForm({ ...tenantForm, primarycontact: e.target.value.replace(/\D/g, '') })}
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">Primary Admin Email (*)</label>
<input
type="email"
placeholder="e.g. admin@kaveri.com"
value={tenantForm.primaryemail}
onChange={(e) => setTenantForm({ ...tenantForm, primaryemail: 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>
<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
type="text"
placeholder="e.g. 12, Avinashi Road"
value={tenantForm.address}
onChange={(e) => setTenantForm({ ...tenantForm, address: 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"
/>
</div>
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
<div className="space-y-1">
<label className="text-[10px] font-bold text-slate-450 uppercase tracking-widest">Suburb</label>
<input
type="text"
placeholder="e.g. Peelamedu"
value={tenantForm.suburb}
onChange={(e) => setTenantForm({ ...tenantForm, suburb: 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"
/>
</div>
<div className="space-y-1">
<label className="text-[10px] font-bold text-slate-450 uppercase tracking-widest">City</label>
<input
type="text"
value={tenantForm.city}
onChange={(e) => setTenantForm({ ...tenantForm, city: 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"
/>
</div>
<div className="space-y-1">
<label className="text-[10px] font-bold text-slate-450 uppercase tracking-widest">State</label>
<input
type="text"
value={tenantForm.state}
onChange={(e) => setTenantForm({ ...tenantForm, state: 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"
/>
</div>
<div className="space-y-1">
<label className="text-[10px] font-bold text-slate-450 uppercase tracking-widest">Postcode</label>
<input
type="text"
placeholder="e.g. 641004"
value={tenantForm.postcode}
onChange={(e) => setTenantForm({ ...tenantForm, postcode: 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"
/>
</div>
</div>
<div className="flex justify-end pt-4">
<button
type="submit"
disabled={createTenantMut.isPending}
className="bg-purple-650 hover:bg-purple-755 text-white px-6 py-3 rounded-xl text-xs font-bold uppercase tracking-wider flex items-center gap-2 cursor-pointer shadow-sm active:scale-95 transition-all border-none"
>
{createTenantMut.isPending ? (
<>
<Loader2 size={14} className="animate-spin" />
Provisioning Tenant...
</>
) : (
<>
<Building2 size={14} />
Provision Tenant
</>
)}
</button>
</div>
</form>
)}
</div>
)}
{/* TAB 2: Store Onboarding */}
{activeTab === 'store' && (
<div className="bg-slate-50/30 border border-slate-200/60 p-7 rounded-2xl shadow-sm space-y-6 animate-in fade-in duration-300">
<div className="flex justify-between items-start border-b border-slate-100 pb-5">
<div>
<h3 className="text-lg font-bold text-slate-900 flex items-center gap-2">
<Store className="text-[#662582]" size={20} />
Add Store Outlet Location
</h3>
<p className="text-slate-550 text-xs mt-1">
Commission a new store branch/hub under a tenant. This sets up store parameters, delivery thresholds, and spawns a placeholder branch manager account.
</p>
</div>
</div>
{storeSuccess ? (
<div className="bg-emerald-50 border border-emerald-100 rounded-2xl p-6 text-center space-y-4 shadow-sm animate-in zoom-in-95 duration-200">
<div className="w-12 h-12 rounded-full bg-emerald-500 text-white flex items-center justify-center mx-auto text-xl font-bold"></div>
<h4 className="font-bold text-emerald-900 text-base">Store Branch Active!</h4>
<p className="text-xs text-emerald-700 max-w-md mx-auto">
Store <strong>{storeForm.locationname}</strong> has been initialized. A default placeholder manager user has been spawned in inactive mode (requires password setup).
</p>
<div className="flex justify-center gap-3">
{onBack && (
<button
type="button"
onClick={onBack}
className="bg-white border border-slate-200 hover:bg-slate-50 text-slate-600 font-bold text-xs uppercase tracking-wider px-5 py-2.5 rounded-lg cursor-pointer flex items-center gap-1.5 shadow-sm active:scale-95 transition-all"
>
<ArrowLeft size={13} />
Back to Outlets
</button>
)}
<button
type="button"
onClick={() => { setStoreSuccess(null); setStoreForm({ tenantid: FIESTA_TENANT_ID, locationname: '', address: '', suburb: '', city: 'Coimbatore', state: 'Tamil Nadu', postcode: '', contactno: '', email: '', opentime: '06:00:00', closetime: '22:00:00', deliverymins: 45, deliveryradius: 5000 }); }}
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 active:scale-95 transition-all shadow-sm"
>
Add Another Branch
</button>
</div>
</div>
) : (
<form onSubmit={handleStoreSubmit} className="space-y-6">
{/* SECTION 1: Identity & Primary Contact */}
<div className="bg-white border border-slate-200/60 p-5 rounded-2xl space-y-4 shadow-sm">
<h4 className="text-xs font-black text-purple-700 uppercase tracking-widest flex items-center gap-1.5 pb-2 border-b border-slate-100/60">
<Building2 size={13} /> Owner & Identity
</h4>
<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-500 uppercase tracking-widest block mb-1">Owner Tenant</label>
<div className="relative rounded-xl">
<div className="absolute inset-y-0 left-0 pl-3.5 flex items-center pointer-events-none text-slate-400">
<Building2 size={14} />
</div>
<input
type="text"
value={tenantsQ.isLoading ? 'Loading tenant info...' : tenantNameDisplay}
readOnly
className="pl-10 pr-4 py-2.5 w-full border border-slate-200 rounded-xl bg-slate-100/75 outline-none font-semibold text-xs text-slate-500 shadow-sm cursor-not-allowed"
/>
</div>
</div>
<div className="space-y-1">
<label className="text-[10px] font-bold text-slate-500 uppercase tracking-widest block mb-1">Store Outlet Name <span className="text-red-500">*</span></label>
<div className="relative rounded-xl">
<div className="absolute inset-y-0 left-0 pl-3.5 flex items-center pointer-events-none text-slate-400">
<Store size={14} />
</div>
<input
type="text"
placeholder="e.g. RS Puram Super Hub"
value={storeForm.locationname}
onChange={(e) => setStoreForm({ ...storeForm, locationname: e.target.value })}
className="pl-10 pr-4 py-2.5 w-full border border-slate-200 rounded-xl bg-slate-50/40 hover:bg-slate-100/60 focus:bg-white outline-none focus:ring-4 focus:ring-purple-100 focus:border-purple-600 transition-all font-semibold text-xs text-slate-800 shadow-sm"
required
/>
</div>
</div>
<div className="space-y-1">
<label className="text-[10px] font-bold text-slate-500 uppercase tracking-widest block mb-1">Outlet Email Address</label>
<div className="relative rounded-xl">
<div className="absolute inset-y-0 left-0 pl-3.5 flex items-center pointer-events-none text-slate-400">
<Mail size={14} />
</div>
<input
type="email"
placeholder="e.g. rspuram@kaveri.com"
value={storeForm.email}
onChange={(e) => setStoreForm({ ...storeForm, email: e.target.value })}
className="pl-10 pr-4 py-2.5 w-full border border-slate-200 rounded-xl bg-slate-50/40 hover:bg-slate-100/60 focus:bg-white outline-none focus:ring-4 focus:ring-purple-100 focus:border-purple-600 transition-all font-semibold text-xs text-slate-800 shadow-sm"
/>
</div>
</div>
<div className="space-y-1">
<label className="text-[10px] font-bold text-slate-500 uppercase tracking-widest block mb-1">Outlet Contact Number</label>
<div className="relative rounded-xl">
<div className="absolute inset-y-0 left-0 pl-3.5 flex items-center pointer-events-none text-slate-400">
<Phone size={14} />
</div>
<input
type="text"
maxLength={10}
placeholder="e.g. 9876543222"
value={storeForm.contactno}
onChange={(e) => setStoreForm({ ...storeForm, contactno: e.target.value.replace(/\D/g, '') })}
className="pl-10 pr-4 py-2.5 w-full border border-slate-200 rounded-xl bg-slate-50/40 hover:bg-slate-100/60 focus:bg-white outline-none focus:ring-4 focus:ring-purple-100 focus:border-purple-600 transition-all font-semibold text-xs text-slate-800 shadow-sm"
/>
</div>
</div>
</div>
</div>
{/* SECTION 2: Location & Address */}
<div className="bg-white border border-slate-200/60 p-5 rounded-2xl space-y-4 shadow-sm">
<h4 className="text-xs font-black text-purple-700 uppercase tracking-widest flex items-center gap-1.5 pb-2 border-b border-slate-100/60">
<MapPin size={13} /> Location & Address Details
</h4>
<div className="space-y-1">
<label className="text-[10px] font-bold text-slate-500 uppercase tracking-widest block mb-1">Store Branch Address</label>
<div className="relative rounded-xl">
<div className="absolute inset-y-0 left-0 pl-3.5 flex items-center pointer-events-none text-slate-400">
<MapPin size={14} />
</div>
<input
type="text"
placeholder="e.g. 240, DB Road"
value={storeForm.address}
onChange={(e) => setStoreForm({ ...storeForm, address: e.target.value })}
className="pl-10 pr-4 py-2.5 w-full border border-slate-200 rounded-xl bg-slate-50/40 hover:bg-slate-100/60 focus:bg-white outline-none focus:ring-4 focus:ring-purple-100 focus:border-purple-600 transition-all font-semibold text-xs text-slate-800 shadow-sm"
/>
</div>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 gap-4">
<div className="space-y-1">
<label className="text-[10px] font-bold text-slate-500 uppercase tracking-widest block mb-1">Suburb</label>
<input
type="text"
placeholder="e.g. RS Puram"
value={storeForm.suburb}
onChange={(e) => setStoreForm({ ...storeForm, suburb: e.target.value })}
className="px-4 py-2.5 w-full border border-slate-200 rounded-xl bg-slate-50/40 hover:bg-slate-100/60 focus:bg-white outline-none focus:ring-4 focus:ring-purple-100 focus:border-purple-600 transition-all font-semibold text-xs text-slate-800 shadow-sm"
/>
</div>
<div className="space-y-1">
<label className="text-[10px] font-bold text-slate-500 uppercase tracking-widest block mb-1">City</label>
<input
type="text"
value={storeForm.city}
onChange={(e) => setStoreForm({ ...storeForm, city: e.target.value })}
className="px-4 py-2.5 w-full border border-slate-200 rounded-xl bg-slate-50/40 hover:bg-slate-100/60 focus:bg-white outline-none focus:ring-4 focus:ring-purple-100 focus:border-purple-600 transition-all font-semibold text-xs text-slate-800 shadow-sm"
/>
</div>
<div className="space-y-1">
<label className="text-[10px] font-bold text-slate-500 uppercase tracking-widest block mb-1">State</label>
<input
type="text"
value={storeForm.state}
onChange={(e) => setStoreForm({ ...storeForm, state: e.target.value })}
className="px-4 py-2.5 w-full border border-slate-200 rounded-xl bg-slate-50/40 hover:bg-slate-100/60 focus:bg-white outline-none focus:ring-4 focus:ring-purple-100 focus:border-purple-600 transition-all font-semibold text-xs text-slate-800 shadow-sm"
/>
</div>
<div className="space-y-1">
<label className="text-[10px] font-bold text-slate-500 uppercase tracking-widest block mb-1">Postcode</label>
<input
type="text"
placeholder="e.g. 641002"
value={storeForm.postcode}
onChange={(e) => setStoreForm({ ...storeForm, postcode: e.target.value })}
className="px-4 py-2.5 w-full border border-slate-200 rounded-xl bg-slate-50/40 hover:bg-slate-100/60 focus:bg-white outline-none focus:ring-4 focus:ring-purple-100 focus:border-purple-600 transition-all font-semibold text-xs text-slate-800 shadow-sm"
/>
</div>
</div>
</div>
{/* SECTION 3: Operations & Logistics */}
<div className="bg-white border border-slate-200/60 p-5 rounded-2xl space-y-4 shadow-sm">
<h4 className="text-xs font-black text-purple-700 uppercase tracking-widest flex items-center gap-1.5 pb-2 border-b border-slate-100/60">
<Navigation size={13} /> Logistics & Operational Hours
</h4>
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 gap-4">
<div className="space-y-1">
<label className="text-[10px] font-bold text-slate-500 uppercase tracking-widest block mb-1 flex items-center gap-1"><Clock size={12} className="text-slate-400" /> Open Time</label>
<div className="relative rounded-xl">
<div className="absolute inset-y-0 left-0 pl-3.5 flex items-center pointer-events-none text-slate-400">
<Clock size={14} />
</div>
<input
type="text"
placeholder="06:00:00"
value={storeForm.opentime}
onChange={(e) => setStoreForm({ ...storeForm, opentime: e.target.value })}
className="pl-10 pr-4 py-2.5 w-full border border-slate-200 rounded-xl bg-slate-50/40 focus:bg-white outline-none focus:ring-4 focus:ring-purple-100 focus:border-purple-600 transition-all font-semibold text-xs text-slate-800 shadow-sm"
/>
</div>
</div>
<div className="space-y-1">
<label className="text-[10px] font-bold text-slate-500 uppercase tracking-widest block mb-1 flex items-center gap-1"><Clock size={12} className="text-slate-400" /> Close Time</label>
<div className="relative rounded-xl">
<div className="absolute inset-y-0 left-0 pl-3.5 flex items-center pointer-events-none text-slate-400">
<Clock size={14} />
</div>
<input
type="text"
placeholder="22:00:00"
value={storeForm.closetime}
onChange={(e) => setStoreForm({ ...storeForm, closetime: e.target.value })}
className="pl-10 pr-4 py-2.5 w-full border border-slate-200 rounded-xl bg-slate-50/40 focus:bg-white outline-none focus:ring-4 focus:ring-purple-100 focus:border-purple-600 transition-all font-semibold text-xs text-slate-800 shadow-sm"
/>
</div>
</div>
<div className="space-y-1">
<label className="text-[10px] font-bold text-slate-500 uppercase tracking-widest block mb-1 flex items-center gap-1"><Navigation size={12} className="text-slate-400" /> Delivery Radius (m)</label>
<div className="relative rounded-xl">
<div className="absolute inset-y-0 left-0 pl-3.5 flex items-center pointer-events-none text-slate-400">
<Navigation size={14} />
</div>
<input
type="number"
value={storeForm.deliveryradius}
onChange={(e) => setStoreForm({ ...storeForm, deliveryradius: Number(e.target.value) })}
className="pl-10 pr-4 py-2.5 w-full border border-slate-200 rounded-xl bg-slate-50/40 focus:bg-white outline-none focus:ring-4 focus:ring-purple-100 focus:border-purple-600 transition-all font-semibold text-xs text-slate-800 shadow-sm"
/>
</div>
</div>
<div className="space-y-1">
<label className="text-[10px] font-bold text-slate-500 uppercase tracking-widest block mb-1 flex items-center gap-1"><Clock size={12} className="text-slate-400" /> Est. Speed (mins)</label>
<div className="relative rounded-xl">
<div className="absolute inset-y-0 left-0 pl-3.5 flex items-center pointer-events-none text-slate-400">
<Clock size={14} />
</div>
<input
type="number"
value={storeForm.deliverymins}
onChange={(e) => setStoreForm({ ...storeForm, deliverymins: Number(e.target.value) })}
className="pl-10 pr-4 py-2.5 w-full border border-slate-200 rounded-xl bg-slate-50/40 focus:bg-white outline-none focus:ring-4 focus:ring-purple-100 focus:border-purple-600 transition-all font-semibold text-xs text-slate-800 shadow-sm"
/>
</div>
</div>
</div>
</div>
{/* FORM ACTIONS */}
<div className="flex justify-end gap-3 pt-2">
{onBack && (
<button
type="button"
onClick={onBack}
className="px-5 py-3 border border-slate-200 hover:bg-slate-50 hover:border-slate-300 bg-white rounded-xl text-xs font-bold uppercase tracking-wider cursor-pointer text-slate-500 hover:text-slate-700 transition-all flex items-center gap-1.5 shadow-sm active:scale-95"
>
<ArrowLeft size={14} />
Back
</button>
)}
<button
type="submit"
disabled={createLocationMut.isPending}
className="bg-gradient-to-r from-[#662582] to-indigo-650 hover:from-purple-755 hover:to-indigo-700 text-white px-6 py-3 rounded-xl text-xs font-bold uppercase tracking-wider flex items-center gap-2 cursor-pointer shadow-md active:scale-95 transition-all border-none"
>
{createLocationMut.isPending ? (
<>
<Loader2 size={14} className="animate-spin" />
Commissioning Hub...
</>
) : (
<>
<Store size={14} />
Commission Branch
</>
)}
</button>
</div>
</form>
)}
</div>
)}
{/* TAB 3: Rider Onboarding */}
{activeTab === 'rider' && (
<div className="bg-white border border-slate-200/60 p-6 rounded-2xl shadow-sm space-y-6">
<div>
<h3 className="text-lg font-bold text-slate-900 flex items-center gap-2">
<Bike className="text-purple-600" />
Onboard Delivery Rider
</h3>
<p className="text-slate-500 text-xs mt-1">
Onboarding riders is a multi-table database transaction. We register the user profile in <code>app_users</code>, and dynamically generate SQL configuration queries to inject vehicle, shift, and availability settings.
</p>
</div>
{riderSuccess ? (
<div className="space-y-5 animate-in slide-in-from-bottom duration-300">
<div className="bg-emerald-50 border border-emerald-100 rounded-2xl p-5 flex items-center gap-4">
<div className="w-10 h-10 rounded-full bg-emerald-500 text-white flex items-center justify-center text-lg font-bold shrink-0"></div>
<div>
<h4 className="font-bold text-emerald-950 text-sm">Base Account Registered successfully!</h4>
<p className="text-xs text-emerald-700 mt-0.5">
Rider user created in <strong>app_users</strong> with ID: <strong>#{riderSuccess.userid}</strong>.
</p>
</div>
</div>
{/* SQL generated panel */}
<div className="space-y-2">
<div className="flex justify-between items-center select-none">
<span className="text-[10px] font-bold text-slate-500 uppercase tracking-widest flex items-center gap-1.5">
<FileText size={12} className="text-slate-400" /> Administrative SQL Synchronization script
</span>
<button
type="button"
onClick={handleCopySql}
className="flex items-center gap-1 text-[11px] font-bold text-purple-650 bg-purple-50 hover:bg-purple-100 border border-purple-200 px-3 py-1.5 rounded-lg cursor-pointer transition-all active:scale-95"
>
{copiedSql ? (
<>
<Check size={12} className="text-emerald-650" />
Copied!
</>
) : (
<>
<Copy size={12} />
Copy Script
</>
)}
</button>
</div>
<pre className="bg-slate-900 text-slate-100 rounded-xl p-4 text-xs font-mono overflow-x-auto shadow-inner leading-relaxed border border-slate-950 max-h-[16rem]">
{riderSuccess.sql}
</pre>
<div className="bg-amber-50 border border-amber-100 rounded-xl p-4 flex gap-3 text-xs leading-relaxed text-amber-800">
<ShieldAlert size={16} className="text-amber-500 shrink-0 mt-0.5" />
<div>
<span className="font-bold block mb-0.5">Immediate Action Required:</span>
Please share this SQL script with the Database Administrator (DBA) or run it against the Postgres instance to complete vehicle assignments, shifts, and active pool configuration.
</div>
</div>
</div>
<div className="flex justify-end pt-2">
<button
type="button"
onClick={() => {
setRiderSuccess(null);
setRiderForm({
firstname: '',
lastname: '',
email: '',
contactno: '',
password: 'Rider@123',
locationid: 0,
shiftid: 0,
vehiclename: '',
vehiclemodel: '',
licensenumber: '',
});
}}
className="bg-purple-650 hover:bg-purple-755 text-white font-bold text-xs uppercase tracking-wider px-5 py-2.5 rounded-lg border-none cursor-pointer"
>
Onboard Another Rider
</button>
</div>
</div>
) : (
<form onSubmit={handleRiderSubmit} className="space-y-4">
{/* Account personal details */}
<div className="bg-slate-50/50 p-4 rounded-xl border border-slate-150 space-y-4">
<h4 className="font-bold text-xs uppercase tracking-wider text-slate-600">1. Base Profile Details</h4>
<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">First Name (*)</label>
<input
type="text"
placeholder="e.g. Karthik"
value={riderForm.firstname}
onChange={(e) => setRiderForm({ ...riderForm, firstname: e.target.value })}
className="w-full border border-slate-250 rounded-xl p-3 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">Last Name</label>
<input
type="text"
placeholder="e.g. Sundar"
value={riderForm.lastname}
onChange={(e) => setRiderForm({ ...riderForm, lastname: e.target.value })}
className="w-full border border-slate-250 rounded-xl p-3 bg-white outline-none focus:border-purple-500 transition-all font-semibold text-xs text-slate-800"
/>
</div>
<div className="space-y-1">
<label className="text-[10px] font-bold text-slate-450 uppercase tracking-widest flex items-center gap-1"><Mail size={11} /> Email (*)</label>
<input
type="email"
placeholder="e.g. karthik@rider.com"
value={riderForm.email}
onChange={(e) => setRiderForm({ ...riderForm, email: e.target.value })}
className="w-full border border-slate-250 rounded-xl p-3 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 flex items-center gap-1"><Phone size={11} /> Contact No (*)</label>
<input
type="text"
maxLength={10}
placeholder="e.g. 9988776655"
value={riderForm.contactno}
onChange={(e) => setRiderForm({ ...riderForm, contactno: e.target.value.replace(/\D/g, '') })}
className="w-full border border-slate-250 rounded-xl p-3 bg-white outline-none focus:border-purple-500 transition-all font-semibold text-xs text-slate-800"
required
/>
</div>
</div>
</div>
{/* Vehicle, Shift & Hub configurations */}
<div className="bg-slate-50/50 p-4 rounded-xl border border-slate-150 space-y-4">
<h4 className="font-bold text-xs uppercase tracking-wider text-slate-600">2. Fleet & Shift configuration</h4>
<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">Assign Store Hub (*)</label>
<select
value={riderForm.locationid}
onChange={(e) => setRiderForm({ ...riderForm, locationid: Number(e.target.value) })}
className="w-full border border-slate-250 rounded-xl p-3 bg-white outline-none focus:border-purple-500 transition-all font-semibold text-xs text-slate-800 cursor-pointer"
required
>
<option value={0}>Select Hub location...</option>
{locations.map((loc) => (
<option key={loc.locationid} value={Number(loc.locationid)}>{fstr(loc.locationname)}</option>
))}
</select>
</div>
<div className="space-y-1">
<label className="text-[10px] font-bold text-slate-450 uppercase tracking-widest">Assigned Shift</label>
<select
value={riderForm.shiftid}
onChange={(e) => setRiderForm({ ...riderForm, shiftid: Number(e.target.value) })}
className="w-full border border-slate-250 rounded-xl p-3 bg-white outline-none focus:border-purple-500 transition-all font-semibold text-xs text-slate-800 cursor-pointer"
>
<option value={0}>Select Shift timings...</option>
{shiftsQ.isLoading ? (
<option>Loading shifts...</option>
) : (shiftsQ.data || []).map((sh: any) => (
<option key={sh.shiftid} value={fnum(sh.shiftid)}>{fstr(sh.shiftname)}</option>
))}
</select>
</div>
<div className="space-y-1">
<label className="text-[10px] font-bold text-slate-450 uppercase tracking-widest">Vehicle Name</label>
<input
type="text"
placeholder="e.g. Coimbatore Hub Bike 4"
value={riderForm.vehiclename}
onChange={(e) => setRiderForm({ ...riderForm, vehiclename: e.target.value })}
className="w-full border border-slate-250 rounded-xl p-3 bg-white outline-none focus:border-purple-500 transition-all font-semibold text-xs text-slate-800"
/>
</div>
<div className="space-y-1">
<label className="text-[10px] font-bold text-slate-450 uppercase tracking-widest">Vehicle Model</label>
<input
type="text"
placeholder="e.g. TVS iQube Electric"
value={riderForm.vehiclemodel}
onChange={(e) => setRiderForm({ ...riderForm, vehiclemodel: e.target.value })}
className="w-full border border-slate-250 rounded-xl p-3 bg-white outline-none focus:border-purple-500 transition-all font-semibold text-xs text-slate-800"
/>
</div>
<div className="space-y-1 md:col-span-2">
<label className="text-[10px] font-bold text-slate-450 uppercase tracking-widest">License Number</label>
<input
type="text"
placeholder="e.g. TN-37-BY-1234"
value={riderForm.licensenumber}
onChange={(e) => setRiderForm({ ...riderForm, licensenumber: e.target.value })}
className="w-full border border-slate-250 rounded-xl p-3 bg-white outline-none focus:border-purple-500 transition-all font-semibold text-xs text-slate-800"
/>
</div>
</div>
</div>
<div className="flex justify-end pt-4">
<button
type="submit"
disabled={createUserMut.isPending}
className="bg-purple-650 hover:bg-purple-755 text-white px-6 py-3 rounded-xl text-xs font-bold uppercase tracking-wider flex items-center gap-2 cursor-pointer shadow-sm active:scale-95 transition-all border-none"
>
{createUserMut.isPending ? (
<>
<Loader2 size={14} className="animate-spin" />
Registering User Profile...
</>
) : (
<>
<Check size={14} />
Onboard Rider Account
</>
)}
</button>
</div>
</form>
)}
</div>
)}
</div>
);
}
return (
<div className="space-y-lg animate-in fade-in duration-300 font-sans text-slate-700">
{/* Tabs Layout */}
<div className="grid grid-cols-1 lg:grid-cols-4 gap-gutter items-start">
{/* Navigation Tabs */}
<div className="lg:col-span-1 space-y-md bg-slate-50/50 border border-slate-200/60 p-4 rounded-2xl shadow-sm">
<nav className="flex lg:flex-col gap-1.5 overflow-x-auto select-none">
<button
onClick={() => { setActiveTab('tenant'); setTenantSuccess(null); }}
className={`flex items-center gap-3 px-4 py-3 rounded-xl text-sm font-bold transition-all duration-200 whitespace-nowrap cursor-pointer border-none w-full ${
activeTab === 'tenant'
? 'bg-purple-50 text-[#662582] shadow-sm border-l-2 border-purple-650'
: 'text-slate-600 hover:text-slate-900 hover:bg-white bg-transparent'
}`}
>
<Building2 size={16} className={activeTab === 'tenant' ? 'text-[#662582]' : 'text-slate-450'} />
<span>Tenant Onboarding</span>
</button>
<button
onClick={() => { setActiveTab('store'); setStoreSuccess(null); }}
className={`flex items-center gap-3 px-4 py-3 rounded-xl text-sm font-bold transition-all duration-200 whitespace-nowrap cursor-pointer border-none w-full ${
activeTab === 'store'
? 'bg-purple-50 text-[#662582] shadow-sm border-l-2 border-purple-650'
: 'text-slate-600 hover:text-slate-900 hover:bg-white bg-transparent'
}`}
>
<Store size={16} className={activeTab === 'store' ? 'text-[#662582]' : 'text-slate-450'} />
<span>Store Branch Onboarding</span>
</button>
<button
onClick={() => { setActiveTab('rider'); setRiderSuccess(null); }}
className={`flex items-center gap-3 px-4 py-3 rounded-xl text-sm font-bold transition-all duration-200 whitespace-nowrap cursor-pointer border-none w-full ${
activeTab === 'rider'
? 'bg-purple-50 text-[#662582] shadow-sm border-l-2 border-purple-650'
: 'text-slate-600 hover:text-slate-900 hover:bg-white bg-transparent'
}`}
>
<Bike size={16} className={activeTab === 'rider' ? 'text-[#662582]' : 'text-slate-450'} />
<span>Rider Onboarding</span>
</button>
</nav>
</div>
{/* Form Panel */}
<div className="lg:col-span-3 space-y-gutter">
{/* TAB 1: Tenant Onboarding */}
{activeTab === 'tenant' && (
<div className="bg-white border border-slate-200/60 p-6 rounded-2xl shadow-sm space-y-6">
<div>
<h3 className="text-lg font-bold text-slate-900">Provision New Merchant Tenant</h3>
<p className="text-slate-500 text-xs mt-1">
Onboard a new merchant group. This registers their enterprise, defaults the order serialization, and spawns the primary Administrator account.
</p>
</div>
{tenantSuccess ? (
<div className="bg-emerald-50 border border-emerald-100 rounded-2xl p-6 text-center space-y-4">
<div className="w-12 h-12 rounded-full bg-emerald-550 text-white flex items-center justify-center mx-auto text-xl font-bold"></div>
<h4 className="font-bold text-emerald-900 text-base">Onboarding Complete!</h4>
<p className="text-xs text-emerald-700 max-w-md mx-auto">
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(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
</button>
</div>
) : (
<form onSubmit={handleTenantSubmit} className="space-y-4">
<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">Merchant Name (*)</label>
<input
type="text"
placeholder="e.g. Kaveri Groceries"
value={tenantForm.tenantname}
onChange={(e) => setTenantForm({ ...tenantForm, tenantname: 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">Company Registered Name (*)</label>
<input
type="text"
placeholder="e.g. Kaveri Retail Pvt. Ltd."
value={tenantForm.companyname}
onChange={(e) => setTenantForm({ ...tenantForm, companyname: 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">Primary Phone Number (*)</label>
<input
type="text"
maxLength={10}
placeholder="e.g. 9876543210"
value={tenantForm.primarycontact}
onChange={(e) => setTenantForm({ ...tenantForm, primarycontact: e.target.value.replace(/\D/g, '') })}
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">Primary Admin Email (*)</label>
<input
type="email"
placeholder="e.g. admin@kaveri.com"
value={tenantForm.primaryemail}
onChange={(e) => setTenantForm({ ...tenantForm, primaryemail: 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>
<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
type="text"
placeholder="e.g. 12, Avinashi Road"
value={tenantForm.address}
onChange={(e) => setTenantForm({ ...tenantForm, address: 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"
/>
</div>
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
<div className="space-y-1">
<label className="text-[10px] font-bold text-slate-450 uppercase tracking-widest">Suburb</label>
<input
type="text"
placeholder="e.g. Peelamedu"
value={tenantForm.suburb}
onChange={(e) => setTenantForm({ ...tenantForm, suburb: 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"
/>
</div>
<div className="space-y-1">
<label className="text-[10px] font-bold text-slate-450 uppercase tracking-widest">City</label>
<input
type="text"
value={tenantForm.city}
onChange={(e) => setTenantForm({ ...tenantForm, city: 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"
/>
</div>
<div className="space-y-1">
<label className="text-[10px] font-bold text-slate-450 uppercase tracking-widest">State</label>
<input
type="text"
value={tenantForm.state}
onChange={(e) => setTenantForm({ ...tenantForm, state: 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"
/>
</div>
<div className="space-y-1">
<label className="text-[10px] font-bold text-slate-450 uppercase tracking-widest">Postcode</label>
<input
type="text"
placeholder="e.g. 641004"
value={tenantForm.postcode}
onChange={(e) => setTenantForm({ ...tenantForm, postcode: 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"
/>
</div>
</div>
<div className="flex justify-end pt-4">
<button
type="submit"
disabled={createTenantMut.isPending}
className="bg-purple-650 hover:bg-purple-755 text-white px-6 py-3 rounded-xl text-xs font-bold uppercase tracking-wider flex items-center gap-2 cursor-pointer shadow-sm active:scale-95 transition-all border-none"
>
{createTenantMut.isPending ? (
<>
<Loader2 size={14} className="animate-spin" />
Provisioning Tenant...
</>
) : (
<>
<Building2 size={14} />
Provision Tenant
</>
)}
</button>
</div>
</form>
)}
</div>
)}
{/* TAB 2: Store Onboarding */}
{activeTab === 'store' && (
<div className="bg-white border border-slate-200/60 p-6 rounded-2xl shadow-sm space-y-6">
<div>
<h3 className="text-lg font-bold text-slate-900">Add Store Outlet Location</h3>
<p className="text-slate-500 text-xs mt-1">
Commission a new store branch/hub under a tenant. This sets up store parameters, delivery thresholds, and spawns an placeholder branch manager account.
</p>
</div>
{storeSuccess ? (
<div className="bg-emerald-50 border border-emerald-100 rounded-2xl p-6 text-center space-y-4">
<div className="w-12 h-12 rounded-full bg-emerald-550 text-white flex items-center justify-center mx-auto text-xl font-bold"></div>
<h4 className="font-bold text-emerald-900 text-base">Store Branch Active!</h4>
<p className="text-xs text-emerald-700 max-w-md mx-auto">
Store <strong>{storeForm.locationname}</strong> has been initialized. A default placeholder manager user has been spawned in inactive mode (requires password setup).
</p>
<div className="flex justify-center gap-3">
{onBack && (
<button
type="button"
onClick={onBack}
className="bg-white border border-slate-200 hover:bg-slate-50 text-slate-600 font-bold text-xs uppercase tracking-wider px-5 py-2.5 rounded-lg cursor-pointer"
>
Back to Outlets
</button>
)}
<button
onClick={() => { setStoreSuccess(null); setStoreForm({ tenantid: FIESTA_TENANT_ID, locationname: '', address: '', suburb: '', city: 'Coimbatore', state: 'Tamil Nadu', postcode: '', contactno: '', email: '', opentime: '06:00:00', closetime: '22:00:00', deliverymins: 45, deliveryradius: 5000 }); }}
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"
>
Add Another Branch
</button>
</div>
</div>
) : (
<form onSubmit={handleStoreSubmit} className="space-y-4">
<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">Select Owner Tenant (*)</label>
<select
value={storeForm.tenantid}
onChange={(e) => setStoreForm({ ...storeForm, tenantid: 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"
>
{tenantsQ.isLoading ? (
<option>Loading tenants...</option>
) : (tenantsQ.data || []).map((t) => (
<option key={t.tenantid} value={Number(t.tenantid)}>{fstr(t.tenantname)} (#{fstr(t.tenantid)})</option>
))}
</select>
</div>
<div className="space-y-1">
<label className="text-[10px] font-bold text-slate-450 uppercase tracking-widest">Store Outlet Name (*)</label>
<input
type="text"
placeholder="e.g. RS Puram Super Hub"
value={storeForm.locationname}
onChange={(e) => setStoreForm({ ...storeForm, 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">Outlet Email Address</label>
<input
type="email"
placeholder="e.g. rspuram@kaveri.com"
value={storeForm.email}
onChange={(e) => setStoreForm({ ...storeForm, email: 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"
/>
</div>
<div className="space-y-1">
<label className="text-[10px] font-bold text-slate-450 uppercase tracking-widest">Outlet Contact Number</label>
<input
type="text"
maxLength={10}
placeholder="e.g. 9876543222"
value={storeForm.contactno}
onChange={(e) => setStoreForm({ ...storeForm, contactno: e.target.value.replace(/\D/g, '') })}
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"
/>
</div>
</div>
<div className="space-y-1">
<label className="text-[10px] font-bold text-slate-450 uppercase tracking-widest">Store Branch Address</label>
<input
type="text"
placeholder="e.g. 240, DB Road"
value={storeForm.address}
onChange={(e) => setStoreForm({ ...storeForm, address: 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"
/>
</div>
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
<div className="space-y-1">
<label className="text-[10px] font-bold text-slate-450 uppercase tracking-widest">Suburb</label>
<input
type="text"
placeholder="e.g. RS Puram"
value={storeForm.suburb}
onChange={(e) => setStoreForm({ ...storeForm, suburb: 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"
/>
</div>
<div className="space-y-1">
<label className="text-[10px] font-bold text-slate-450 uppercase tracking-widest">City</label>
<input
type="text"
value={storeForm.city}
onChange={(e) => setStoreForm({ ...storeForm, city: 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"
/>
</div>
<div className="space-y-1">
<label className="text-[10px] font-bold text-slate-450 uppercase tracking-widest">State</label>
<input
type="text"
value={storeForm.state}
onChange={(e) => setStoreForm({ ...storeForm, state: 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"
/>
</div>
<div className="space-y-1">
<label className="text-[10px] font-bold text-slate-450 uppercase tracking-widest">Postcode</label>
<input
type="text"
placeholder="e.g. 641002"
value={storeForm.postcode}
onChange={(e) => setStoreForm({ ...storeForm, postcode: 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"
/>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-4 gap-4 pt-2 border-t border-slate-100">
<div className="space-y-1">
<label className="text-[10px] font-bold text-slate-450 uppercase tracking-widest flex items-center gap-1"><Clock size={12} /> Open Time</label>
<input
type="text"
placeholder="06:00:00"
value={storeForm.opentime}
onChange={(e) => setStoreForm({ ...storeForm, opentime: e.target.value })}
className="w-full border border-slate-250 rounded-xl p-3 bg-slate-50/40 focus:bg-white outline-none focus:border-purple-500 transition-all font-semibold text-xs text-slate-800"
/>
</div>
<div className="space-y-1">
<label className="text-[10px] font-bold text-slate-450 uppercase tracking-widest flex items-center gap-1"><Clock size={12} /> Close Time</label>
<input
type="text"
placeholder="22:00:00"
value={storeForm.closetime}
onChange={(e) => setStoreForm({ ...storeForm, closetime: e.target.value })}
className="w-full border border-slate-250 rounded-xl p-3 bg-slate-50/40 focus:bg-white outline-none focus:border-purple-500 transition-all font-semibold text-xs text-slate-800"
/>
</div>
<div className="space-y-1">
<label className="text-[10px] font-bold text-slate-450 uppercase tracking-widest flex items-center gap-1"><Navigation size={12} /> Delivery Radius (m)</label>
<input
type="number"
value={storeForm.deliveryradius}
onChange={(e) => setStoreForm({ ...storeForm, deliveryradius: Number(e.target.value) })}
className="w-full border border-slate-250 rounded-xl p-3 bg-slate-50/40 focus:bg-white outline-none focus:border-purple-500 transition-all font-semibold text-xs text-slate-800"
/>
</div>
<div className="space-y-1">
<label className="text-[10px] font-bold text-slate-450 uppercase tracking-widest flex items-center gap-1"><Clock size={12} /> Est. Speed (mins)</label>
<input
type="number"
value={storeForm.deliverymins}
onChange={(e) => setStoreForm({ ...storeForm, deliverymins: Number(e.target.value) })}
className="w-full border border-slate-250 rounded-xl p-3 bg-slate-50/40 focus:bg-white outline-none focus:border-purple-500 transition-all font-semibold text-xs text-slate-800"
/>
</div>
</div>
<div className="flex justify-end gap-3 pt-4">
{onBack && (
<button
type="button"
onClick={onBack}
className="px-5 py-3 border border-slate-200 hover:bg-slate-55 bg-white rounded-xl text-xs font-bold uppercase tracking-wider cursor-pointer text-slate-500 hover:text-slate-700 transition-all border-none"
>
Back
</button>
)}
<button
type="submit"
disabled={createLocationMut.isPending}
className="bg-purple-650 hover:bg-purple-755 text-white px-6 py-3 rounded-xl text-xs font-bold uppercase tracking-wider flex items-center gap-2 cursor-pointer shadow-sm active:scale-95 transition-all border-none"
>
{createLocationMut.isPending ? (
<>
<Loader2 size={14} className="animate-spin" />
Commissioning Hub...
</>
) : (
<>
<Store size={14} />
Commission Branch
</>
)}
</button>
</div>
</form>
)}
</div>
)}
{/* TAB 3: Rider Onboarding */}
{activeTab === 'rider' && (
<div className="bg-white border border-slate-200/60 p-6 rounded-2xl shadow-sm space-y-6">
<div>
<h3 className="text-lg font-bold text-slate-900 flex items-center gap-2">
<Bike className="text-purple-600" />
Onboard Delivery Rider
</h3>
<p className="text-slate-500 text-xs mt-1">
Onboarding riders is a multi-table database transaction. We register the user profile in <code>app_users</code>, and dynamically generate SQL configuration queries to inject vehicle, shift, and availability settings.
</p>
</div>
{riderSuccess ? (
<div className="space-y-5 animate-in slide-in-from-bottom duration-300">
<div className="bg-emerald-50 border border-emerald-100 rounded-2xl p-5 flex items-center gap-4">
<div className="w-10 h-10 rounded-full bg-emerald-500 text-white flex items-center justify-center text-lg font-bold shrink-0"></div>
<div>
<h4 className="font-bold text-emerald-950 text-sm">Base Account Registered successfully!</h4>
<p className="text-xs text-emerald-700 mt-0.5">
Rider user created in <strong>app_users</strong> with ID: <strong>#{riderSuccess.userid}</strong>.
</p>
</div>
</div>
{/* SQL generated panel */}
<div className="space-y-2">
<div className="flex justify-between items-center select-none">
<span className="text-[10px] font-bold text-slate-500 uppercase tracking-widest flex items-center gap-1.5">
<FileText size={12} className="text-slate-400" /> Administrative SQL Synchronization script
</span>
<button
onClick={handleCopySql}
className="flex items-center gap-1 text-[11px] font-bold text-purple-650 bg-purple-50 hover:bg-purple-100 border border-purple-200 px-3 py-1.5 rounded-lg cursor-pointer transition-all active:scale-95"
>
{copiedSql ? (
<>
<Check size={12} className="text-emerald-650" />
Copied!
</>
) : (
<>
<Copy size={12} />
Copy Script
</>
)}
</button>
</div>
<pre className="bg-slate-900 text-slate-100 rounded-xl p-4 text-xs font-mono overflow-x-auto shadow-inner leading-relaxed border border-slate-950 max-h-[16rem]">
{riderSuccess.sql}
</pre>
<div className="bg-amber-50 border border-amber-100 rounded-xl p-4 flex gap-3 text-xs leading-relaxed text-amber-800">
<ShieldAlert size={16} className="text-amber-500 shrink-0 mt-0.5" />
<div>
<span className="font-bold block mb-0.5">Immediate Action Required:</span>
Please share this SQL script with the Database Administrator (DBA) or run it against the Postgres instance to complete vehicle assignments, shifts, and active pool configuration.
</div>
</div>
</div>
<div className="flex justify-end pt-2">
<button
onClick={() => {
setRiderSuccess(null);
setRiderForm({
firstname: '',
lastname: '',
email: '',
contactno: '',
password: 'Rider@123',
locationid: 0,
shiftid: 0,
vehiclename: '',
vehiclemodel: '',
licensenumber: '',
});
}}
className="bg-purple-650 hover:bg-purple-755 text-white font-bold text-xs uppercase tracking-wider px-5 py-2.5 rounded-lg border-none cursor-pointer"
>
Onboard Another Rider
</button>
</div>
</div>
) : (
<form onSubmit={handleRiderSubmit} className="space-y-4">
{/* Account personal details */}
<div className="bg-slate-50/50 p-4 rounded-xl border border-slate-150 space-y-4">
<h4 className="font-bold text-xs uppercase tracking-wider text-slate-600">1. Base Profile Details</h4>
<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">First Name (*)</label>
<input
type="text"
placeholder="e.g. Karthik"
value={riderForm.firstname}
onChange={(e) => setRiderForm({ ...riderForm, firstname: e.target.value })}
className="w-full border border-slate-250 rounded-xl p-3 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">Last Name</label>
<input
type="text"
placeholder="e.g. Sundar"
value={riderForm.lastname}
onChange={(e) => setRiderForm({ ...riderForm, lastname: e.target.value })}
className="w-full border border-slate-250 rounded-xl p-3 bg-white outline-none focus:border-purple-500 transition-all font-semibold text-xs text-slate-800"
/>
</div>
<div className="space-y-1">
<label className="text-[10px] font-bold text-slate-450 uppercase tracking-widest flex items-center gap-1"><Mail size={11} /> Email (*)</label>
<input
type="email"
placeholder="e.g. karthik@rider.com"
value={riderForm.email}
onChange={(e) => setRiderForm({ ...riderForm, email: e.target.value })}
className="w-full border border-slate-250 rounded-xl p-3 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 flex items-center gap-1"><Phone size={11} /> Contact No (*)</label>
<input
type="text"
maxLength={10}
placeholder="e.g. 9988776655"
value={riderForm.contactno}
onChange={(e) => setRiderForm({ ...riderForm, contactno: e.target.value.replace(/\D/g, '') })}
className="w-full border border-slate-250 rounded-xl p-3 bg-white outline-none focus:border-purple-500 transition-all font-semibold text-xs text-slate-800"
required
/>
</div>
</div>
</div>
{/* Vehicle, Shift & Hub configurations */}
<div className="bg-slate-50/50 p-4 rounded-xl border border-slate-150 space-y-4">
<h4 className="font-bold text-xs uppercase tracking-wider text-slate-600">2. Fleet & Shift configuration</h4>
<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">Assign Store Hub (*)</label>
<select
value={riderForm.locationid}
onChange={(e) => setRiderForm({ ...riderForm, locationid: Number(e.target.value) })}
className="w-full border border-slate-250 rounded-xl p-3 bg-white outline-none focus:border-purple-500 transition-all font-semibold text-xs text-slate-800 cursor-pointer"
required
>
<option value={0}>Select Hub location...</option>
{locations.map((loc) => (
<option key={loc.locationid} value={Number(loc.locationid)}>{fstr(loc.locationname)}</option>
))}
</select>
</div>
<div className="space-y-1">
<label className="text-[10px] font-bold text-slate-450 uppercase tracking-widest">Assigned Shift</label>
<select
value={riderForm.shiftid}
onChange={(e) => setRiderForm({ ...riderForm, shiftid: Number(e.target.value) })}
className="w-full border border-slate-250 rounded-xl p-3 bg-white outline-none focus:border-purple-500 transition-all font-semibold text-xs text-slate-800 cursor-pointer"
>
<option value={0}>Select Shift timings...</option>
{shiftsQ.isLoading ? (
<option>Loading shifts...</option>
) : (shiftsQ.data || []).map((sh: any) => (
<option key={sh.shiftid} value={fnum(sh.shiftid)}>{fstr(sh.shiftname)}</option>
))}
</select>
</div>
<div className="space-y-1">
<label className="text-[10px] font-bold text-slate-450 uppercase tracking-widest">Vehicle Name</label>
<input
type="text"
placeholder="e.g. Coimbatore Hub Bike 4"
value={riderForm.vehiclename}
onChange={(e) => setRiderForm({ ...riderForm, vehiclename: e.target.value })}
className="w-full border border-slate-250 rounded-xl p-3 bg-white outline-none focus:border-purple-500 transition-all font-semibold text-xs text-slate-800"
/>
</div>
<div className="space-y-1">
<label className="text-[10px] font-bold text-slate-450 uppercase tracking-widest">Vehicle Model</label>
<input
type="text"
placeholder="e.g. TVS iQube Electric"
value={riderForm.vehiclemodel}
onChange={(e) => setRiderForm({ ...riderForm, vehiclemodel: e.target.value })}
className="w-full border border-slate-250 rounded-xl p-3 bg-white outline-none focus:border-purple-500 transition-all font-semibold text-xs text-slate-800"
/>
</div>
<div className="space-y-1 md:col-span-2">
<label className="text-[10px] font-bold text-slate-450 uppercase tracking-widest">License Number</label>
<input
type="text"
placeholder="e.g. TN-37-BY-1234"
value={riderForm.licensenumber}
onChange={(e) => setRiderForm({ ...riderForm, licensenumber: e.target.value })}
className="w-full border border-slate-250 rounded-xl p-3 bg-white outline-none focus:border-purple-500 transition-all font-semibold text-xs text-slate-800"
/>
</div>
</div>
</div>
<div className="flex justify-end pt-4">
<button
type="submit"
disabled={createUserMut.isPending}
className="bg-purple-650 hover:bg-purple-755 text-white px-6 py-3 rounded-xl text-xs font-bold uppercase tracking-wider flex items-center gap-2 cursor-pointer shadow-sm active:scale-95 transition-all border-none"
>
{createUserMut.isPending ? (
<>
<Loader2 size={14} className="animate-spin" />
Registering User Profile...
</>
) : (
<>
<Check size={14} />
Onboard Rider Account
</>
)}
</button>
</div>
</form>
)}
</div>
)}
</div>
</div>
</div>
);
}