mirror of
https://gitlab.w-hs.de/an14051/backerup-website.git
synced 2026-07-27 17:35:29 +00:00
feat: implement user authentication and admin management
- Add user management API endpoints for listing and creating users. - Implement login and logout functionality with rate limiting. - Create UI components for login form, logout button, and animated elements. - Add session management with JWT and secure cookie handling. - Seed initial admin user if no users exist. - Introduce utility functions for password hashing and user authentication. - Set up proxy middleware for route protection and security headers. - Create a JSON file for user data storage. - Add links configuration for external resources.
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
|
||||
interface CountUpProps {
|
||||
/** Target number to count up to */
|
||||
to: number;
|
||||
/** String appended after the number (e.g. "%", " MB", "+") */
|
||||
suffix?: string;
|
||||
/** String prepended before the number */
|
||||
prefix?: string;
|
||||
/** Animation duration in ms */
|
||||
duration?: number;
|
||||
/** Decimal places to show */
|
||||
decimals?: number;
|
||||
}
|
||||
|
||||
export default function CountUp({
|
||||
to,
|
||||
suffix = "",
|
||||
prefix = "",
|
||||
duration = 1800,
|
||||
decimals = 0,
|
||||
}: CountUpProps) {
|
||||
const ref = useRef<HTMLSpanElement>(null);
|
||||
const [current, setCurrent] = useState(0);
|
||||
const [started, setStarted] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const el = ref.current;
|
||||
if (!el) return;
|
||||
|
||||
const observer = new IntersectionObserver(
|
||||
([entry]) => {
|
||||
if (entry.isIntersecting && !started) {
|
||||
setStarted(true);
|
||||
observer.disconnect();
|
||||
}
|
||||
},
|
||||
{ threshold: 0.5 },
|
||||
);
|
||||
|
||||
observer.observe(el);
|
||||
return () => observer.disconnect();
|
||||
}, [started]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!started) return;
|
||||
|
||||
// Skip animation if user prefers reduced motion
|
||||
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
|
||||
setCurrent(to);
|
||||
return;
|
||||
}
|
||||
|
||||
const startTime = performance.now();
|
||||
let raf: number;
|
||||
|
||||
function tick(now: number) {
|
||||
const elapsed = now - startTime;
|
||||
const progress = Math.min(elapsed / duration, 1);
|
||||
// Ease-out quart — fast start, smooth finish
|
||||
const eased = 1 - Math.pow(1 - progress, 4);
|
||||
setCurrent(eased * to);
|
||||
if (progress < 1) raf = requestAnimationFrame(tick);
|
||||
}
|
||||
|
||||
raf = requestAnimationFrame(tick);
|
||||
return () => cancelAnimationFrame(raf);
|
||||
}, [started, to, duration]);
|
||||
|
||||
const display =
|
||||
decimals > 0 ? current.toFixed(decimals) : Math.floor(current).toString();
|
||||
|
||||
return (
|
||||
<span ref={ref}>
|
||||
{prefix}
|
||||
{display}
|
||||
{suffix}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user