feat(login): per-role person picker for quick demo access
This commit is contained in:
+109
-38
@@ -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 Login = () => {
|
||||||
const [loginType, setLoginType] = useState('customer'); // 'customer', 'employee', 'owner', 'contractor', 'subcontractor'
|
const [loginType, setLoginType] = useState('customer'); // 'customer', 'employee', 'owner', 'contractor', 'subcontractor'
|
||||||
const [identifier, setIdentifier] = useState('');
|
const [identifier, setIdentifier] = useState('');
|
||||||
const [password, setPassword] = useState('');
|
const [password, setPassword] = useState('');
|
||||||
const [showPassword, setShowPassword] = useState(false);
|
const [showPassword, setShowPassword] = useState(false);
|
||||||
const [error, setError] = useState('');
|
const [error, setError] = useState('');
|
||||||
|
const [expandedDemoRole, setExpandedDemoRole] = useState(null);
|
||||||
|
|
||||||
const { login } = useAuth();
|
const { login } = useAuth();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@@ -99,35 +148,32 @@ const Login = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const fillDemo = (role) => {
|
// Called when a person is selected from the demo picker — logs in immediately
|
||||||
if (role === 'customer') {
|
const handleDemoLogin = (account) => {
|
||||||
setLoginType('customer');
|
setError('');
|
||||||
setIdentifier('alice');
|
const result = login(account.id, account.pw, account.loginType);
|
||||||
setPassword('password');
|
if (result.success) {
|
||||||
} else if (role === 'agent') {
|
switch (result.role) {
|
||||||
setLoginType('employee');
|
case 'CUSTOMER': navigate('/'); break;
|
||||||
setIdentifier('FA001');
|
case 'OWNER': navigate('/owner/snapshot'); break;
|
||||||
setPassword('password');
|
case 'CONTRACTOR': navigate('/contractor/dashboard'); break;
|
||||||
} else if (role === 'admin') {
|
case 'VENDOR': navigate('/vendor/dashboard'); break;
|
||||||
setLoginType('employee');
|
case 'SUBCONTRACTOR': navigate('/subcontractor/dashboard'); break;
|
||||||
setIdentifier('ADM01');
|
default: navigate('/emp/fa/dashboard');
|
||||||
setPassword('password');
|
}
|
||||||
} else if (role === 'owner') {
|
} else {
|
||||||
setLoginType('owner');
|
setError(result.message);
|
||||||
setIdentifier('justin');
|
}
|
||||||
setPassword('password');
|
};
|
||||||
} else if (role === 'contractor') {
|
|
||||||
setLoginType('contractor');
|
const toggleDemoRole = (roleKey) => {
|
||||||
setIdentifier('mike');
|
const accounts = DEMO_ACCOUNTS[roleKey];
|
||||||
setPassword('password');
|
if (!accounts) return;
|
||||||
} else if (role === 'vendor') {
|
// Single-person roles → log in directly; multi-person roles → expand picker
|
||||||
setLoginType('vendor');
|
if (accounts.length === 1) {
|
||||||
setIdentifier('abc_supply');
|
handleDemoLogin(accounts[0]);
|
||||||
setPassword('password');
|
} else {
|
||||||
} else if (role === 'subcontractor') {
|
setExpandedDemoRole(prev => (prev === roleKey ? null : roleKey));
|
||||||
setLoginType('subcontractor');
|
|
||||||
setIdentifier('carlos');
|
|
||||||
setPassword('password');
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -266,18 +312,43 @@ const Login = () => {
|
|||||||
</RainbowButton>
|
</RainbowButton>
|
||||||
</form>
|
</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">
|
<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>
|
<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">
|
<div className="flex flex-wrap gap-2 justify-center mb-3">
|
||||||
<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>
|
{Object.keys(DEMO_ACCOUNTS).map((roleKey) => {
|
||||||
<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>
|
const styles = DEMO_ROLE_STYLES[roleKey] || DEMO_ROLE_STYLES.customer;
|
||||||
<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>
|
const isExpanded = expandedDemoRole === roleKey;
|
||||||
<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>
|
const isSingle = DEMO_ACCOUNTS[roleKey].length === 1;
|
||||||
<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>
|
return (
|
||||||
<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
|
||||||
<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>
|
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>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
</SpotlightCard>
|
</SpotlightCard>
|
||||||
|
|||||||
Reference in New Issue
Block a user