fix: remove dark: variants, use theme colors only
ISSUE: Tailwind v4 doesn't handle dark: variants the same way as v3 The dark: prefix wasn't working properly in light mode SOLUTION: Replace all dark: variants with theme-aware colors Admin Pages: - AdminPlans: badges use bg-primary/20, bg-green-500/20, etc. - AdminUsers: role/status badges use opacity-based colors - AdminDashboard: stat cards use /20 opacity backgrounds Member Pages: - Overview: chart tooltips use text-foreground Benefits: ✅ Works correctly in both light and dark mode ✅ Colors automatically adapt via CSS variables ✅ No more hardcoded dark: classes ✅ Consistent opacity-based approach Technical: - Tailwind v4 uses @theme and CSS variables - dark: variants need proper configuration - Using /20 opacity on colors works universally
This commit is contained in:
@@ -62,7 +62,7 @@ export function AdminSidebar({ currentPage, onNavigate }: AdminSidebarProps) {
|
||||
<Logo variant="large" />
|
||||
</div>
|
||||
</SidebarHeader>
|
||||
<SidebarContent>
|
||||
<SidebarContent className="p-4">
|
||||
<SidebarGroup>
|
||||
<SidebarGroupContent>
|
||||
<SidebarMenu>
|
||||
@@ -80,7 +80,7 @@ export function AdminSidebar({ currentPage, onNavigate }: AdminSidebarProps) {
|
||||
: ''
|
||||
}`}
|
||||
>
|
||||
<button className="w-full">
|
||||
<button className="cursor-pointer w-full py-6 px-4">
|
||||
<item.icon />
|
||||
<span>{item.title}</span>
|
||||
</button>
|
||||
@@ -110,15 +110,13 @@ export function AdminSidebar({ currentPage, onNavigate }: AdminSidebarProps) {
|
||||
{user?.name && (
|
||||
<span className="text-sm font-medium truncate">{user.name}</span>
|
||||
)}
|
||||
<span className="text-xs text-muted-foreground truncate">
|
||||
{user?.email}
|
||||
</span>
|
||||
<span className="text-xs text-muted-foreground truncate">{user?.email}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={logout}
|
||||
className="w-full mt-3 flex items-center justify-center px-4 py-2 text-sm font-medium text-destructive-foreground bg-destructive rounded-lg hover:bg-destructive/90 transition-colors"
|
||||
className="w-full mt-3 flex items-center justify-center px-4 py-2 text-sm font-medium text-destructive bg-destructive/10 rounded-lg hover:bg-destructive/20 transition-colors cursor-pointer"
|
||||
>
|
||||
<LogOut className="h-4 w-4 mr-2" />
|
||||
Logout
|
||||
|
||||
@@ -52,29 +52,29 @@ export function AdminDashboard() {
|
||||
name: 'Total Users',
|
||||
value: stats?.totalUsers || 0,
|
||||
icon: Users,
|
||||
color: 'bg-blue-500',
|
||||
bgColor: 'bg-blue-50 dark:bg-blue-900/20',
|
||||
color: 'text-blue-600',
|
||||
bgColor: 'bg-blue-500/20',
|
||||
},
|
||||
{
|
||||
name: 'Active Subscriptions',
|
||||
value: stats?.activeSubscriptions || 0,
|
||||
icon: TrendingUp,
|
||||
color: 'bg-green-500',
|
||||
bgColor: 'bg-green-50 dark:bg-green-900/20',
|
||||
color: 'text-green-600',
|
||||
bgColor: 'bg-green-500/20',
|
||||
},
|
||||
{
|
||||
name: 'Pending Payments',
|
||||
value: pendingPayments,
|
||||
icon: DollarSign,
|
||||
color: 'bg-yellow-500',
|
||||
bgColor: 'bg-yellow-50 dark:bg-yellow-900/20',
|
||||
color: 'text-yellow-600',
|
||||
bgColor: 'bg-yellow-500/20',
|
||||
},
|
||||
{
|
||||
name: 'Suspended Users',
|
||||
value: stats?.suspendedUsers || 0,
|
||||
icon: CreditCard,
|
||||
color: 'bg-red-500',
|
||||
bgColor: 'bg-red-50 dark:bg-red-900/20',
|
||||
color: 'text-red-600',
|
||||
bgColor: 'bg-red-500/20',
|
||||
},
|
||||
]
|
||||
|
||||
@@ -98,7 +98,7 @@ export function AdminDashboard() {
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<div className={`p-3 rounded-lg ${stat.bgColor}`}>
|
||||
<stat.icon className={`h-6 w-6 text-white ${stat.color}`} />
|
||||
<stat.icon className={`h-6 w-6 ${stat.color}`} />
|
||||
</div>
|
||||
<div className="ml-4">
|
||||
<p className="text-sm font-medium text-muted-foreground">
|
||||
|
||||
@@ -138,12 +138,12 @@ export function AdminPlans() {
|
||||
</div>
|
||||
{plan.badge && (
|
||||
<span
|
||||
className={`px-2 py-1 text-xs font-medium rounded ${
|
||||
className={`text-xs font-semibold inline-flex items-center rounded-md px-2.5 py-0.5 ${
|
||||
plan.badgeColor === 'blue'
|
||||
? 'bg-blue-100 text-blue-800 dark:bg-blue-900/20 dark:text-blue-400'
|
||||
? 'bg-primary/20 text-primary ring-1 ring-primary'
|
||||
: plan.badgeColor === 'green'
|
||||
? 'bg-green-100 text-green-800 dark:bg-green-900/20 dark:text-green-400'
|
||||
: 'bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-300'
|
||||
? 'bg-green-500/20 text-green-600 ring-1 ring-green-600'
|
||||
: 'bg-muted text-muted-foreground ring-1 ring-border'
|
||||
}`}
|
||||
>
|
||||
{plan.badge}
|
||||
@@ -190,12 +190,12 @@ export function AdminPlans() {
|
||||
<span className="text-muted-foreground">Status:</span>
|
||||
<div className="flex items-center space-x-2">
|
||||
{plan.isActive && (
|
||||
<span className="px-2 py-1 text-xs bg-green-100 text-green-800 dark:bg-green-900/20 dark:text-green-400 rounded">
|
||||
<span className="px-2 py-1 text-xs bg-green-500/20 text-green-600 rounded">
|
||||
Active
|
||||
</span>
|
||||
)}
|
||||
{plan.isVisible ? (
|
||||
<Eye className="h-4 w-4 text-green-600 dark:text-green-400" />
|
||||
<Eye className="h-4 w-4 text-green-600" />
|
||||
) : (
|
||||
<EyeOff className="h-4 w-4 text-muted-foreground" />
|
||||
)}
|
||||
|
||||
@@ -165,8 +165,8 @@ export function AdminUsers() {
|
||||
<span
|
||||
className={`px-2 py-1 text-xs font-medium rounded ${
|
||||
user.role === 'admin'
|
||||
? 'bg-purple-100 text-purple-800 dark:bg-purple-900/20 dark:text-purple-400'
|
||||
: 'bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-300'
|
||||
? 'bg-purple-500/20 text-purple-600'
|
||||
: 'bg-muted text-muted-foreground'
|
||||
}`}
|
||||
>
|
||||
{user.role}
|
||||
@@ -178,15 +178,15 @@ export function AdminUsers() {
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap">
|
||||
{user.suspendedAt ? (
|
||||
<span className="px-2 py-1 text-xs font-medium bg-red-100 text-red-800 dark:bg-red-900/20 dark:text-red-400 rounded">
|
||||
<span className="px-2 py-1 text-xs font-medium bg-red-500/20 text-red-600 rounded">
|
||||
Suspended
|
||||
</span>
|
||||
) : user.emailVerified ? (
|
||||
<span className="px-2 py-1 text-xs font-medium bg-green-100 text-green-800 dark:bg-green-900/20 dark:text-green-400 rounded">
|
||||
<span className="px-2 py-1 text-xs font-medium bg-green-500/20 text-green-600 rounded">
|
||||
Active
|
||||
</span>
|
||||
) : (
|
||||
<span className="px-2 py-1 text-xs font-medium bg-yellow-100 text-yellow-800 dark:bg-yellow-900/20 dark:text-yellow-400 rounded">
|
||||
<span className="px-2 py-1 text-xs font-medium bg-yellow-500/20 text-yellow-600 rounded">
|
||||
Unverified
|
||||
</span>
|
||||
)}
|
||||
|
||||
@@ -52,10 +52,10 @@ export function AppSidebar({ currentPage, onNavigate }: AppSidebarProps) {
|
||||
<Logo variant="large"/>
|
||||
</div>
|
||||
</SidebarHeader>
|
||||
<SidebarContent>
|
||||
<SidebarContent className="p-4">
|
||||
<SidebarGroup>
|
||||
<SidebarGroupContent>
|
||||
<SidebarMenu className="flex-1 space-y-1 overflow-y-auto">
|
||||
<SidebarMenu>
|
||||
{items.map((item) => {
|
||||
const isActive = currentPage === item.url
|
||||
return (
|
||||
@@ -64,13 +64,13 @@ export function AppSidebar({ currentPage, onNavigate }: AppSidebarProps) {
|
||||
asChild
|
||||
isActive={isActive}
|
||||
onClick={() => onNavigate(item.url)}
|
||||
className={`flex items-center px-4 py-3 text-sm font-medium rounded-lg transition-colors ${
|
||||
className={`${
|
||||
isActive
|
||||
? 'bg-primary/10 text-primary'
|
||||
: 'text-foreground hover:bg-accent hover:text-accent-foreground'
|
||||
? 'bg-primary/10 text-primary hover:bg-primary/10 hover:text-primary'
|
||||
: ''
|
||||
}`}
|
||||
>
|
||||
<button className="w-full">
|
||||
<button className="cursor-pointer w-full py-6 px-4">
|
||||
<item.icon />
|
||||
<span>{item.title}</span>
|
||||
</button>
|
||||
@@ -106,7 +106,7 @@ export function AppSidebar({ currentPage, onNavigate }: AppSidebarProps) {
|
||||
</div>
|
||||
<button
|
||||
onClick={logout}
|
||||
className="w-full mt-3 flex items-center justify-center px-4 py-2 text-sm font-medium text-destructive-foreground bg-destructive rounded-lg hover:bg-destructive/90 transition-colors"
|
||||
className="w-full mt-3 flex items-center justify-center px-4 py-2 text-sm font-medium text-destructive bg-destructive/10 rounded-lg hover:bg-destructive/20 transition-colors cursor-pointer"
|
||||
>
|
||||
<LogOut className="h-4 w-4 mr-2" />
|
||||
Logout
|
||||
|
||||
@@ -674,14 +674,14 @@ export function Overview() {
|
||||
<TableCell className="text-center">
|
||||
<Badge
|
||||
variant="outline"
|
||||
className={`text-xs ${wallet.kind === 'money' ? 'bg-[var(--chart-1)]/10 text-[var(--chart-1)] ring-1 ring-[var(--chart-1)]' : 'bg-[var(--chart-2)]/10 text-[var(--chart-2)] ring-1 ring-[var(--chart-2)]'}`}
|
||||
className={`text-xs border-0 ${wallet.kind === 'money' ? 'bg-primary/10 text-primary ring-1 ring-primary' : 'bg-accent/10 text-accent ring-1 ring-accent'}`}
|
||||
>
|
||||
{wallet.currency}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell className="text-center">
|
||||
<button
|
||||
className="hover:text-blue-600 hover:underline cursor-pointer"
|
||||
className="hover:text-primary-600 hover:underline cursor-pointer"
|
||||
onClick={() => {
|
||||
// Navigate to transactions page with wallet filter
|
||||
window.location.href = `/transactions?wallet=${encodeURIComponent(wallet.name)}`
|
||||
@@ -766,7 +766,7 @@ export function Overview() {
|
||||
const data = payload[0].payload
|
||||
return (
|
||||
<div className="bg-background p-2 border rounded shadow">
|
||||
<p className="font-medium text-background dark:text-foreground">{data.name}</p>
|
||||
<p className="font-medium text-foreground">{data.name}</p>
|
||||
<p className="text-[var(--primary)]">{formatCurrency(data.value, 'IDR')}</p>
|
||||
</div>
|
||||
)
|
||||
@@ -814,7 +814,7 @@ export function Overview() {
|
||||
const data = payload[0].payload
|
||||
return (
|
||||
<div className="bg-background p-2 border rounded shadow">
|
||||
<p className="font-medium text-background dark:text-foreground">{data.name}</p>
|
||||
<p className="font-medium text-foreground">{data.name}</p>
|
||||
<p className="text-[var(--primary)]">{formatCurrency(data.value, 'IDR')}</p>
|
||||
</div>
|
||||
)
|
||||
@@ -897,7 +897,7 @@ export function Overview() {
|
||||
const data = payload[0].payload
|
||||
return (
|
||||
<div className="bg-background p-2 border rounded shadow">
|
||||
<p className="font-medium text-background dark:text-foreground">{data.name}</p>
|
||||
<p className="font-medium text-foreground">{data.name}</p>
|
||||
<p className="text-[var(--destructive)]">{formatCurrency(data.value, 'IDR')}</p>
|
||||
</div>
|
||||
)
|
||||
@@ -945,7 +945,7 @@ export function Overview() {
|
||||
const data = payload[0].payload
|
||||
return (
|
||||
<div className="bg-background p-2 border rounded shadow">
|
||||
<p className="font-medium text-background dark:text-foreground">{data.name}</p>
|
||||
<p className="font-medium text-foreground">{data.name}</p>
|
||||
<p className="text-[var(--destructive)]">{formatCurrency(data.value, 'IDR')}</p>
|
||||
</div>
|
||||
)
|
||||
@@ -1035,7 +1035,7 @@ export function Overview() {
|
||||
if (active && payload && payload.length) {
|
||||
return (
|
||||
<div className="bg-background p-3 border rounded shadow">
|
||||
<p className="font-medium mb-2 text-background dark:text-foreground">{label}</p>
|
||||
<p className="font-medium mb-2 text-foreground">{label}</p>
|
||||
{payload.map((entry, index) => (
|
||||
<div key={index} className="flex items-center gap-2">
|
||||
<div
|
||||
|
||||
@@ -208,7 +208,7 @@ const ChartTooltipContent = React.forwardRef<
|
||||
!hideIndicator && (
|
||||
<div
|
||||
className={cn(
|
||||
"shrink-0 rounded-[2px] border-[--color-border] bg-[--color-bg]",
|
||||
"shrink-0 rounded-[2px] border-[var(--color-border)] bg-[var(--color-bg)]",
|
||||
{
|
||||
"h-2.5 w-2.5": indicator === "dot",
|
||||
"w-1": indicator === "line",
|
||||
|
||||
@@ -20,7 +20,7 @@ const PopoverContent = React.forwardRef<
|
||||
sideOffset={sideOffset}
|
||||
style={{backgroundColor: "var(--background)"}}
|
||||
className={cn(
|
||||
"z-50 w-72 rounded-md border-2 border-border bg-card p-4 text-card-foreground shadow-xl outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-popover-content-transform-origin]",
|
||||
"z-50 w-72 rounded-md border-2 border-border bg-card p-4 text-card-foreground shadow-xl outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[var(--radix-popover-content-transform-origin)]",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -74,7 +74,7 @@ const SelectContent = React.forwardRef<
|
||||
ref={ref}
|
||||
style={{backgroundColor: "var(--background)"}}
|
||||
className={cn(
|
||||
"relative z-50 max-h-[--radix-select-content-available-height] min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border-2 border-border bg-card text-card-foreground shadow-xl data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-select-content-transform-origin]",
|
||||
"relative z-50 max-h-[var(--radix-select-content-available-height)] min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border-2 border-border bg-card text-card-foreground shadow-xl data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[var(--radix-select-content-transform-origin)]",
|
||||
position === "popper" &&
|
||||
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
||||
className
|
||||
|
||||
@@ -185,7 +185,7 @@ const Sidebar = React.forwardRef<
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"flex h-full w-[--sidebar-width] flex-col bg-sidebar bg-background text-sidebar-foreground",
|
||||
"flex h-full w-[var(--sidebar-width)] flex-col bg-sidebar bg-background text-sidebar-foreground",
|
||||
className
|
||||
)}
|
||||
ref={ref}
|
||||
@@ -202,7 +202,7 @@ const Sidebar = React.forwardRef<
|
||||
<SheetContent
|
||||
data-sidebar="sidebar"
|
||||
data-mobile="true"
|
||||
className="w-[--sidebar-width] bg-sidebar bg-background p-0 text-sidebar-foreground [&>button]:hidden"
|
||||
className="w-[var(--sidebar-width)] bg-sidebar bg-background p-0 text-sidebar-foreground [&>button]:hidden"
|
||||
style={
|
||||
{
|
||||
"--sidebar-width": SIDEBAR_WIDTH_MOBILE,
|
||||
@@ -232,24 +232,24 @@ const Sidebar = React.forwardRef<
|
||||
{/* This is what handles the sidebar gap on desktop */}
|
||||
<div
|
||||
className={cn(
|
||||
"relative w-[--sidebar-width] bg-transparent transition-[width] duration-200 ease-linear",
|
||||
"relative w-[var(--sidebar-width)] bg-transparent transition-[width] duration-200 ease-linear",
|
||||
"group-data-[collapsible=offcanvas]:w-0",
|
||||
"group-data-[side=right]:rotate-180",
|
||||
variant === "floating" || variant === "inset"
|
||||
? "group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4))]"
|
||||
: "group-data-[collapsible=icon]:w-[--sidebar-width-icon]"
|
||||
: "group-data-[collapsible=icon]:w-[var(--sidebar-width-icon)]"
|
||||
)}
|
||||
/>
|
||||
<div
|
||||
className={cn(
|
||||
"inset-y-0 z-10 hidden h-svh w-[--sidebar-width] transition-[left,right,width] duration-200 ease-linear md:flex",
|
||||
"inset-y-0 z-10 hidden h-svh w-[var(--sidebar-width)] transition-[left,right,width] duration-200 ease-linear md:flex",
|
||||
side === "left"
|
||||
? "left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]"
|
||||
: "right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]",
|
||||
// Adjust the padding for floating and inset variants.
|
||||
variant === "floating" || variant === "inset"
|
||||
? "p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4)_+2px)]"
|
||||
: "group-data-[collapsible=icon]:w-[--sidebar-width-icon] group-data-[side=left]:border-r group-data-[side=right]:border-l",
|
||||
: "group-data-[collapsible=icon]:w-[var(--sidebar-width-icon)] group-data-[side=left]:border-r group-data-[side=right]:border-l",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
@@ -677,7 +677,7 @@ const SidebarMenuSkeleton = React.forwardRef<
|
||||
/>
|
||||
)}
|
||||
<Skeleton
|
||||
className="h-4 max-w-[--skeleton-width] flex-1"
|
||||
className="h-4 max-w-[var(--skeleton-width)] flex-1"
|
||||
data-sidebar="menu-skeleton-text"
|
||||
style={
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user