mirror of
https://github.com/kjanat/livedash-node.git
synced 2026-01-16 10:12:09 +01:00
Major code quality overhaul addressing 58% of all linting issues: • Type Safety Improvements: - Replace all any types with proper TypeScript interfaces - Fix Map component shadowing (renamed to CountryMap) - Add comprehensive custom error classes system - Enhance API route type safety • Accessibility Enhancements: - Add explicit button types to all interactive elements - Implement useId() hooks for form element accessibility - Add SVG title attributes for screen readers - Fix static element interactions with keyboard handlers • React Best Practices: - Resolve exhaustive dependencies warnings with useCallback - Extract nested component definitions to top level - Fix array index keys with proper unique identifiers - Improve component organization and prop typing • Code Organization: - Automatic import organization and type import optimization - Fix unused function parameters and variables - Enhanced error handling with structured error responses - Improve component reusability and maintainability Results: 248 → 104 total issues (58% reduction) - Fixed all critical type safety and security issues - Enhanced accessibility compliance significantly - Improved code maintainability and performance
151 lines
3.3 KiB
TypeScript
151 lines
3.3 KiB
TypeScript
"use client";
|
|
|
|
import type {
|
|
GlobalOptions as ConfettiGlobalOptions,
|
|
CreateTypes as ConfettiInstance,
|
|
Options as ConfettiOptions,
|
|
} from "canvas-confetti";
|
|
import confetti from "canvas-confetti";
|
|
import type React from "react";
|
|
import type { ReactNode } from "react";
|
|
import {
|
|
createContext,
|
|
forwardRef,
|
|
useCallback,
|
|
useEffect,
|
|
useImperativeHandle,
|
|
useMemo,
|
|
useRef,
|
|
} from "react";
|
|
|
|
import { Button, type ButtonProps } from "@/components/ui/button";
|
|
|
|
type Api = {
|
|
fire: (options?: ConfettiOptions) => void;
|
|
};
|
|
|
|
type Props = React.ComponentPropsWithRef<"canvas"> & {
|
|
options?: ConfettiOptions;
|
|
globalOptions?: ConfettiGlobalOptions;
|
|
manualstart?: boolean;
|
|
children?: ReactNode;
|
|
};
|
|
|
|
export type ConfettiRef = Api | null;
|
|
|
|
const ConfettiContext = createContext<Api>({} as Api);
|
|
|
|
// Define component first
|
|
const ConfettiComponent = forwardRef<ConfettiRef, Props>((props, ref) => {
|
|
const {
|
|
options,
|
|
globalOptions = { resize: true, useWorker: true },
|
|
manualstart = false,
|
|
children,
|
|
...rest
|
|
} = props;
|
|
const instanceRef = useRef<ConfettiInstance | null>(null);
|
|
|
|
const canvasRef = useCallback(
|
|
(node: HTMLCanvasElement) => {
|
|
if (node !== null) {
|
|
if (instanceRef.current) return;
|
|
instanceRef.current = confetti.create(node, {
|
|
...globalOptions,
|
|
resize: true,
|
|
});
|
|
} else {
|
|
if (instanceRef.current) {
|
|
instanceRef.current.reset();
|
|
instanceRef.current = null;
|
|
}
|
|
}
|
|
},
|
|
[globalOptions]
|
|
);
|
|
|
|
const fire = useCallback(
|
|
async (opts = {}) => {
|
|
try {
|
|
await instanceRef.current?.({ ...options, ...opts });
|
|
} catch (error) {
|
|
console.error("Confetti error:", error);
|
|
}
|
|
},
|
|
[options]
|
|
);
|
|
|
|
const api = useMemo(
|
|
() => ({
|
|
fire,
|
|
}),
|
|
[fire]
|
|
);
|
|
|
|
useImperativeHandle(ref, () => api, [api]);
|
|
|
|
useEffect(() => {
|
|
if (!manualstart) {
|
|
(async () => {
|
|
try {
|
|
await fire();
|
|
} catch (error) {
|
|
console.error("Confetti effect error:", error);
|
|
}
|
|
})();
|
|
}
|
|
}, [manualstart, fire]);
|
|
|
|
return (
|
|
<ConfettiContext.Provider value={api}>
|
|
<canvas ref={canvasRef} {...rest} />
|
|
{children}
|
|
</ConfettiContext.Provider>
|
|
);
|
|
});
|
|
|
|
// Set display name immediately
|
|
ConfettiComponent.displayName = "Confetti";
|
|
|
|
// Export as Confetti
|
|
export const Confetti = ConfettiComponent;
|
|
|
|
interface ConfettiButtonProps extends ButtonProps {
|
|
options?: ConfettiOptions &
|
|
ConfettiGlobalOptions & { canvas?: HTMLCanvasElement };
|
|
children?: React.ReactNode;
|
|
}
|
|
|
|
const ConfettiButtonComponent = ({
|
|
options,
|
|
children,
|
|
...props
|
|
}: ConfettiButtonProps) => {
|
|
const handleClick = async (event: React.MouseEvent<HTMLButtonElement>) => {
|
|
try {
|
|
const rect = event.currentTarget.getBoundingClientRect();
|
|
const x = rect.left + rect.width / 2;
|
|
const y = rect.top + rect.height / 2;
|
|
await confetti({
|
|
...options,
|
|
origin: {
|
|
x: x / window.innerWidth,
|
|
y: y / window.innerHeight,
|
|
},
|
|
});
|
|
} catch (error) {
|
|
console.error("Confetti button error:", error);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<Button onClick={handleClick} {...props}>
|
|
{children}
|
|
</Button>
|
|
);
|
|
};
|
|
|
|
ConfettiButtonComponent.displayName = "ConfettiButton";
|
|
|
|
export const ConfettiButton = ConfettiButtonComponent;
|