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,86 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
|
||||
type Variant =
|
||||
| "fade-up"
|
||||
| "fade-down"
|
||||
| "fade-in"
|
||||
| "fade-left"
|
||||
| "fade-right"
|
||||
| "scale-up";
|
||||
|
||||
interface AnimateInProps {
|
||||
children: React.ReactNode;
|
||||
variant?: Variant;
|
||||
/** Delay (ms) after the element enters the viewport before animating */
|
||||
delay?: number;
|
||||
/** Animation duration in ms */
|
||||
duration?: number;
|
||||
/** Extra classes to put on the wrapper div */
|
||||
className?: string;
|
||||
/** If true (default) the animation only fires once */
|
||||
once?: boolean;
|
||||
}
|
||||
|
||||
const transforms: Record<Variant, { hidden: string; visible: string }> = {
|
||||
"fade-up": { hidden: "opacity-0 translate-y-10", visible: "opacity-100 translate-y-0" },
|
||||
"fade-down": { hidden: "opacity-0 -translate-y-10", visible: "opacity-100 translate-y-0" },
|
||||
"fade-in": { hidden: "opacity-0", visible: "opacity-100" },
|
||||
"fade-left": { hidden: "opacity-0 -translate-x-10", visible: "opacity-100 translate-x-0" },
|
||||
"fade-right": { hidden: "opacity-0 translate-x-10", visible: "opacity-100 translate-x-0" },
|
||||
"scale-up": { hidden: "opacity-0 scale-95", visible: "opacity-100 scale-100" },
|
||||
};
|
||||
|
||||
export default function AnimateIn({
|
||||
children,
|
||||
variant = "fade-up",
|
||||
delay = 0,
|
||||
duration = 650,
|
||||
className = "",
|
||||
once = true,
|
||||
}: AnimateInProps) {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const [visible, setVisible] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
// Respect the user's motion preference
|
||||
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
|
||||
setVisible(true);
|
||||
return;
|
||||
}
|
||||
|
||||
const el = ref.current;
|
||||
if (!el) return;
|
||||
|
||||
const observer = new IntersectionObserver(
|
||||
([entry]) => {
|
||||
if (entry.isIntersecting) {
|
||||
setVisible(true);
|
||||
if (once) observer.disconnect();
|
||||
} else if (!once) {
|
||||
setVisible(false);
|
||||
}
|
||||
},
|
||||
{ threshold: 0.08, rootMargin: "0px 0px -60px 0px" },
|
||||
);
|
||||
|
||||
observer.observe(el);
|
||||
return () => observer.disconnect();
|
||||
}, [once]);
|
||||
|
||||
const { hidden, visible: vis } = transforms[variant];
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={`${visible ? vis : hidden} ${className}`}
|
||||
style={{
|
||||
transition: `opacity ${duration}ms cubic-bezier(0.22,1,0.36,1) ${delay}ms, transform ${duration}ms cubic-bezier(0.22,1,0.36,1) ${delay}ms`,
|
||||
willChange: "opacity, transform",
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user