Bug fixes
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useRef, useState } from "react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import { Plus, RotateCcw, Sparkles, X } from "lucide-react";
|
||||
import { gsap } from "@/lib/gsap";
|
||||
@@ -51,6 +51,8 @@ export default function WheelOfFortune() {
|
||||
const pointerRef = useRef<HTMLDivElement>(null);
|
||||
const rotationRef = useRef(0);
|
||||
const lastTickRef = useRef(-1);
|
||||
const optionInputRefs = useRef<Array<HTMLInputElement | null>>([]);
|
||||
const justAddedRef = useRef(false);
|
||||
|
||||
const { play } = useSound();
|
||||
const reduced = usePrefersReducedMotion();
|
||||
@@ -115,20 +117,49 @@ export default function WheelOfFortune() {
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
const activeTag = document.activeElement?.tagName.toLowerCase();
|
||||
if (activeTag === "input" || activeTag === "textarea") return;
|
||||
if (e.code === "Space") {
|
||||
e.preventDefault();
|
||||
spin();
|
||||
}
|
||||
};
|
||||
window.addEventListener("keydown", handleKeyDown);
|
||||
return () => window.removeEventListener("keydown", handleKeyDown);
|
||||
}, [spinning, n]);
|
||||
|
||||
const updateOption = (i: number, value: string) => {
|
||||
if (spinning) return;
|
||||
setOptions((opts) => opts.map((o, idx) => (idx === i ? value : o)));
|
||||
setWinner(null);
|
||||
};
|
||||
|
||||
const addOption = () => {
|
||||
if (n >= MAX_OPTIONS) return;
|
||||
if (spinning || n >= MAX_OPTIONS) return;
|
||||
setOptions((opts) => [...opts, ""]);
|
||||
setWinner(null);
|
||||
justAddedRef.current = true;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!justAddedRef.current) return;
|
||||
justAddedRef.current = false;
|
||||
const last = optionInputRefs.current[options.length - 1];
|
||||
last?.scrollIntoView({ block: "nearest", behavior: "smooth" });
|
||||
last?.focus();
|
||||
}, [options.length]);
|
||||
|
||||
const removeOption = (i: number) => {
|
||||
if (spinning || n <= MIN_OPTIONS) return;
|
||||
setOptions((opts) => opts.filter((_, idx) => idx !== i));
|
||||
setWinner(null);
|
||||
};
|
||||
|
||||
const removeOption = (i: number) => {
|
||||
if (n <= MIN_OPTIONS) return;
|
||||
setOptions((opts) => opts.filter((_, idx) => idx !== i));
|
||||
const resetOptions = () => {
|
||||
if (spinning) return;
|
||||
setOptions(DEFAULT_OPTIONS);
|
||||
setWinner(null);
|
||||
};
|
||||
|
||||
@@ -141,14 +172,19 @@ export default function WheelOfFortune() {
|
||||
<div className="relative w-full max-w-[400px]">
|
||||
{/* Zeiger */}
|
||||
<div
|
||||
ref={pointerRef}
|
||||
ref={pointerRef}
|
||||
aria-hidden
|
||||
className="absolute -top-1.5 left-1/2 z-10 h-9 w-7 -translate-x-1/2 bg-primary shadow-[0_6px_16px_rgb(108_92_231/0.5)]"
|
||||
className="absolute -top-1.5 left-1/2 z-10 h-9 w-7 -translate-x-1/2 bg-primary shadow-[0_6px_16px_rgb(13_122_87/0.35)]"
|
||||
style={{
|
||||
clipPath: "polygon(50% 100%, 0 0, 100% 0)",
|
||||
}}
|
||||
/>
|
||||
<svg viewBox="0 0 420 420" className="w-full drop-shadow-[0_20px_36px_rgb(23_23_28/0.14)]">
|
||||
<svg
|
||||
viewBox="0 0 420 420"
|
||||
role="img"
|
||||
aria-label="Glücksrad mit Zufalls-Optionen"
|
||||
className="w-full drop-shadow-[0_20px_36px_rgb(23_23_28/0.14)]"
|
||||
>
|
||||
{/* äußerer Ring */}
|
||||
<circle cx={C} cy={C} r={206} fill="#ffffff" stroke="#e9e9e4" />
|
||||
<g ref={wheelRef}>
|
||||
@@ -199,7 +235,7 @@ export default function WheelOfFortune() {
|
||||
</g>
|
||||
{/* Nabe */}
|
||||
<circle cx={C} cy={C} r={30} fill="#ffffff" stroke="#e9e9e4" strokeWidth={2} />
|
||||
<circle cx={C} cy={C} r={9} fill="#6c5ce7" />
|
||||
<circle cx={C} cy={C} r={9} fill="#0d7a57" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
@@ -234,18 +270,16 @@ export default function WheelOfFortune() {
|
||||
</div>
|
||||
|
||||
{/* Optionen-Editor */}
|
||||
<div className="rounded-3xl border border-line bg-cream/70 p-5">
|
||||
<div className="rounded-3xl border border-line bg-surface p-5 shadow-soft">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="text-sm font-semibold uppercase tracking-[0.16em] text-ink-soft">
|
||||
Optionen ({n}/{MAX_OPTIONS})
|
||||
</h3>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setOptions(DEFAULT_OPTIONS);
|
||||
setWinner(null);
|
||||
}}
|
||||
className="cursor-pointer text-xs font-medium text-ink-soft underline-offset-2 transition-colors hover:text-primary hover:underline"
|
||||
onClick={resetOptions}
|
||||
disabled={spinning}
|
||||
className="cursor-pointer text-xs font-medium text-ink-soft underline-offset-2 transition-colors hover:text-[#0d7a57] hover:underline disabled:cursor-not-allowed disabled:opacity-40"
|
||||
>
|
||||
Zurücksetzen
|
||||
</button>
|
||||
@@ -260,17 +294,21 @@ export default function WheelOfFortune() {
|
||||
style={{ background: PALETTE[i % PALETTE.length] }}
|
||||
/>
|
||||
<input
|
||||
ref={(el) => {
|
||||
optionInputRefs.current[i] = el;
|
||||
}}
|
||||
value={option}
|
||||
onChange={(e) => updateOption(i, e.target.value)}
|
||||
disabled={spinning}
|
||||
maxLength={24}
|
||||
placeholder={`Option ${i + 1}`}
|
||||
aria-label={`Option ${i + 1}`}
|
||||
className="w-full rounded-xl border border-line bg-surface px-3.5 py-2.5 text-sm font-medium outline-none transition focus:border-primary/60 focus:ring-2 focus:ring-primary/20"
|
||||
className="w-full rounded-xl border border-line bg-paper/50 px-3.5 py-2.5 text-sm font-medium outline-none transition focus:border-[#0d7a57] focus:bg-surface focus:ring-2 focus:ring-[#0d7a57]/20 disabled:cursor-not-allowed disabled:opacity-60"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => removeOption(i)}
|
||||
disabled={n <= MIN_OPTIONS}
|
||||
disabled={spinning || n <= MIN_OPTIONS}
|
||||
aria-label={`Option ${i + 1} entfernen`}
|
||||
className="flex h-9 w-9 shrink-0 cursor-pointer items-center justify-center rounded-xl text-ink-soft transition-colors hover:bg-clay-soft hover:text-clay disabled:cursor-not-allowed disabled:opacity-30"
|
||||
>
|
||||
@@ -283,8 +321,8 @@ export default function WheelOfFortune() {
|
||||
<button
|
||||
type="button"
|
||||
onClick={addOption}
|
||||
disabled={n >= MAX_OPTIONS}
|
||||
className="mt-3 inline-flex w-full cursor-pointer items-center justify-center gap-2 rounded-xl border-2 border-dashed border-line px-4 py-2.5 text-sm font-semibold text-ink-soft transition-colors hover:border-primary/50 hover:text-primary disabled:cursor-not-allowed disabled:opacity-40"
|
||||
disabled={spinning || n >= MAX_OPTIONS}
|
||||
className="mt-3 inline-flex w-full cursor-pointer items-center justify-center gap-2 rounded-xl border-2 border-dashed border-line px-4 py-2.5 text-sm font-semibold text-ink-soft transition-colors hover:border-[#0d7a57] hover:bg-[#e6f7f0]/50 hover:text-[#0d7a57] disabled:cursor-not-allowed disabled:opacity-40"
|
||||
>
|
||||
<Plus className="h-4 w-4" />
|
||||
Option hinzufügen
|
||||
|
||||
Reference in New Issue
Block a user