feat(login): per-role person picker for quick demo access

This commit is contained in:
Satyam Rastogi
2026-05-29 04:45:03 +05:30
parent 155bf6ce63
commit a3dfd732e8
+109 -38
View File
@@ -52,12 +52,61 @@ const RainbowButton = ({ children, onClick, type = "button", className = "" }) =
);
};
// Demo account roster — each entry maps directly to MOCK_USERS credentials
const DEMO_ACCOUNTS = {
customer: [
{ name: 'Alice Carter', loginType: 'customer', id: 'alice', pw: 'password' },
],
employee: [
{ name: 'Wade Hollis — Admin', loginType: 'employee', id: 'LUP-1010', pw: 'password' },
{ name: 'Darlene Brooks — Admin', loginType: 'employee', id: 'LUP-1011', pw: 'password' },
{ name: 'Roy Schaefer — Admin', loginType: 'employee', id: 'LUP-1012', pw: 'password' },
{ name: 'Cody Tatum — Agent', loginType: 'employee', id: 'LUP-1040', pw: 'password' },
{ name: 'Hannah Reyes — Agent', loginType: 'employee', id: 'LUP-1041', pw: 'password' },
{ name: 'Travis Boone — Agent', loginType: 'employee', id: 'LUP-1042', pw: 'password' },
{ name: 'Shelby Greer — Agent', loginType: 'employee', id: 'LUP-1043', pw: 'password' },
{ name: 'Dalton Pruitt — Agent', loginType: 'employee', id: 'LUP-1044', pw: 'password' },
],
owner: [
{ name: 'Justin Johnson', loginType: 'owner', id: 'justin', pw: 'password' },
{ name: 'Diana Reeves', loginType: 'owner', id: 'diana', pw: 'password' },
],
contractor: [
{ name: 'Mike Brennan', loginType: 'contractor', id: 'mike', pw: 'password' },
],
subcontractor: [
{ name: 'Carlos Mendoza', loginType: 'subcontractor', id: 'carlos', pw: 'password' },
{ name: 'Maya Patel', loginType: 'subcontractor', id: 'maya', pw: 'password' },
{ name: 'Dwayne Boudreaux', loginType: 'subcontractor', id: 'dwayne', pw: 'password' },
{ name: 'Jennifer Castillo', loginType: 'subcontractor', id: 'jennifer', pw: 'password' },
{ name: 'Greg Alston', loginType: 'subcontractor', id: 'greg', pw: 'password' },
],
};
// Color palette per demo role chip
const DEMO_ROLE_STYLES = {
customer: { chip: 'bg-zinc-100 dark:bg-zinc-900/50 border-zinc-200 dark:border-white/5 text-zinc-500 hover:border-zinc-300 dark:hover:border-white/20 hover:text-zinc-800 dark:hover:text-zinc-300', active: 'bg-zinc-200 dark:bg-zinc-800 border-zinc-400 dark:border-white/20 text-zinc-900 dark:text-white' },
employee: { chip: 'bg-zinc-100 dark:bg-zinc-900/50 border-zinc-200 dark:border-white/5 text-zinc-500 hover:border-zinc-300 dark:hover:border-white/20 hover:text-zinc-800 dark:hover:text-zinc-300', active: 'bg-zinc-200 dark:bg-zinc-800 border-zinc-400 dark:border-white/20 text-zinc-900 dark:text-white' },
owner: { chip: 'bg-amber-100 dark:bg-amber-900/20 border-amber-200 dark:border-amber-500/20 text-amber-600 dark:text-amber-400 hover:bg-amber-200 dark:hover:bg-amber-900/30', active: 'bg-amber-200 dark:bg-amber-900/40 border-amber-400 dark:border-amber-400/50 text-amber-800 dark:text-amber-300' },
contractor: { chip: 'bg-blue-100 dark:bg-blue-900/20 border-blue-200 dark:border-blue-500/20 text-blue-600 dark:text-blue-400 hover:bg-blue-200 dark:hover:bg-blue-900/30', active: 'bg-blue-200 dark:bg-blue-900/40 border-blue-400 dark:border-blue-400/50 text-blue-800 dark:text-blue-300' },
subcontractor: { chip: 'bg-purple-100 dark:bg-purple-900/20 border-purple-200 dark:border-purple-500/20 text-purple-600 dark:text-purple-400 hover:bg-purple-200 dark:hover:bg-purple-900/30', active: 'bg-purple-200 dark:bg-purple-900/40 border-purple-400 dark:border-purple-400/50 text-purple-800 dark:text-purple-300' },
};
const DEMO_ROLE_LABELS = {
customer: 'Customer',
employee: 'Employee',
owner: 'Owner',
contractor: 'Contractor',
subcontractor: 'Sub-Con',
};
const Login = () => {
const [loginType, setLoginType] = useState('customer'); // 'customer', 'employee', 'owner', 'contractor', 'subcontractor'
const [identifier, setIdentifier] = useState('');
const [password, setPassword] = useState('');
const [showPassword, setShowPassword] = useState(false);
const [error, setError] = useState('');
const [expandedDemoRole, setExpandedDemoRole] = useState(null);
const { login } = useAuth();
const navigate = useNavigate();
@@ -99,35 +148,32 @@ const Login = () => {
}
};
const fillDemo = (role) => {
if (role === 'customer') {
setLoginType('customer');
setIdentifier('alice');
setPassword('password');
} else if (role === 'agent') {
setLoginType('employee');
setIdentifier('FA001');
setPassword('password');
} else if (role === 'admin') {
setLoginType('employee');
setIdentifier('ADM01');
setPassword('password');
} else if (role === 'owner') {
setLoginType('owner');
setIdentifier('justin');
setPassword('password');
} else if (role === 'contractor') {
setLoginType('contractor');
setIdentifier('mike');
setPassword('password');
} else if (role === 'vendor') {
setLoginType('vendor');
setIdentifier('abc_supply');
setPassword('password');
} else if (role === 'subcontractor') {
setLoginType('subcontractor');
setIdentifier('carlos');
setPassword('password');
// Called when a person is selected from the demo picker — logs in immediately
const handleDemoLogin = (account) => {
setError('');
const result = login(account.id, account.pw, account.loginType);
if (result.success) {
switch (result.role) {
case 'CUSTOMER': navigate('/'); break;
case 'OWNER': navigate('/owner/snapshot'); break;
case 'CONTRACTOR': navigate('/contractor/dashboard'); break;
case 'VENDOR': navigate('/vendor/dashboard'); break;
case 'SUBCONTRACTOR': navigate('/subcontractor/dashboard'); break;
default: navigate('/emp/fa/dashboard');
}
} else {
setError(result.message);
}
};
const toggleDemoRole = (roleKey) => {
const accounts = DEMO_ACCOUNTS[roleKey];
if (!accounts) return;
// Single-person roles → log in directly; multi-person roles → expand picker
if (accounts.length === 1) {
handleDemoLogin(accounts[0]);
} else {
setExpandedDemoRole(prev => (prev === roleKey ? null : roleKey));
}
};
@@ -266,18 +312,43 @@ const Login = () => {
</RainbowButton>
</form>
{/* Demo Quick Links */}
{/* Demo Quick Links — per-role person picker */}
<div className="mt-8 md:mt-10 pt-6 md:pt-8 border-t border-zinc-200 dark:border-white/5">
<p className="text-[10px] text-center text-zinc-500 uppercase tracking-widest font-bold mb-4">Quick Demo Access</p>
<div className="grid grid-cols-3 md:grid-cols-4 gap-2.5">
<button onClick={() => fillDemo('customer')} className="text-[10px] font-bold uppercase tracking-wider bg-zinc-100 dark:bg-zinc-900/50 border border-zinc-200 dark:border-white/5 text-zinc-500 px-2 py-2.5 rounded-lg hover:border-zinc-300 dark:hover:border-white/20 hover:text-zinc-800 dark:hover:text-zinc-300 transition-colors">Customer</button>
<button onClick={() => fillDemo('agent')} className="text-[10px] font-bold uppercase tracking-wider bg-zinc-100 dark:bg-zinc-900/50 border border-zinc-200 dark:border-white/5 text-zinc-500 px-2 py-2.5 rounded-lg hover:border-zinc-300 dark:hover:border-white/20 hover:text-zinc-800 dark:hover:text-zinc-300 transition-colors">Agent</button>
<button onClick={() => fillDemo('admin')} className="text-[10px] font-bold uppercase tracking-wider bg-zinc-100 dark:bg-zinc-900/50 border border-zinc-200 dark:border-white/5 text-zinc-500 px-2 py-2.5 rounded-lg hover:border-zinc-300 dark:hover:border-white/20 hover:text-zinc-800 dark:hover:text-zinc-300 transition-colors">Admin</button>
<button onClick={() => fillDemo('owner')} className="text-[10px] font-bold uppercase tracking-wider bg-amber-100 dark:bg-amber-900/20 border border-amber-200 dark:border-amber-500/20 text-amber-600 dark:text-amber-400 px-2 py-2.5 rounded-lg hover:bg-amber-200 dark:hover:bg-amber-900/30 transition-colors">Owner</button>
<button onClick={() => fillDemo('contractor')} className="text-[10px] font-bold uppercase tracking-wider bg-blue-100 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-500/20 text-blue-600 dark:text-blue-400 px-2 py-2.5 rounded-lg hover:bg-blue-200 dark:hover:bg-blue-900/30 transition-colors">Contractor</button>
<button onClick={() => fillDemo('vendor')} className="text-[10px] font-bold uppercase tracking-wider bg-cyan-100 dark:bg-cyan-900/20 border border-cyan-200 dark:border-cyan-500/20 text-cyan-600 dark:text-cyan-400 px-2 py-2.5 rounded-lg hover:bg-cyan-200 dark:hover:bg-cyan-900/30 transition-colors">Vendor</button>
<button onClick={() => fillDemo('subcontractor')} className="text-[10px] font-bold uppercase tracking-wider bg-purple-100 dark:bg-purple-900/20 border border-purple-200 dark:border-purple-500/20 text-purple-600 dark:text-purple-400 px-2 py-2.5 rounded-lg hover:bg-purple-200 dark:hover:bg-purple-900/30 transition-colors">Sub-Con</button>
<div className="flex flex-wrap gap-2 justify-center mb-3">
{Object.keys(DEMO_ACCOUNTS).map((roleKey) => {
const styles = DEMO_ROLE_STYLES[roleKey] || DEMO_ROLE_STYLES.customer;
const isExpanded = expandedDemoRole === roleKey;
const isSingle = DEMO_ACCOUNTS[roleKey].length === 1;
return (
<button
key={roleKey}
onClick={() => toggleDemoRole(roleKey)}
className={`text-[10px] font-bold uppercase tracking-wider border px-3 py-2 rounded-lg transition-all duration-200 ${isExpanded ? styles.active : styles.chip}`}
>
{DEMO_ROLE_LABELS[roleKey]}
{!isSingle && (
<span className={`ml-1 transition-transform duration-200 inline-block ${isExpanded ? 'rotate-180' : ''}`}></span>
)}
</button>
);
})}
</div>
{/* Person picker — expands below chips when a multi-person role is selected */}
{expandedDemoRole && DEMO_ACCOUNTS[expandedDemoRole] && (
<div className="mt-2 p-2.5 bg-zinc-50 dark:bg-zinc-900/60 border border-zinc-200 dark:border-white/5 rounded-xl flex flex-col gap-1">
{DEMO_ACCOUNTS[expandedDemoRole].map((account) => (
<button
key={account.id}
onClick={() => handleDemoLogin(account)}
className="w-full text-left text-[11px] font-semibold text-zinc-700 dark:text-zinc-300 px-3 py-2 rounded-lg hover:bg-zinc-100 dark:hover:bg-zinc-800 hover:text-zinc-900 dark:hover:text-white transition-colors flex items-center justify-between group"
>
<span>{account.name}</span>
<span className="text-zinc-400 dark:text-zinc-600 text-[10px] font-mono group-hover:text-zinc-500 dark:group-hover:text-zinc-400 transition-colors">{account.id}</span>
</button>
))}
</div>
)}
</div>
</div>
</SpotlightCard>