feat(merchant): premium UI redesign of Reports, Inventory, Stocks, Settings, and Team Access views

This commit is contained in:
Suriya
2026-06-04 13:04:11 +05:30
parent a11a859761
commit 7dbae96b5f
7 changed files with 2679 additions and 1006 deletions

View File

@@ -5,12 +5,28 @@
/**
* Users & Access — tenant staff directory with role filtering and user creation.
* Rendered as a tab inside SettingsView (it used to be a standalone sidebar page).
* Self-contained: owns its search box, role filter, live query, and Add User modal.
* Rendered as a tab inside SettingsView.
* Self-contained: search box, role filter, live query, and Add User modal.
*/
import React, { useState } from 'react';
import { Users, Search, X } from 'lucide-react';
import {
Users,
Search,
X,
Plus,
ShieldAlert,
Shield,
User,
Mail,
Phone,
MapPin,
Lock,
UserCheck,
Check,
SlidersHorizontal,
Coins
} from 'lucide-react';
import { useFiestaUsers, useFiestaCreateUser } from '../services/fiestaQueries';
import { FIESTA_TENANT_ID, str as fstr, roleName } from '../services/fiestaApi';
@@ -26,6 +42,14 @@ const USER_AVATARS = [
'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?auto=format&fit=crop&w=150&q=80',
];
const ROLE_THEMES: Record<number, { bg: string; text: string; border: string; label: string }> = {
1: { bg: 'bg-rose-50/75', text: 'text-rose-700', border: 'border-rose-100', label: 'Owner' },
2: { bg: 'bg-amber-50/75', text: 'text-amber-700', border: 'border-amber-100', label: 'Manager' },
3: { bg: 'bg-blue-50/75', text: 'text-blue-700', border: 'border-blue-100', label: 'Admin' },
4: { bg: 'bg-emerald-50/75', text: 'text-emerald-700', border: 'border-emerald-100', label: 'Staff' },
6: { bg: 'bg-indigo-50/75', text: 'text-indigo-700', border: 'border-indigo-100', label: 'Cashier' },
};
export default function UsersPanel({ tenantId = FIESTA_TENANT_ID, defaultNewUserRole = 4 }: UsersPanelProps) {
const usersQ = useFiestaUsers({ tenantid: tenantId, pagesize: 100 });
const createUserMut = useFiestaCreateUser();
@@ -73,6 +97,7 @@ export default function UsersPanel({ tenantId = FIESTA_TENANT_ID, defaultNewUser
const matchesRole = userRoleFilter === 'ALL' || u.roleid === userRoleFilter;
return matchesSearch && matchesRole;
});
const roleOptions = Array.from(new Set(users.map((u) => u.roleid)));
const handleCreateUser = async (e: React.FormEvent) => {
@@ -93,32 +118,34 @@ export default function UsersPanel({ tenantId = FIESTA_TENANT_ID, defaultNewUser
});
setShowAddUserModal(false);
setNewUser({ firstname: '', lastname: '', email: '', contactno: '', password: '', roleid: defaultNewUserRole });
alert(`User "${newUser.firstname}" created successfully and synced to the live Users directory.`);
alert(`Team member "${newUser.firstname}" added successfully.`);
} catch (err) {
alert(`Could not create user: ${err instanceof Error ? err.message : 'Unknown error'}`);
alert(`Could not add team member: ${err instanceof Error ? err.message : 'Unknown error'}`);
}
};
return (
<div className="space-y-md">
<div className="flex flex-col md:flex-row md:items-center justify-between gap-md">
<div className="space-y-lg animate-in fade-in duration-300 text-sm">
{/* Header section */}
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-md pb-4 border-b border-slate-100">
<div>
<span className="text-[10px] font-bold text-zinc-400 uppercase tracking-widest block">Users & Access</span>
<p className="text-zinc-500 text-[11px] mt-0.5">
Tenant staff accounts, roles, shifts, and status live from the Users API.
<span className="text-xs font-bold text-slate-400 uppercase tracking-widest block font-sans">Users & Access</span>
<h2 className="text-xl font-bold text-slate-900 mt-1">Manage Store Team</h2>
<p className="text-slate-500 text-xs mt-1.5 leading-relaxed">
Manage your store team, access roles, and contact details.
</p>
<div className="mt-1.5">
<div className="mt-3">
{usersQ.isLoading ? (
<span className="inline-flex items-center gap-1 text-[10px] font-semibold text-zinc-400 uppercase tracking-wide">
<span className="w-1.5 h-1.5 rounded-full bg-zinc-300 animate-pulse" /> Loading live users
<span className="inline-flex items-center gap-1.5 text-xs font-bold text-zinc-400 uppercase tracking-wide">
<span className="w-2 h-2 rounded-full bg-zinc-300 animate-pulse" /> Loading team list
</span>
) : usersQ.isError ? (
<span className="inline-flex items-center gap-1 text-[10px] font-semibold text-rose-600 uppercase tracking-wide">
<span className="w-1.5 h-1.5 rounded-full bg-rose-500" /> Live data unavailable
<span className="inline-flex items-center gap-1.5 text-xs font-bold text-rose-600 uppercase tracking-wide">
<span className="w-2 h-2 rounded-full bg-rose-500" /> Connection issue
</span>
) : (
<span className="inline-flex items-center gap-1 text-[10px] font-semibold text-emerald-600 uppercase tracking-wide">
<span className="w-1.5 h-1.5 rounded-full bg-emerald-500" /> Live · {users.length} users
<span className="inline-flex items-center gap-1.5 text-xs font-bold text-emerald-650 uppercase tracking-wide">
<span className="w-2 h-2 rounded-full bg-emerald-500 animate-pulse" /> Active · {users.length} Team Members
</span>
)}
</div>
@@ -126,247 +153,307 @@ export default function UsersPanel({ tenantId = FIESTA_TENANT_ID, defaultNewUser
<button
onClick={() => setShowAddUserModal(true)}
className="bg-[#581c87] text-white px-xl py-2.5 rounded-lg text-xs font-bold uppercase tracking-wider flex items-center justify-center gap-xs cursor-pointer hover:bg-purple-800 transition shrink-0"
className="bg-purple-650 hover:bg-purple-755 text-white px-5 py-3 rounded-xl text-sm font-bold uppercase tracking-wider flex items-center justify-center gap-2 cursor-pointer shadow-sm active:scale-95 transition-all border-none"
>
Add User
<Plus size={16} /> Add Team Member
</button>
</div>
{/* Search */}
<div className="relative w-full md:max-w-md">
<span className="absolute inset-y-0 left-0 pl-3.5 flex items-center pointer-events-none text-zinc-400">
<Search className="w-4 h-4" />
</span>
<input
type="text"
placeholder="Search users by name, email, or contact…"
value={search}
onChange={(e) => setSearch(e.target.value)}
className="w-full pl-10 pr-4 py-2 bg-zinc-50 border border-zinc-200 rounded-lg text-xs font-medium text-zinc-800 placeholder-zinc-400 focus:outline-none focus:ring-2 focus:ring-[#581c87]/20 focus:border-[#581c87] transition-all"
/>
{search && (
<button
onClick={() => setSearch('')}
className="absolute inset-y-0 right-0 pr-3 flex items-center text-zinc-400 hover:text-zinc-600"
>
<X className="w-3.5 h-3.5" />
</button>
)}
</div>
{/* Search & Filter Utility Bar */}
<div className="bg-slate-50/50 border border-slate-200/60 p-4 rounded-2xl flex flex-col md:flex-row gap-4 items-stretch md:items-center justify-between select-none">
<div className="relative w-full md:max-w-sm shrink-0">
<span className="absolute inset-y-0 left-0 pl-3.5 flex items-center pointer-events-none text-slate-450">
<Search className="w-4.5 h-4.5" />
</span>
<input
type="text"
placeholder="Search team…"
value={search}
onChange={(e) => setSearch(e.target.value)}
className="w-full pl-10 pr-9 py-2.5 bg-white border border-slate-200/80 rounded-xl text-sm font-medium text-slate-800 placeholder-slate-405 focus:outline-none focus:ring-4 focus:ring-purple-500/10 focus:border-purple-500 transition-all shadow-sm"
/>
{search && (
<button
onClick={() => setSearch('')}
className="absolute inset-y-0 right-0 pr-3 flex items-center text-slate-450 hover:text-slate-700"
>
<X className="w-4 h-4" />
</button>
)}
</div>
{/* Role filter pills */}
<div className="flex flex-wrap gap-2">
<button
onClick={() => setUserRoleFilter('ALL')}
className={`px-3 py-1.5 rounded-lg text-xs font-semibold border transition-all cursor-pointer ${
userRoleFilter === 'ALL'
? 'bg-[#581c87] text-white border-[#581c87] shadow-sm'
: 'bg-white text-zinc-700 border-[#e2e8f0] hover:bg-zinc-50'
}`}
>
All Roles
</button>
{roleOptions.map((rid) => (
{/* Role filter capsules */}
<div className="flex items-center gap-2 overflow-x-auto pb-1 md:pb-0 scrollbar-none">
<button
key={rid}
onClick={() => setUserRoleFilter(rid)}
className={`px-3 py-1.5 rounded-lg text-xs font-semibold border transition-all cursor-pointer ${
userRoleFilter === rid
? 'bg-[#581c87] text-white border-[#581c87] shadow-sm'
: 'bg-white text-zinc-700 border-[#e2e8f0] hover:bg-zinc-50'
onClick={() => setUserRoleFilter('ALL')}
className={`px-3.5 py-2 rounded-xl text-xs uppercase tracking-wider font-extrabold transition-all duration-200 cursor-pointer border ${
userRoleFilter === 'ALL'
? 'bg-purple-600 text-white border-purple-600 shadow-sm'
: 'bg-white text-slate-600 border-slate-200/80 hover:text-slate-800 hover:bg-slate-100'
}`}
>
{roleName(rid)}
All Roles
</button>
))}
{roleOptions.map((rid) => (
<button
key={rid}
onClick={() => setUserRoleFilter(rid)}
className={`px-3.5 py-2 rounded-xl text-xs uppercase tracking-wider font-extrabold transition-all duration-200 cursor-pointer border whitespace-nowrap ${
userRoleFilter === rid
? 'bg-purple-600 text-white border-purple-600 shadow-sm'
: 'bg-white text-slate-600 border-slate-200/80 hover:text-slate-800 hover:bg-slate-100'
}`}
>
{roleName(rid)}
</button>
))}
</div>
</div>
{/* Users table */}
<div className="bg-white border border-[#e2e8f0] rounded-xl overflow-hidden shadow-sm">
<div className="p-md border-b border-[#e2e8f0] bg-[#f8fafc] flex justify-between items-center">
<h4 className="font-sans font-bold text-sm text-[#0f172a]">Directory ({filteredUsers.length})</h4>
<span className="text-[10px] text-zinc-400 font-medium uppercase tracking-wider">Tenant {tenantId}</span>
</div>
{/* Directory Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-md">
{filteredUsers.length === 0 ? (
<div className="col-span-full bg-white border border-slate-200/60 rounded-2xl p-12 text-center text-slate-500 font-bold text-base">
{usersQ.isLoading ? 'Loading team list…' : 'No matches found in team list.'}
</div>
) : (
filteredUsers.map((u) => {
const roleInfo = ROLE_THEMES[u.roleid] || { bg: 'bg-slate-55', text: 'text-slate-700', border: 'border-slate-100', label: u.role };
return (
<div key={u.userid} className="bg-white border border-slate-200/60 hover:border-purple-300 hover:shadow-md rounded-2xl p-5 transition-all duration-300 flex flex-col justify-between group relative overflow-hidden">
{/* Background aura gradient effect on hover */}
<div className="absolute top-0 right-0 w-28 h-28 bg-purple-500/5 rounded-full blur-xl -mr-8 -mt-8 pointer-events-none transition-all group-hover:bg-purple-500/10" />
<div className="overflow-x-auto text-xs font-sans">
<table className="w-full text-left">
<thead className="bg-[#f8fafc] border-b border-[#e2e8f0] text-zinc-500 text-[10px] uppercase font-bold tracking-wider">
<tr>
<th className="px-md py-sm">User</th>
<th className="px-md py-sm">Role</th>
<th className="px-md py-sm">Contact</th>
<th className="px-md py-sm">Shift</th>
<th className="px-md py-sm">Location</th>
<th className="px-md py-sm text-right">Status</th>
</tr>
</thead>
<tbody className="divide-y divide-[#f1f5f9]">
{filteredUsers.length === 0 ? (
<tr>
<td colSpan={6} className="text-center py-10 text-zinc-400">
{usersQ.isLoading ? 'Loading live users…' : 'No users match this filter.'}
</td>
</tr>
) : (
filteredUsers.map((u) => (
<tr key={u.userid} className="hover:bg-[#f2f4f6]/50 transition-colors">
<td className="px-md py-md">
<div className="flex items-center gap-sm">
<img
src={u.avatar}
alt={u.name}
referrerPolicy="no-referrer"
className="w-9 h-9 object-cover rounded-full border border-zinc-200 shrink-0"
/>
<div className="min-w-0">
<p className="font-bold text-[#0f172a] truncate">{u.name}</p>
<p className="text-[10px] text-zinc-400 font-medium truncate">{u.email}</p>
</div>
<div>
<div className="flex items-start gap-4 relative z-10">
{/* User Avatar with status indicator ring */}
<div className="relative shrink-0 select-none">
<img
src={u.avatar}
alt={u.name}
referrerPolicy="no-referrer"
className="w-14 h-14 object-cover rounded-2xl border border-slate-200"
/>
<span className={`absolute -bottom-1 -right-1 w-4 h-4 rounded-full border-3 border-white ${
u.status.toLowerCase() === 'active' ? 'bg-emerald-500' : 'bg-slate-350'
}`} />
</div>
<div className="min-w-0 flex-1">
<h4 className="font-bold text-slate-800 text-sm truncate group-hover:text-purple-950 transition-colors leading-tight">{u.name}</h4>
<p className="text-xs text-slate-450 mt-1.5 truncate font-medium">{u.email}</p>
</div>
</div>
{/* Metadata fields */}
<div className="mt-5 space-y-2.5 border-t border-slate-100/75 pt-4 text-xs">
<div className="flex items-center gap-2.5 text-slate-600 font-medium">
<Phone size={14} className="text-slate-400 shrink-0" />
<span className="font-mono">{u.contact}</span>
</div>
<div className="flex items-center gap-2.5 text-slate-600 font-medium">
<MapPin size={14} className="text-slate-400 shrink-0" />
<span className="truncate">{u.location}</span>
</div>
{u.shift && u.shift !== '—' && (
<div className="flex items-center gap-2.5 text-slate-600 font-medium">
<span className="text-[10px] uppercase tracking-wider text-slate-400 font-extrabold">Shift</span>
<span className="font-bold text-slate-700 bg-slate-50 px-2.5 py-0.5 rounded-lg border border-slate-150 text-xs">{u.shift}</span>
</div>
</td>
<td className="px-md py-md">
<span className="px-2 py-0.5 rounded text-[9px] font-bold uppercase bg-purple-50 text-[#581c87] border border-purple-100">
{u.role}
</span>
</td>
<td className="px-md py-md font-mono text-zinc-600 font-medium">{u.contact}</td>
<td className="px-md py-md text-zinc-500 font-medium">{u.shift}</td>
<td className="px-md py-md text-zinc-500 font-medium">{u.location}</td>
<td className="px-md py-md text-right">
<span className={`px-1.5 py-0.5 rounded text-[9px] font-bold uppercase ${
u.status.toLowerCase() === 'active'
? 'text-emerald-700 bg-emerald-100'
: 'text-zinc-500 bg-zinc-200'
}`}>
{u.status}
</span>
</td>
</tr>
))
)}
</tbody>
</table>
</div>
)}
</div>
</div>
<div className="mt-5 pt-4 border-t border-slate-100/75 flex justify-between items-center select-none">
<span className={`inline-flex items-center gap-1.5 px-3 py-1 rounded-xl text-xs font-extrabold uppercase border ${roleInfo.bg} ${roleInfo.text} ${roleInfo.border}`}>
{u.roleid === 1 && <ShieldAlert size={12} />}
{u.roleid === 2 && <Shield size={12} />}
{u.roleid === 3 && <SlidersHorizontal size={12} />}
{u.roleid === 4 && <User size={12} />}
{u.roleid === 6 && <Coins size={12} />}
{roleInfo.label}
</span>
<span className="text-xs font-mono text-slate-400 font-bold">ID: #{u.userid}</span>
</div>
</div>
);
})
)}
</div>
{/* CREATE NEW USER MODAL */}
{showAddUserModal && (
<div
className="fixed inset-0 bg-[#0f172a]/40 backdrop-blur-sm z-[200] flex items-center justify-center p-md"
className="fixed inset-0 bg-slate-900/40 backdrop-blur-sm z-[200] flex items-center justify-center p-md select-none"
onClick={(e) => { if (e.target === e.currentTarget) setShowAddUserModal(false); }}
>
<div className="bg-white border border-[#e2e8f0] rounded-xl w-full max-w-[26rem] max-h-[90vh] flex flex-col shadow-2xl overflow-hidden animate-in zoom-in-95 duration-200 text-xs font-sans cursor-default">
<div className="p-md border-b border-[#e2e8f0] bg-[#f8fafc] flex justify-between items-center shrink-0">
<h4 className="font-bold text-[#0f172a] flex items-center gap-xs">
<Users size={15} className="text-[#581c87]" />
Create User Account
<div className="bg-white border border-slate-200/80 rounded-2xl w-full max-w-[30rem] max-h-[90vh] flex flex-col shadow-2xl overflow-hidden animate-in zoom-in-95 duration-200 text-sm font-sans cursor-default">
{/* Modal Header */}
<div className="p-5 border-b border-slate-100 bg-slate-50/50 flex justify-between items-center shrink-0">
<h4 className="font-bold text-slate-900 flex items-center gap-2.5 text-base">
<div className="w-8 h-8 rounded-lg bg-purple-50 text-purple-650 flex items-center justify-center">
<Users size={16} />
</div>
Add Team Member
</h4>
<button
onClick={() => setShowAddUserModal(false)}
className="p-1 hover:bg-zinc-200 rounded-full text-zinc-400 cursor-pointer transition-colors"
className="p-1.5 hover:bg-slate-100 rounded-lg text-slate-400 hover:text-slate-600 cursor-pointer transition-colors"
>
<X size={16} />
<X size={18} />
</button>
</div>
{/* Modal Form */}
<form onSubmit={handleCreateUser} className="flex-1 flex flex-col min-h-0 overflow-hidden">
<div className="p-md space-y-md overflow-y-auto flex-1">
<p className="text-zinc-500 leading-relaxed">
Creates a real user against the live Users API for tenant {tenantId}.
<div className="p-6 space-y-md overflow-y-auto flex-1 scrollbar-thin">
<p className="text-slate-505 leading-relaxed text-xs">
Add a new member to your store team. This will create their account and sync it to the list.
</p>
<div className="space-y-sm">
<div className="space-y-4">
{/* Name Fields */}
<div className="grid grid-cols-2 gap-sm">
<div className="space-y-1">
<label className="font-bold text-zinc-500 uppercase tracking-widest text-[9px]">FIRST NAME (*)</label>
<div className="space-y-1.5">
<label className="font-bold text-slate-500 uppercase tracking-widest text-[10px]">FIRST NAME (*)</label>
<input
type="text"
placeholder="e.g. Harini"
value={newUser.firstname}
onChange={(e) => setNewUser({ ...newUser, firstname: e.target.value })}
className="w-full border border-[#e2e8f0] rounded-lg p-sm bg-[#f8fafc] focus:bg-white outline-none focus:ring-1 focus:ring-[#581c87]"
className="w-full border border-slate-200 rounded-xl p-3 bg-slate-50/40 hover:bg-slate-100 focus:bg-white outline-none focus:border-purple-500 focus:ring-4 focus:ring-purple-500/10 transition-all text-slate-800 font-semibold text-sm shadow-sm"
required
/>
</div>
<div className="space-y-1">
<label className="font-bold text-zinc-500 uppercase tracking-widest text-[9px]">LAST NAME</label>
<div className="space-y-1.5">
<label className="font-bold text-slate-500 uppercase tracking-widest text-[10px]">LAST NAME</label>
<input
type="text"
placeholder="e.g. Rajan"
value={newUser.lastname}
onChange={(e) => setNewUser({ ...newUser, lastname: e.target.value })}
className="w-full border border-[#e2e8f0] rounded-lg p-sm bg-[#f8fafc] focus:bg-white outline-none focus:ring-1 focus:ring-[#581c87]"
className="w-full border border-slate-200 rounded-xl p-3 bg-slate-50/40 hover:bg-slate-100 focus:bg-white outline-none focus:border-purple-500 focus:ring-4 focus:ring-purple-500/10 transition-all text-slate-800 font-semibold text-sm shadow-sm"
/>
</div>
</div>
<div className="space-y-1">
<label className="font-bold text-zinc-500 uppercase tracking-widest text-[9px]">EMAIL (*)</label>
<input
type="email"
placeholder="e.g. harini@store.com"
value={newUser.email}
onChange={(e) => setNewUser({ ...newUser, email: e.target.value })}
className="w-full border border-[#e2e8f0] rounded-lg p-sm bg-[#f8fafc] focus:bg-white outline-none focus:ring-1 focus:ring-[#581c87]"
required
/>
</div>
<div className="grid grid-cols-2 gap-sm">
<div className="space-y-1">
<label className="font-bold text-zinc-500 uppercase tracking-widest text-[9px]">CONTACT NO (*)</label>
{/* Email & Contact */}
<div className="space-y-1.5">
<label className="font-bold text-slate-500 uppercase tracking-widest text-[10px]">EMAIL ADDRESS (*)</label>
<div className="relative">
<span className="absolute inset-y-0 left-0 pl-3.5 flex items-center pointer-events-none text-slate-400">
<Mail size={14} />
</span>
<input
type="text"
placeholder="9988776655"
value={newUser.contactno}
onChange={(e) => setNewUser({ ...newUser, contactno: e.target.value })}
className="w-full border border-[#e2e8f0] rounded-lg p-sm bg-[#f8fafc] focus:bg-white outline-none focus:ring-1 focus:ring-[#581c87]"
type="email"
placeholder="e.g. harini@store.com"
value={newUser.email}
onChange={(e) => setNewUser({ ...newUser, email: e.target.value })}
className="w-full border border-slate-200 rounded-xl pl-10 pr-4 py-3 bg-slate-50/40 hover:bg-slate-100 focus:bg-white outline-none focus:border-purple-500 focus:ring-4 focus:ring-purple-500/10 transition-all text-slate-800 font-semibold text-sm shadow-sm"
required
/>
</div>
<div className="space-y-1">
<label className="font-bold text-zinc-500 uppercase tracking-widest text-[9px]">ROLE</label>
<select
value={newUser.roleid}
onChange={(e) => setNewUser({ ...newUser, roleid: Number(e.target.value) })}
className="w-full border border-[#e2e8f0] rounded-lg p-2 bg-[#f8fafc] focus:bg-white outline-none"
>
<option value={1}>Owner</option>
<option value={2}>Manager</option>
<option value={3}>Admin</option>
<option value={4}>Staff</option>
<option value={6}>Cashier</option>
</select>
</div>
<div className="space-y-1.5">
<label className="font-bold text-slate-500 uppercase tracking-widest text-[10px]">CONTACT NUMBER (*)</label>
<div className="relative">
<span className="absolute inset-y-0 left-0 pl-3.5 flex items-center pointer-events-none text-slate-400">
<Phone size={14} />
</span>
<input
type="text"
placeholder="e.g. 9988776655"
value={newUser.contactno}
onChange={(e) => setNewUser({ ...newUser, contactno: e.target.value })}
className="w-full border border-slate-200 rounded-xl pl-10 pr-4 py-3 bg-slate-50/40 hover:bg-slate-100 focus:bg-white outline-none focus:border-purple-500 focus:ring-4 focus:ring-purple-500/10 transition-all text-slate-800 font-semibold text-sm shadow-sm"
required
/>
</div>
</div>
<div className="space-y-1">
<label className="font-bold text-zinc-500 uppercase tracking-widest text-[9px]">TEMPORARY PASSWORD (*)</label>
<input
type="text"
placeholder="Set an initial password"
value={newUser.password}
onChange={(e) => setNewUser({ ...newUser, password: e.target.value })}
className="w-full border border-[#e2e8f0] rounded-lg p-sm bg-[#f8fafc] focus:bg-white outline-none focus:ring-1 focus:ring-[#581c87] font-mono"
required
/>
{/* Interactive Role Buttons instead of standard select */}
<div className="space-y-2">
<label className="font-bold text-slate-500 uppercase tracking-widest text-[10px]">SELECT ACCOUNT ROLE (*)</label>
<div className="grid grid-cols-2 gap-2.5">
{[
{ id: 1, label: 'Owner', desc: 'Full business access', icon: ShieldAlert },
{ id: 2, label: 'Manager', desc: 'Operations control', icon: Shield },
{ id: 3, label: 'Admin', desc: 'Manage store settings', icon: SlidersHorizontal },
{ id: 4, label: 'Staff', desc: 'Standard staff duties', icon: User },
{ id: 6, label: 'Cashier', desc: 'Checkout & registers', icon: Coins },
].map((r) => {
const isSelected = newUser.roleid === r.id;
const Icon = r.icon;
return (
<button
key={r.id}
type="button"
onClick={() => setNewUser({ ...newUser, roleid: r.id })}
className={`p-3 rounded-xl border text-left transition-all cursor-pointer flex gap-2.5 items-start ${
isSelected
? 'bg-purple-50 border-purple-500 ring-2 ring-purple-500/10'
: 'bg-slate-50/40 hover:bg-slate-50 border-slate-200/80'
}`}
>
<Icon size={14} className={`shrink-0 mt-0.5 ${isSelected ? 'text-purple-650' : 'text-slate-450'}`} />
<div className="min-w-0">
<span className={`font-bold text-xs block leading-tight ${isSelected ? 'text-purple-950' : 'text-slate-800'}`}>{r.label}</span>
<span className="text-[10px] text-slate-455 leading-tight block mt-1 font-medium">{r.desc}</span>
</div>
</button>
);
})}
</div>
</div>
{/* Temporary Password */}
<div className="space-y-1.5">
<label className="font-bold text-slate-500 uppercase tracking-widest text-[10px]">TEMPORARY PASSWORD (*)</label>
<div className="relative">
<span className="absolute inset-y-0 left-0 pl-3.5 flex items-center pointer-events-none text-slate-400">
<Lock size={14} />
</span>
<input
type="text"
placeholder="Set password credentials"
value={newUser.password}
onChange={(e) => setNewUser({ ...newUser, password: e.target.value })}
className="w-full border border-slate-200 rounded-xl pl-10 pr-4 py-3 bg-slate-50/40 hover:bg-slate-100 focus:bg-white outline-none focus:border-purple-500 focus:ring-4 focus:ring-purple-500/10 transition-all text-slate-800 font-semibold font-mono text-sm shadow-sm"
required
/>
</div>
</div>
</div>
</div>
<div className="p-md border-t border-[#f1f5f9] flex justify-end gap-sm bg-[#f8fafc] shrink-0">
{/* Modal Footer */}
<div className="p-5 border-t border-slate-100 flex justify-end gap-sm bg-slate-50/50 shrink-0">
<button
type="button"
onClick={() => setShowAddUserModal(false)}
className="px-4 py-2 border border-[#e2e8f0] rounded-lg font-semibold text-zinc-500 hover:bg-zinc-50 cursor-pointer"
className="px-5 py-2.5 border border-slate-200 hover:bg-slate-100 rounded-xl font-bold text-slate-500 hover:text-slate-700 cursor-pointer active:scale-95 transition-all text-sm"
>
Cancel
</button>
<button
type="submit"
disabled={createUserMut.isPending}
className="px-4 py-2 bg-[#581c87] text-white rounded-lg font-bold hover:bg-purple-800 cursor-pointer shadow-sm disabled:opacity-60 disabled:cursor-not-allowed"
className="px-6 py-2.5 bg-purple-650 hover:bg-purple-755 text-white rounded-xl font-bold cursor-pointer shadow-sm disabled:opacity-60 disabled:cursor-not-allowed active:scale-95 transition-all flex items-center gap-1.5 border-none text-sm"
>
{createUserMut.isPending ? 'Creating…' : 'Create User'}
{createUserMut.isPending ? (
<>
<span className="w-2 h-2 rounded-full bg-white animate-pulse" />
Creating
</>
) : (
<>
<Check size={14} />
Add Member
</>
)}
</button>
</div>
</form>