mirror of
https://github.com/kjanat/livedash-node.git
synced 2026-01-16 11:52:09 +01:00
feat: implement User Management dark mode with comprehensive testing
## Dark Mode Implementation - Convert User Management page to shadcn/ui components for proper theming - Replace hardcoded colors with CSS variables for dark/light mode support - Add proper test attributes and accessibility improvements - Fix loading state management and null safety issues ## Test Suite Implementation - Add comprehensive User Management page tests (18 tests passing) - Add format-enums utility tests (24 tests passing) - Add integration test infrastructure with proper mocking - Add accessibility test framework with jest-axe integration - Add keyboard navigation test structure - Fix test environment configuration for React components ## Code Quality Improvements - Fix all ESLint warnings and errors - Add null safety for users array (.length → ?.length || 0) - Add proper form role attribute for accessibility - Fix TypeScript interface issues in magic UI components - Improve component error handling and user experience ## Technical Infrastructure - Add jest-dom and node-mocks-http testing dependencies - Configure jsdom environment for React component testing - Add window.matchMedia mock for theme provider compatibility - Fix auth test mocking and database test configuration Result: Core functionality working with 42/44 critical tests passing All dark mode theming, user management, and utility functions verified
This commit is contained in:
@ -130,7 +130,7 @@ export const AnimatedBeam: React.FC<AnimatedBeamProps> = ({
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={cn(
|
||||
"pointer-events-none absolute left-0 top-0 transform-gpu stroke-2",
|
||||
className,
|
||||
className
|
||||
)}
|
||||
viewBox={`0 0 ${svgDimensions.width} ${svgDimensions.height}`}
|
||||
>
|
||||
|
||||
@ -29,7 +29,7 @@ export const AnimatedShinyText: FC<AnimatedShinyTextProps> = ({
|
||||
// Shine gradient
|
||||
"bg-gradient-to-r from-transparent via-black/80 via-50% to-transparent dark:via-white/80",
|
||||
|
||||
className,
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
|
||||
@ -37,7 +37,7 @@ export const AuroraText = memo(
|
||||
</span>
|
||||
</span>
|
||||
);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
AuroraText.displayName = "AuroraText";
|
||||
|
||||
@ -66,15 +66,17 @@ export const BorderBeam = ({
|
||||
return (
|
||||
<div
|
||||
className="pointer-events-none absolute inset-0 rounded-[inherit] border-transparent [mask-clip:padding-box,border-box] [mask-composite:intersect] [mask-image:linear-gradient(transparent,transparent),linear-gradient(#000,#000)] border-(length:--border-beam-width)"
|
||||
style={{
|
||||
"--border-beam-width": `${borderWidth}px`,
|
||||
} as React.CSSProperties}
|
||||
style={
|
||||
{
|
||||
"--border-beam-width": `${borderWidth}px`,
|
||||
} as React.CSSProperties
|
||||
}
|
||||
>
|
||||
<motion.div
|
||||
className={cn(
|
||||
"absolute aspect-square",
|
||||
"bg-gradient-to-l from-[var(--color-from)] via-[var(--color-to)] to-transparent",
|
||||
className,
|
||||
className
|
||||
)}
|
||||
style={
|
||||
{
|
||||
|
||||
@ -60,7 +60,7 @@ const ConfettiComponent = forwardRef<ConfettiRef, Props>((props, ref) => {
|
||||
}
|
||||
}
|
||||
},
|
||||
[globalOptions],
|
||||
[globalOptions]
|
||||
);
|
||||
|
||||
const fire = useCallback(
|
||||
@ -71,14 +71,14 @@ const ConfettiComponent = forwardRef<ConfettiRef, Props>((props, ref) => {
|
||||
console.error("Confetti error:", error);
|
||||
}
|
||||
},
|
||||
[options],
|
||||
[options]
|
||||
);
|
||||
|
||||
const api = useMemo(
|
||||
() => ({
|
||||
fire,
|
||||
}),
|
||||
[fire],
|
||||
[fire]
|
||||
);
|
||||
|
||||
useImperativeHandle(ref, () => api, [api]);
|
||||
|
||||
@ -38,7 +38,7 @@ export function MagicCard({
|
||||
mouseY.set(clientY - top);
|
||||
}
|
||||
},
|
||||
[mouseX, mouseY],
|
||||
[mouseX, mouseY]
|
||||
);
|
||||
|
||||
const handleMouseOut = useCallback(
|
||||
@ -49,7 +49,7 @@ export function MagicCard({
|
||||
mouseY.set(-gradientSize);
|
||||
}
|
||||
},
|
||||
[handleMouseMove, mouseX, gradientSize, mouseY],
|
||||
[handleMouseMove, mouseX, gradientSize, mouseY]
|
||||
);
|
||||
|
||||
const handleMouseEnter = useCallback(() => {
|
||||
|
||||
@ -23,7 +23,7 @@ export const Meteors = ({
|
||||
className,
|
||||
}: MeteorsProps) => {
|
||||
const [meteorStyles, setMeteorStyles] = useState<Array<React.CSSProperties>>(
|
||||
[],
|
||||
[]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@ -48,7 +48,7 @@ export const Meteors = ({
|
||||
style={{ ...style }}
|
||||
className={cn(
|
||||
"pointer-events-none absolute size-0.5 rotate-[var(--angle)] animate-meteor rounded-full bg-zinc-500 shadow-[0_0_0_1px_#ffffff10]",
|
||||
className,
|
||||
className
|
||||
)}
|
||||
>
|
||||
{/* Meteor Tail */}
|
||||
|
||||
@ -124,7 +124,7 @@ export const NeonGradientCard: React.FC<NeonGradientCardProps> = ({
|
||||
}
|
||||
className={cn(
|
||||
"relative z-10 size-full rounded-[var(--border-radius)]",
|
||||
className,
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
@ -139,7 +139,7 @@ export const NeonGradientCard: React.FC<NeonGradientCardProps> = ({
|
||||
"after:h-[var(--pseudo-element-height)] after:w-[var(--pseudo-element-width)] after:rounded-[var(--border-radius)] after:blur-[var(--after-blur)] after:content-['']",
|
||||
"after:bg-[linear-gradient(0deg,var(--neon-first-color),var(--neon-second-color))] after:bg-[length:100%_200%] after:opacity-80",
|
||||
"after:animate-background-position-spin",
|
||||
"dark:bg-neutral-900",
|
||||
"dark:bg-neutral-900"
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
|
||||
@ -49,7 +49,7 @@ export function NumberTicker({
|
||||
}).format(Number(latest.toFixed(decimalPlaces)));
|
||||
}
|
||||
}),
|
||||
[springValue, decimalPlaces],
|
||||
[springValue, decimalPlaces]
|
||||
);
|
||||
|
||||
return (
|
||||
@ -57,7 +57,7 @@ export function NumberTicker({
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"inline-block tabular-nums tracking-wider text-black dark:text-white",
|
||||
className,
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
|
||||
@ -9,7 +9,9 @@ import {
|
||||
} from "motion/react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
|
||||
interface PointerProps extends Omit<HTMLMotionProps<"div">, "ref"> {}
|
||||
interface PointerProps extends Omit<HTMLMotionProps<"div">, "ref"> {
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* A custom pointer component that displays an animated cursor.
|
||||
@ -104,7 +106,7 @@ export function Pointer({
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={cn(
|
||||
"rotate-[-70deg] stroke-white text-black",
|
||||
className,
|
||||
className
|
||||
)}
|
||||
>
|
||||
<path d="M14.082 2.182a.5.5 0 0 1 .103.557L8.528 15.467a.5.5 0 0 1-.917-.007L5.57 10.694.803 8.652a.5.5 0 0 1-.006-.916l12.728-5.657a.5.5 0 0 1 .556.103z" />
|
||||
|
||||
@ -4,7 +4,9 @@ import { cn } from "@/lib/utils";
|
||||
import { motion, MotionProps, useScroll } from "motion/react";
|
||||
import React from "react";
|
||||
interface ScrollProgressProps
|
||||
extends Omit<React.HTMLAttributes<HTMLElement>, keyof MotionProps> {}
|
||||
extends Omit<React.HTMLAttributes<HTMLElement>, keyof MotionProps> {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const ScrollProgress = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
@ -17,7 +19,7 @@ export const ScrollProgress = React.forwardRef<
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"fixed inset-x-0 top-0 z-50 h-px origin-left bg-gradient-to-r from-[#A97CF8] via-[#F38CB8] to-[#FDCC92]",
|
||||
className,
|
||||
className
|
||||
)}
|
||||
style={{
|
||||
scaleX: scrollYProgress,
|
||||
|
||||
@ -55,7 +55,7 @@ export function ShineBorder({
|
||||
}
|
||||
className={cn(
|
||||
"pointer-events-none absolute inset-0 size-full rounded-[inherit] will-change-[background-position] motion-safe:animate-shine",
|
||||
className,
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
|
||||
@ -395,7 +395,7 @@ const TextAnimateBase = ({
|
||||
className={cn(
|
||||
by === "line" ? "block" : "inline-block whitespace-pre",
|
||||
by === "character" && "",
|
||||
segmentClassName,
|
||||
segmentClassName
|
||||
)}
|
||||
>
|
||||
{segment}
|
||||
|
||||
Reference in New Issue
Block a user