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" />
</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

View File

@@ -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">

View File

@@ -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" />
)}

View File

@@ -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>
)}

View File

@@ -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

View File

@@ -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

View File

@@ -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",

View File

@@ -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}

View File

@@ -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

View File

@@ -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={
{