refactor: comprehensive frontend review — consistency, a11y, code quality

- Replace event dispatchers with BookingContext (Hero, Header, FloatingContact)
- Add focus trap hook for modals (SignupModal, NewsModal)
- Extract shared components: CollapsibleSection, ConfirmDialog, PriceField, AdminSkeleton
- Add delete confirmation dialog to ArrayEditor
- Replace hardcoded colors (#050505, #0a0a0a, #c9a96e, #2ecc71) with theme tokens
- Add CSS variables --color-surface-deep/dark for consistent dark surfaces
- Improve contrast: muted text neutral-500 → neutral-400 in dark mode
- Fix modal z-index hierarchy (modals z-60, header z-50, floats z-40)
- Consolidate duplicate formatDate → shared formatting.ts
- Add useMemo to TeamProfile groupMap computation
- Fix typography: responsive price text in Pricing section
- Add ARIA labels/expanded to FAQ, OpenDay, ArrayEditor grip handles
- Hide number input spinners globally
- Reorder admin sidebar: Dashboard → SEO → Bookings → site section order
- Use shared PriceField in Open Day editor
- Fix schedule grid first time slot (09:00) clipped by container
- Fix pre-existing type errors (bookings, hero, db interfaces)
This commit is contained in:
2026-03-26 19:45:37 +03:00
parent ec08f8e8d5
commit 76307e298b
32 changed files with 613 additions and 319 deletions
+6 -4
View File
@@ -5,6 +5,7 @@ import { Button } from "@/components/ui/Button";
import { FloatingHearts } from "@/components/ui/FloatingHearts";
import { HeroLogo } from "@/components/ui/HeroLogo";
import type { SiteContent } from "@/types/content";
import { useBooking } from "@/contexts/BookingContext";
const DEFAULT_VIDEOS = ["/video/ira.mp4", "/video/nadezda.mp4", "/video/nastya-2.mp4"];
@@ -13,6 +14,7 @@ interface HeroProps {
}
export function Hero({ data: hero }: HeroProps) {
const { openBooking } = useBooking();
const sectionRef = useRef<HTMLElement>(null);
const scrolledRef = useRef(false);
const overlayRef = useRef<HTMLDivElement>(null);
@@ -78,7 +80,7 @@ export function Hero({ data: hero }: HeroProps) {
}, [scrollToNext]);
return (
<section id="hero" ref={sectionRef} className="relative flex min-h-svh items-center justify-center overflow-hidden bg-[#050505]">
<section id="hero" ref={sectionRef} className="relative flex min-h-svh items-center justify-center overflow-hidden bg-neutral-950">
{/* Videos render only after hydration to avoid SSR mismatch */}
{mounted && (
<>
@@ -140,7 +142,7 @@ export function Hero({ data: hero }: HeroProps) {
{/* Loading overlay — covers videos but not content */}
<div
ref={overlayRef}
className="absolute inset-0 z-[5] bg-[#050505] pointer-events-none transition-opacity duration-1000"
className="absolute inset-0 z-[5] bg-neutral-950 pointer-events-none transition-opacity duration-1000"
/>
{/* Floating hearts */}
@@ -162,12 +164,12 @@ export function Hero({ data: hero }: HeroProps) {
<span className="gradient-text">{hero.headline}</span>
</h1>
<p className="hero-subtitle mx-auto mt-6 max-w-lg text-lg text-[#b8a080] sm:text-xl">
<p className="hero-subtitle mx-auto mt-6 max-w-lg text-lg text-gold/70 sm:text-xl">
{hero.subheadline}
</p>
<div className="hero-cta mt-12">
<Button size="lg" onClick={() => window.dispatchEvent(new Event("open-booking"))}>
<Button size="lg" onClick={openBooking}>
{hero.ctaText}
</Button>
</div>