- Created automated script (scripts/align-8px-grid.py) to align all spacing to 8px grid - Replaced non-8px-aligned spacing: gap-3/p-3/m-3 (12px) → gap-4/p-4/m-4 (16px), gap-5/p-5/m-5 (20px) → gap-6/p-6/m-6 (24px), gap-10/p-10/m-10 (40px) → gap-12/p-12/m-12 (48px), gap-20/p-20/m-20 (80px) → gap-24/p-24/m-24 (96px) - Preserved: 4px values (gap-1, p-1, m-1) as they may be intentional fine-tuning, responsive breakpoints (sm:, md:, lg:), test files, documentation - Modified files across all components to ensure consistent 8px grid alignment - Action 11.2.1.3: Align all elements to 8px grid - COMPLETE
135 lines
4.5 KiB
TypeScript
135 lines
4.5 KiB
TypeScript
import React, { useState } from 'react';
|
|
import { User } from '@/types';
|
|
import { Badge } from '../ui/badge';
|
|
import { Button } from '../ui/button';
|
|
import { MoreVertical, Shield, Ban, Mail, Trash2 } from 'lucide-react';
|
|
|
|
interface UserTableRowProps {
|
|
user: User;
|
|
onBan: (user: User) => void;
|
|
onDelete: (user: User) => void;
|
|
onEditRole: (user: User) => void;
|
|
}
|
|
|
|
export const UserTableRow: React.FC<UserTableRowProps> = ({
|
|
user,
|
|
onBan,
|
|
onDelete,
|
|
onEditRole,
|
|
}) => {
|
|
const [showMenu, setShowMenu] = useState(false);
|
|
|
|
const statusColor: Record<string, string> = {
|
|
online: 'bg-kodo-lime',
|
|
offline: 'bg-kodo-steel',
|
|
away: 'bg-kodo-gold',
|
|
idle: 'bg-kodo-gold',
|
|
busy: 'bg-kodo-red',
|
|
dnd: 'bg-kodo-red',
|
|
};
|
|
|
|
return (
|
|
<tr className="hover:bg-white/5 transition-colors group relative">
|
|
<td className="p-4">
|
|
<div className="flex items-center gap-4">
|
|
<div className="relative">
|
|
<img
|
|
src={user.avatar}
|
|
className="w-8 h-8 rounded-full object-cover"
|
|
/>
|
|
<div
|
|
className={`absolute -bottom-0.5 -right-0.5 w-2.5 h-2.5 border-2 border-kodo-ink rounded-full ${user.status ? statusColor[user.status] : statusColor.offline}`}
|
|
></div>
|
|
</div>
|
|
<div>
|
|
<div className="font-bold text-white text-sm">{user.username}</div>
|
|
<div className="text-xs text-kodo-content-dim font-mono">{user.id}</div>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
<td className="p-4 text-sm text-kodo-content-dim">{user.email}</td>
|
|
<td className="p-4">
|
|
<div className="flex gap-1">
|
|
{(user.roles || [user.role]).map((role: string) => (
|
|
<Badge
|
|
key={role}
|
|
label={role}
|
|
variant={
|
|
role === 'Admin' || role === 'admin' ? 'magenta' : 'cyan'
|
|
}
|
|
className="scale-90"
|
|
/>
|
|
))}
|
|
</div>
|
|
</td>
|
|
<td className="p-4 text-sm text-kodo-content-dim">{user.tier || 'Free'}</td>
|
|
<td className="p-4 text-sm text-kodo-content-dim font-mono">
|
|
{user.joinDate || user.created_at}
|
|
</td>
|
|
<td className="p-4 text-sm text-kodo-content-dim font-mono">
|
|
{user.lastLogin || user.last_login_at || 'Never'}
|
|
</td>
|
|
<td className="p-4 text-right">
|
|
<div className="relative">
|
|
<button
|
|
className="p-1.5 hover:bg-white/10 rounded text-kodo-content-dim hover:text-white"
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
setShowMenu(!showMenu);
|
|
}}
|
|
>
|
|
<MoreVertical className="w-4 h-4" />
|
|
</button>
|
|
|
|
{showMenu && (
|
|
<>
|
|
<div
|
|
className="fixed inset-0 z-10"
|
|
onClick={() => setShowMenu(false)}
|
|
></div>
|
|
<div className="absolute right-0 top-full mt-2 w-48 bg-kodo-graphite border border-kodo-steel rounded-lg shadow-xl z-20 overflow-hidden">
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
className="w-full justify-start text-xs h-auto py-2.5"
|
|
onClick={() => {
|
|
onEditRole(user);
|
|
setShowMenu(false);
|
|
}}
|
|
>
|
|
<Shield className="w-3 h-3" /> Change Role
|
|
</Button>
|
|
<Button variant="ghost" size="sm" className="w-full justify-start text-xs h-auto py-2.5">
|
|
<Mail className="w-3 h-3" /> Send Email
|
|
</Button>
|
|
<div className="h-px bg-kodo-steel/50 my-1"></div>
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
className="w-full justify-start text-xs h-auto py-2.5 text-kodo-gold"
|
|
onClick={() => {
|
|
onBan(user);
|
|
setShowMenu(false);
|
|
}}
|
|
>
|
|
<Ban className="w-3 h-3" /> Suspend User
|
|
</Button>
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
className="w-full justify-start text-xs h-auto py-2.5 text-kodo-red"
|
|
onClick={() => {
|
|
onDelete(user);
|
|
setShowMenu(false);
|
|
}}
|
|
>
|
|
<Trash2 className="w-3 h-3" /> Delete Account
|
|
</Button>
|
|
</div>
|
|
</>
|
|
)}
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
);
|
|
};
|