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:
dwindown
2025-10-11 21:20:36 +07:00
parent 46f03a34a5
commit bef2344ddc
10 changed files with 48 additions and 50 deletions

View File

@@ -62,7 +62,7 @@ export function AdminSidebar({ currentPage, onNavigate }: AdminSidebarProps) {
<Logo variant="large" /> <Logo variant="large" />
</div> </div>
</SidebarHeader> </SidebarHeader>
<SidebarContent> <SidebarContent className="p-4">
<SidebarGroup> <SidebarGroup>
<SidebarGroupContent> <SidebarGroupContent>
<SidebarMenu> <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 /> <item.icon />
<span>{item.title}</span> <span>{item.title}</span>
</button> </button>
@@ -110,15 +110,13 @@ export function AdminSidebar({ currentPage, onNavigate }: AdminSidebarProps) {
{user?.name && ( {user?.name && (
<span className="text-sm font-medium truncate">{user.name}</span> <span className="text-sm font-medium truncate">{user.name}</span>
)} )}
<span className="text-xs text-muted-foreground truncate"> <span className="text-xs text-muted-foreground truncate">{user?.email}</span>
{user?.email}
</span>
</div> </div>
</div> </div>
</div> </div>
<button <button
onClick={logout} 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 className="h-4 w-4 mr-2" />
Logout Logout

View File

@@ -52,29 +52,29 @@ export function AdminDashboard() {
name: 'Total Users', name: 'Total Users',
value: stats?.totalUsers || 0, value: stats?.totalUsers || 0,
icon: Users, icon: Users,
color: 'bg-blue-500', color: 'text-blue-600',
bgColor: 'bg-blue-50 dark:bg-blue-900/20', bgColor: 'bg-blue-500/20',
}, },
{ {
name: 'Active Subscriptions', name: 'Active Subscriptions',
value: stats?.activeSubscriptions || 0, value: stats?.activeSubscriptions || 0,
icon: TrendingUp, icon: TrendingUp,
color: 'bg-green-500', color: 'text-green-600',
bgColor: 'bg-green-50 dark:bg-green-900/20', bgColor: 'bg-green-500/20',
}, },
{ {
name: 'Pending Payments', name: 'Pending Payments',
value: pendingPayments, value: pendingPayments,
icon: DollarSign, icon: DollarSign,
color: 'bg-yellow-500', color: 'text-yellow-600',
bgColor: 'bg-yellow-50 dark:bg-yellow-900/20', bgColor: 'bg-yellow-500/20',
}, },
{ {
name: 'Suspended Users', name: 'Suspended Users',
value: stats?.suspendedUsers || 0, value: stats?.suspendedUsers || 0,
icon: CreditCard, icon: CreditCard,
color: 'bg-red-500', color: 'text-red-600',
bgColor: 'bg-red-50 dark:bg-red-900/20', bgColor: 'bg-red-500/20',
}, },
] ]
@@ -98,7 +98,7 @@ export function AdminDashboard() {
> >
<div className="flex items-center"> <div className="flex items-center">
<div className={`p-3 rounded-lg ${stat.bgColor}`}> <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>
<div className="ml-4"> <div className="ml-4">
<p className="text-sm font-medium text-muted-foreground"> <p className="text-sm font-medium text-muted-foreground">

View File

@@ -138,12 +138,12 @@ export function AdminPlans() {
</div> </div>
{plan.badge && ( {plan.badge && (
<span <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' 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' : plan.badgeColor === 'green'
? 'bg-green-100 text-green-800 dark:bg-green-900/20 dark:text-green-400' ? 'bg-green-500/20 text-green-600 ring-1 ring-green-600'
: 'bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-300' : 'bg-muted text-muted-foreground ring-1 ring-border'
}`} }`}
> >
{plan.badge} {plan.badge}
@@ -190,12 +190,12 @@ export function AdminPlans() {
<span className="text-muted-foreground">Status:</span> <span className="text-muted-foreground">Status:</span>
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
{plan.isActive && ( {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 Active
</span> </span>
)} )}
{plan.isVisible ? ( {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" /> <EyeOff className="h-4 w-4 text-muted-foreground" />
)} )}

View File

@@ -165,8 +165,8 @@ export function AdminUsers() {
<span <span
className={`px-2 py-1 text-xs font-medium rounded ${ className={`px-2 py-1 text-xs font-medium rounded ${
user.role === 'admin' user.role === 'admin'
? 'bg-purple-100 text-purple-800 dark:bg-purple-900/20 dark:text-purple-400' ? 'bg-purple-500/20 text-purple-600'
: 'bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-300' : 'bg-muted text-muted-foreground'
}`} }`}
> >
{user.role} {user.role}
@@ -178,15 +178,15 @@ export function AdminUsers() {
</td> </td>
<td className="px-6 py-4 whitespace-nowrap"> <td className="px-6 py-4 whitespace-nowrap">
{user.suspendedAt ? ( {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 Suspended
</span> </span>
) : user.emailVerified ? ( ) : 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 Active
</span> </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 Unverified
</span> </span>
)} )}

View File

@@ -52,10 +52,10 @@ export function AppSidebar({ currentPage, onNavigate }: AppSidebarProps) {
<Logo variant="large"/> <Logo variant="large"/>
</div> </div>
</SidebarHeader> </SidebarHeader>
<SidebarContent> <SidebarContent className="p-4">
<SidebarGroup> <SidebarGroup>
<SidebarGroupContent> <SidebarGroupContent>
<SidebarMenu className="flex-1 space-y-1 overflow-y-auto"> <SidebarMenu>
{items.map((item) => { {items.map((item) => {
const isActive = currentPage === item.url const isActive = currentPage === item.url
return ( return (
@@ -64,13 +64,13 @@ export function AppSidebar({ currentPage, onNavigate }: AppSidebarProps) {
asChild asChild
isActive={isActive} isActive={isActive}
onClick={() => onNavigate(item.url)} onClick={() => onNavigate(item.url)}
className={`flex items-center px-4 py-3 text-sm font-medium rounded-lg transition-colors ${ className={`${
isActive isActive
? 'bg-primary/10 text-primary' ? 'bg-primary/10 text-primary hover:bg-primary/10 hover:text-primary'
: 'text-foreground hover:bg-accent hover:text-accent-foreground' : ''
}`} }`}
> >
<button className="w-full"> <button className="cursor-pointer w-full py-6 px-4">
<item.icon /> <item.icon />
<span>{item.title}</span> <span>{item.title}</span>
</button> </button>
@@ -106,7 +106,7 @@ export function AppSidebar({ currentPage, onNavigate }: AppSidebarProps) {
</div> </div>
<button <button
onClick={logout} 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 className="h-4 w-4 mr-2" />
Logout Logout

View File

@@ -674,14 +674,14 @@ export function Overview() {
<TableCell className="text-center"> <TableCell className="text-center">
<Badge <Badge
variant="outline" 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} {wallet.currency}
</Badge> </Badge>
</TableCell> </TableCell>
<TableCell className="text-center"> <TableCell className="text-center">
<button <button
className="hover:text-blue-600 hover:underline cursor-pointer" className="hover:text-primary-600 hover:underline cursor-pointer"
onClick={() => { onClick={() => {
// Navigate to transactions page with wallet filter // Navigate to transactions page with wallet filter
window.location.href = `/transactions?wallet=${encodeURIComponent(wallet.name)}` window.location.href = `/transactions?wallet=${encodeURIComponent(wallet.name)}`
@@ -766,7 +766,7 @@ export function Overview() {
const data = payload[0].payload const data = payload[0].payload
return ( return (
<div className="bg-background p-2 border rounded shadow"> <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> <p className="text-[var(--primary)]">{formatCurrency(data.value, 'IDR')}</p>
</div> </div>
) )
@@ -814,7 +814,7 @@ export function Overview() {
const data = payload[0].payload const data = payload[0].payload
return ( return (
<div className="bg-background p-2 border rounded shadow"> <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> <p className="text-[var(--primary)]">{formatCurrency(data.value, 'IDR')}</p>
</div> </div>
) )
@@ -897,7 +897,7 @@ export function Overview() {
const data = payload[0].payload const data = payload[0].payload
return ( return (
<div className="bg-background p-2 border rounded shadow"> <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> <p className="text-[var(--destructive)]">{formatCurrency(data.value, 'IDR')}</p>
</div> </div>
) )
@@ -945,7 +945,7 @@ export function Overview() {
const data = payload[0].payload const data = payload[0].payload
return ( return (
<div className="bg-background p-2 border rounded shadow"> <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> <p className="text-[var(--destructive)]">{formatCurrency(data.value, 'IDR')}</p>
</div> </div>
) )
@@ -1035,7 +1035,7 @@ export function Overview() {
if (active && payload && payload.length) { if (active && payload && payload.length) {
return ( return (
<div className="bg-background p-3 border rounded shadow"> <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) => ( {payload.map((entry, index) => (
<div key={index} className="flex items-center gap-2"> <div key={index} className="flex items-center gap-2">
<div <div

View File

@@ -208,7 +208,7 @@ const ChartTooltipContent = React.forwardRef<
!hideIndicator && ( !hideIndicator && (
<div <div
className={cn( 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", "h-2.5 w-2.5": indicator === "dot",
"w-1": indicator === "line", "w-1": indicator === "line",

View File

@@ -20,7 +20,7 @@ const PopoverContent = React.forwardRef<
sideOffset={sideOffset} sideOffset={sideOffset}
style={{backgroundColor: "var(--background)"}} style={{backgroundColor: "var(--background)"}}
className={cn( 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 className
)} )}
{...props} {...props}

View File

@@ -74,7 +74,7 @@ const SelectContent = React.forwardRef<
ref={ref} ref={ref}
style={{backgroundColor: "var(--background)"}} style={{backgroundColor: "var(--background)"}}
className={cn( 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" && 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", "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 className

View File

@@ -185,7 +185,7 @@ const Sidebar = React.forwardRef<
return ( return (
<div <div
className={cn( 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 className
)} )}
ref={ref} ref={ref}
@@ -202,7 +202,7 @@ const Sidebar = React.forwardRef<
<SheetContent <SheetContent
data-sidebar="sidebar" data-sidebar="sidebar"
data-mobile="true" 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={ style={
{ {
"--sidebar-width": SIDEBAR_WIDTH_MOBILE, "--sidebar-width": SIDEBAR_WIDTH_MOBILE,
@@ -232,24 +232,24 @@ const Sidebar = React.forwardRef<
{/* This is what handles the sidebar gap on desktop */} {/* This is what handles the sidebar gap on desktop */}
<div <div
className={cn( 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-[collapsible=offcanvas]:w-0",
"group-data-[side=right]:rotate-180", "group-data-[side=right]:rotate-180",
variant === "floating" || variant === "inset" variant === "floating" || variant === "inset"
? "group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4))]" ? "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 <div
className={cn( 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" side === "left"
? "left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]" ? "left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]"
: "right-0 group-data-[collapsible=offcanvas]:right-[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. // Adjust the padding for floating and inset variants.
variant === "floating" || variant === "inset" variant === "floating" || variant === "inset"
? "p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4)_+2px)]" ? "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 className
)} )}
{...props} {...props}
@@ -677,7 +677,7 @@ const SidebarMenuSkeleton = React.forwardRef<
/> />
)} )}
<Skeleton <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" data-sidebar="menu-skeleton-text"
style={ style={
{ {