AV: precise recolor to spec — warm dark backgrounds (#1c1815/#231d18/#26201a/#15110e) + gold #F0A623 / orange #D85A30 accents + warm text (#FBEFE0/#C9BBA8/#8a7e6e). 138 component files + 36 token swaps.
This commit is contained in:
@@ -61,7 +61,7 @@ const ROLE_COLORS: Record<string, string> = {
|
||||
editor: 'text-purple-400 bg-purple-400/10 border-purple-400/20',
|
||||
moderator: 'text-orange-400 bg-orange-400/10 border-orange-400/20',
|
||||
contributor: 'text-blue-400 bg-blue-400/10 border-blue-400/20',
|
||||
reader: 'text-[#888] bg-[#1a1a1a] border-[#333]',
|
||||
reader: 'text-[#888] bg-[#231d18] border-[#333]',
|
||||
};
|
||||
|
||||
const ROLE_ICONS: Record<string, React.ReactNode> = {
|
||||
@@ -324,7 +324,7 @@ export default function AdminRolesPage() {
|
||||
return (
|
||||
<div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center">
|
||||
<div className="flex flex-col items-center gap-3">
|
||||
<div className="w-8 h-8 border-2 border-[#ffb800] border-t-transparent rounded-full animate-spin" />
|
||||
<div className="w-8 h-8 border-2 border-[#F0A623] border-t-transparent rounded-full animate-spin" />
|
||||
<p className="text-[#555] text-sm font-body">Loading roles panel…</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -376,7 +376,7 @@ export default function AdminRolesPage() {
|
||||
key={role}
|
||||
onClick={() => { setSelectedRole(role === selectedRole ? 'all' : role); setActiveTab('assign'); }}
|
||||
className={`bg-[#111] border rounded-lg p-4 text-left hover:border-[#333] transition-all ${
|
||||
selectedRole === role ? 'border-[#ffb800] bg-[#0d1a2d]' : 'border-[#252525]'
|
||||
selectedRole === role ? 'border-[#F0A623] bg-[#0d1a2d]' : 'border-[#3a322b]'
|
||||
}`}
|
||||
>
|
||||
<div className={`inline-flex items-center gap-1.5 px-2 py-0.5 rounded-full text-xs font-body font-semibold border mb-2 ${color}`}>
|
||||
@@ -391,7 +391,7 @@ export default function AdminRolesPage() {
|
||||
</div>
|
||||
|
||||
{/* Tabs */}
|
||||
<div className="flex gap-1 mb-6 bg-[#111] border border-[#252525] rounded-lg p-1 w-fit">
|
||||
<div className="flex gap-1 mb-6 bg-[#111] border border-[#3a322b] rounded-lg p-1 w-fit">
|
||||
{([
|
||||
{ id: 'assign', label: 'Assign Roles', icon: 'M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0z' },
|
||||
{ id: 'permissions', label: 'Permissions', icon: 'M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z' },
|
||||
@@ -402,7 +402,7 @@ export default function AdminRolesPage() {
|
||||
onClick={() => setActiveTab(tab.id)}
|
||||
className={`flex items-center gap-2 px-4 py-2 rounded-md text-sm font-body font-medium transition-all ${
|
||||
activeTab === tab.id
|
||||
? 'bg-[#ffb800] text-white'
|
||||
? 'bg-[#F0A623] text-white'
|
||||
: 'text-[#888] hover:text-white'
|
||||
}`}
|
||||
>
|
||||
@@ -416,9 +416,9 @@ export default function AdminRolesPage() {
|
||||
|
||||
{/* ── Tab: Assign Roles ── */}
|
||||
{activeTab === 'assign' && (
|
||||
<div className="bg-[#111] border border-[#252525] rounded-lg overflow-hidden">
|
||||
<div className="bg-[#111] border border-[#3a322b] rounded-lg overflow-hidden">
|
||||
{/* Toolbar */}
|
||||
<div className="px-5 py-4 border-b border-[#1a1a1a] flex flex-col sm:flex-row gap-3">
|
||||
<div className="px-5 py-4 border-b border-[#231d18] flex flex-col sm:flex-row gap-3">
|
||||
<div className="relative flex-1">
|
||||
<svg className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-[#555]" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
||||
@@ -428,13 +428,13 @@ export default function AdminRolesPage() {
|
||||
placeholder="Search users…"
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className="w-full bg-[#0d0d0d] border border-[#252525] rounded-lg pl-9 pr-4 py-2 text-sm text-white placeholder-[#555] font-body focus:outline-none focus:border-[#ffb800]"
|
||||
className="w-full bg-[#1c1815] border border-[#3a322b] rounded-lg pl-9 pr-4 py-2 text-sm text-white placeholder-[#555] font-body focus:outline-none focus:border-[#F0A623]"
|
||||
/>
|
||||
</div>
|
||||
<select
|
||||
value={selectedRole}
|
||||
onChange={(e) => setSelectedRole(e.target.value)}
|
||||
className="bg-[#0d0d0d] border border-[#252525] rounded-lg px-3 py-2 text-sm text-white font-body focus:outline-none focus:border-[#ffb800]"
|
||||
className="bg-[#1c1815] border border-[#3a322b] rounded-lg px-3 py-2 text-sm text-white font-body focus:outline-none focus:border-[#F0A623]"
|
||||
>
|
||||
<option value="all">All Roles</option>
|
||||
{ROLES.map((r) => (
|
||||
@@ -447,7 +447,7 @@ export default function AdminRolesPage() {
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full">
|
||||
<thead>
|
||||
<tr className="border-b border-[#1a1a1a]">
|
||||
<tr className="border-b border-[#231d18]">
|
||||
<th className="px-5 py-3 text-left text-xs font-body font-bold text-[#555] uppercase tracking-wider">User</th>
|
||||
<th className="px-5 py-3 text-left text-xs font-body font-bold text-[#555] uppercase tracking-wider">Current Role</th>
|
||||
<th className="px-5 py-3 text-left text-xs font-body font-bold text-[#555] uppercase tracking-wider hidden sm:table-cell">Status</th>
|
||||
@@ -455,7 +455,7 @@ export default function AdminRolesPage() {
|
||||
<th className="px-5 py-3 text-left text-xs font-body font-bold text-[#555] uppercase tracking-wider">Assign Role</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-[#1a1a1a]">
|
||||
<tbody className="divide-y divide-[#231d18]">
|
||||
{filteredUsers.length === 0 ? (
|
||||
<tr>
|
||||
<td colSpan={5} className="px-5 py-10 text-center text-[#555] text-sm font-body">
|
||||
@@ -464,10 +464,10 @@ export default function AdminRolesPage() {
|
||||
</tr>
|
||||
) : (
|
||||
filteredUsers.map((u) => (
|
||||
<tr key={u.id} className="hover:bg-[#0d0d0d] transition-colors">
|
||||
<tr key={u.id} className="hover:bg-[#1c1815] transition-colors">
|
||||
<td className="px-5 py-3">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-8 h-8 rounded-full bg-[#1a1a1a] flex items-center justify-center flex-shrink-0 text-xs font-body font-bold text-[#888]">
|
||||
<div className="w-8 h-8 rounded-full bg-[#231d18] flex items-center justify-center flex-shrink-0 text-xs font-body font-bold text-[#888]">
|
||||
{u.avatar_url
|
||||
? <img src={u.avatar_url} alt={u.full_name} className="w-8 h-8 rounded-full object-cover" />
|
||||
: getInitials(u.full_name || u.email)
|
||||
@@ -484,7 +484,7 @@ export default function AdminRolesPage() {
|
||||
</td>
|
||||
<td className="px-5 py-3 hidden sm:table-cell">
|
||||
<span className={`text-xs font-body font-semibold px-2 py-0.5 rounded-full ${
|
||||
u.is_active ? 'text-green-400 bg-green-400/10' : 'text-[#555] bg-[#1a1a1a]'
|
||||
u.is_active ? 'text-green-400 bg-green-400/10' : 'text-[#555] bg-[#231d18]'
|
||||
}`}>
|
||||
{u.is_active ? 'Active' : 'Inactive'}
|
||||
</span>
|
||||
@@ -500,14 +500,14 @@ export default function AdminRolesPage() {
|
||||
defaultValue={u.role}
|
||||
onChange={(e) => handleAssignRole(u.id, e.target.value)}
|
||||
disabled={processingUserId === u.id}
|
||||
className="bg-[#0d0d0d] border border-[#252525] rounded-lg px-2 py-1.5 text-xs text-white font-body focus:outline-none focus:border-[#ffb800] disabled:opacity-50"
|
||||
className="bg-[#1c1815] border border-[#3a322b] rounded-lg px-2 py-1.5 text-xs text-white font-body focus:outline-none focus:border-[#F0A623] disabled:opacity-50"
|
||||
>
|
||||
{ROLES.map((r) => (
|
||||
<option key={r} value={r} className="capitalize">{r.charAt(0).toUpperCase() + r.slice(1)}</option>
|
||||
))}
|
||||
</select>
|
||||
{processingUserId === u.id && (
|
||||
<div className="w-4 h-4 border-2 border-[#ffb800] border-t-transparent rounded-full animate-spin" />
|
||||
<div className="w-4 h-4 border-2 border-[#F0A623] border-t-transparent rounded-full animate-spin" />
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
@@ -519,7 +519,7 @@ export default function AdminRolesPage() {
|
||||
</div>
|
||||
|
||||
{filteredUsers.length > 0 && (
|
||||
<div className="px-5 py-3 border-t border-[#1a1a1a]">
|
||||
<div className="px-5 py-3 border-t border-[#231d18]">
|
||||
<p className="text-xs text-[#555] font-body">{filteredUsers.length} user{filteredUsers.length !== 1 ? 's' : ''} shown</p>
|
||||
</div>
|
||||
)}
|
||||
@@ -535,9 +535,9 @@ export default function AdminRolesPage() {
|
||||
const color = ROLE_COLORS[perm.role] || ROLE_COLORS['reader'];
|
||||
|
||||
return (
|
||||
<div key={perm.role} className="bg-[#111] border border-[#252525] rounded-lg overflow-hidden">
|
||||
<div key={perm.role} className="bg-[#111] border border-[#3a322b] rounded-lg overflow-hidden">
|
||||
{/* Role Header */}
|
||||
<div className="px-5 py-4 border-b border-[#1a1a1a] flex items-center justify-between">
|
||||
<div className="px-5 py-4 border-b border-[#231d18] flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className={`inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-body font-semibold border ${color}`}>
|
||||
{ROLE_ICONS[perm.role]}
|
||||
@@ -557,7 +557,7 @@ export default function AdminRolesPage() {
|
||||
<button
|
||||
onClick={handleSavePermissions}
|
||||
disabled={savingPermissions}
|
||||
className="px-3 py-1.5 text-xs font-body font-semibold bg-[#ffb800] hover:bg-blue-500 text-white rounded-lg transition-colors disabled:opacity-50 flex items-center gap-1.5"
|
||||
className="px-3 py-1.5 text-xs font-body font-semibold bg-[#F0A623] hover:bg-blue-500 text-white rounded-lg transition-colors disabled:opacity-50 flex items-center gap-1.5"
|
||||
>
|
||||
{savingPermissions && <div className="w-3 h-3 border-2 border-white border-t-transparent rounded-full animate-spin" />}
|
||||
Save Changes
|
||||
@@ -593,12 +593,12 @@ export default function AdminRolesPage() {
|
||||
{isEditing ? (
|
||||
<button
|
||||
onClick={() => setEditedPermissions((prev) => ({ ...prev, [key]: !val }))}
|
||||
className={`relative w-9 h-5 rounded-full transition-colors ${val ? 'bg-[#ffb800]' : 'bg-[#333]'}`}
|
||||
className={`relative w-9 h-5 rounded-full transition-colors ${val ? 'bg-[#F0A623]' : 'bg-[#333]'}`}
|
||||
>
|
||||
<span className={`absolute top-0.5 w-4 h-4 bg-white rounded-full shadow transition-transform ${val ? 'translate-x-4' : 'translate-x-0.5'}`} />
|
||||
</button>
|
||||
) : (
|
||||
<span className={`w-5 h-5 rounded-full flex items-center justify-center flex-shrink-0 ${val ? 'bg-green-400/10' : 'bg-[#1a1a1a]'}`}>
|
||||
<span className={`w-5 h-5 rounded-full flex items-center justify-center flex-shrink-0 ${val ? 'bg-green-400/10' : 'bg-[#231d18]'}`}>
|
||||
{val ? (
|
||||
<svg className="w-3 h-3 text-green-400" fill="none" stroke="currentColor" strokeWidth="3" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" />
|
||||
@@ -631,9 +631,9 @@ export default function AdminRolesPage() {
|
||||
|
||||
{/* ── Tab: Activity Logs ── */}
|
||||
{activeTab === 'activity' && (
|
||||
<div className="bg-[#111] border border-[#252525] rounded-lg overflow-hidden">
|
||||
<div className="bg-[#111] border border-[#3a322b] rounded-lg overflow-hidden">
|
||||
{/* Toolbar */}
|
||||
<div className="px-5 py-4 border-b border-[#1a1a1a] flex flex-col sm:flex-row gap-3 items-start sm:items-center justify-between">
|
||||
<div className="px-5 py-4 border-b border-[#231d18] flex flex-col sm:flex-row gap-3 items-start sm:items-center justify-between">
|
||||
<h2 className="text-sm font-display font-bold text-white">Role-Based Activity</h2>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-xs text-[#555] font-body">Filter by role:</span>
|
||||
@@ -644,8 +644,8 @@ export default function AdminRolesPage() {
|
||||
onClick={() => setActivityRoleFilter(r)}
|
||||
className={`px-2.5 py-1 rounded-md text-xs font-body font-medium transition-colors capitalize ${
|
||||
activityRoleFilter === r
|
||||
? 'bg-[#ffb800] text-white'
|
||||
: 'bg-[#1a1a1a] text-[#888] hover:text-white'
|
||||
? 'bg-[#F0A623] text-white'
|
||||
: 'bg-[#231d18] text-[#888] hover:text-white'
|
||||
}`}
|
||||
>
|
||||
{r}
|
||||
@@ -656,7 +656,7 @@ export default function AdminRolesPage() {
|
||||
</div>
|
||||
|
||||
{/* Log List */}
|
||||
<div className="divide-y divide-[#1a1a1a]">
|
||||
<div className="divide-y divide-[#231d18]">
|
||||
{activityLogs.length === 0 ? (
|
||||
<div className="px-5 py-12 text-center">
|
||||
<svg className="w-10 h-10 text-[#333] mx-auto mb-3" fill="none" stroke="currentColor" strokeWidth="1.5" viewBox="0 0 24 24">
|
||||
@@ -666,13 +666,13 @@ export default function AdminRolesPage() {
|
||||
</div>
|
||||
) : (
|
||||
activityLogs.map((log) => {
|
||||
const actionColor = ACTION_COLORS[log.action] || 'text-[#888] bg-[#1a1a1a]';
|
||||
const actionColor = ACTION_COLORS[log.action] || 'text-[#888] bg-[#231d18]';
|
||||
const actionLabel = ACTION_LABELS[log.action] || log.action.replace(/_/g, ' ');
|
||||
const performer = log.user_profiles?.full_name || log.performed_by_email || 'System';
|
||||
const performerRole = log.user_profiles?.role;
|
||||
|
||||
return (
|
||||
<div key={log.id} className="px-5 py-3.5 flex items-start gap-4 hover:bg-[#0d0d0d] transition-colors">
|
||||
<div key={log.id} className="px-5 py-3.5 flex items-start gap-4 hover:bg-[#1c1815] transition-colors">
|
||||
{/* Action badge */}
|
||||
<div className={`mt-0.5 px-2 py-0.5 rounded text-xs font-body font-semibold flex-shrink-0 ${actionColor}`}>
|
||||
{actionLabel}
|
||||
@@ -720,7 +720,7 @@ export default function AdminRolesPage() {
|
||||
</div>
|
||||
|
||||
{activityLogs.length > 0 && (
|
||||
<div className="px-5 py-3 border-t border-[#1a1a1a]">
|
||||
<div className="px-5 py-3 border-t border-[#231d18]">
|
||||
<p className="text-xs text-[#555] font-body">{activityLogs.length} log entries shown</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user