Untitled
unknown
plain_text
23 days ago
8.8 kB
3
Indexable
// app/[clientId]/auswahl/[selectionId]/page.tsx "use client" import { useEffect, useState, useMemo, useRef } from "react" import { useRouter } from "next/navigation" import { notFound } from "next/navigation" import { LoginForm } from "@/components/login-form" import { Gallery } from "@/components/gallery" import { WelcomeHero } from "@/components/welcome-hero" import { getClientConfig } from "@/lib/client-config" import { BackgroundBeams } from "@/components/ui/background-beams" import Cursor from '@/components/cursor/cursor'; import { FooterWithContact } from "@/components/footer-with-contact" import { Share2 } from "lucide-react" import { useInView } from "framer-motion" import { ClientThemeProvider } from "@/components/client-theme-provider" // Import our new component interface Selection { id: string; clientId: string; selectionId: string; data: { favorites: Record<string, number[]>; requirePassword: boolean; password: string; createdAt: string; parentClientId: string; }; createdAt: string; } export default function SelectionPage({ params }: { params: { clientId: string; selectionId: string } }) { const [isAuthenticated, setIsAuthenticated] = useState(false) const [isLoading, setIsLoading] = useState(true) const [selection, setSelection] = useState<Selection | null>(null) const [error, setError] = useState<string | null>(null) const [filteredSections, setFilteredSections] = useState([]) const footerRef = useRef(null) const inView = useInView(footerRef, { amount: 0.1 }) const router = useRouter() // Use useMemo to cache the client config and prevent recalculation on every render const clientConfig = useMemo(() => { const config = selection?.data?.parentClientId ? getClientConfig(selection.data.parentClientId) : getClientConfig(params.clientId); if (!config && !isLoading) { return null; } return config || getClientConfig(params.clientId); }, [params.clientId, selection?.data?.parentClientId, isLoading]); // Catch null clientConfig after loading is complete useEffect(() => { if (!isLoading && !clientConfig) { notFound(); } }, [clientConfig, isLoading]); // Fetch the selection data useEffect(() => { let isMounted = true; // For preventing state updates after component unmount const fetchSelection = async () => { try { const response = await fetch(`/api/selections/${params.clientId}/${params.selectionId}`); if (!response.ok) { if (response.status === 404) { notFound(); } throw new Error(`HTTP error! status: ${response.status}`); } const data = await response.json(); if (isMounted) { setSelection(data); // If no password is required, auto-authenticate if (data.data && !data.data.requirePassword) { setIsAuthenticated(true); } // Filter the sections based on the favorites if (data.data && data.data.favorites && clientConfig) { const favorites = data.data.favorites; const filtered = clientConfig.sections .map(section => { // If no favorites for this section, skip it if (!favorites[section.title] || favorites[section.title].length === 0) { return null; } // Include only favorited images return { ...section, images: section.images.filter((_, index) => favorites[section.title].includes(index) ) }; }) .filter(Boolean); // Remove null sections setFilteredSections(filtered); } } } catch (error) { console.error("Error fetching selection:", error); if (isMounted) { setError("Die Auswahl konnte nicht geladen werden."); } } finally { if (isMounted) { setIsLoading(false); } } }; fetchSelection(); return () => { isMounted = false; // Cleanup to prevent setting state after unmount }; }, [params.clientId, params.selectionId, clientConfig]); // clientConfig dependency is safe now with useMemo // Handle authentication via cookies or URL hash useEffect(() => { if (!selection) return; // Check for hash-based password const hash = window.location.hash; if (hash.startsWith("#password=")) { const password = hash.slice(10); if (password === selection.data.password) { document.cookie = `auth-selection-${params.clientId}-${params.selectionId}=true; path=/`; setIsAuthenticated(true); // Optionally remove the hash from URL window.history.replaceState(null, '', window.location.pathname); } } // Check for existing authentication const authCookie = document.cookie .split("; ") .find((row) => row.startsWith(`auth-selection-${params.clientId}-${params.selectionId}=`)); setIsAuthenticated(authCookie?.split("=")[1] === "true" || !selection.data.requirePassword); }, [selection, params.clientId, params.selectionId]); if (isLoading) { return null; } if (error) { return ( <main className="flex min-h-screen flex-col items-center justify-center p-4 bg-black"> <div className="text-center"> <h1 className="text-2xl font-bold mb-4">Fehler</h1> <p className="text-muted-foreground">{error}</p> </div> </main> ); } if (!clientConfig) { return null; } // After authentication, render the filtered layout const filteredClientConfig = { ...clientConfig, }; // Wrap everything in the ClientThemeProvider return ( <ClientThemeProvider clientConfig={clientConfig}> {!isAuthenticated && selection?.data.requirePassword ? ( <main className="relative flex min-h-screen flex-col items-center justify-center p-4 bg-black"> <BackgroundBeams className="opacity-100" /> <div className="relative z-10"> <div className="mb-8 flex flex-col items-center"> <div className="h-12 w-12 rounded-full bg-indigo-600/20 flex items-center justify-center mb-4"> <Share2 className="h-6 w-6 text-indigo-500" /> </div> <h1 className="text-2xl font-bold text-center mb-1">Geteilte Auswahl</h1> <p className="text-muted-foreground text-center"> Diese Auswahl wurde mit Ihnen geteilt.<br />Bitte geben Sie das Passwort ein, um fortzufahren. </p> </div> <LoginForm clientId={`${params.clientId}/auswahl/${params.selectionId}`} customPassword={selection?.data.password} onSuccess={() => setIsAuthenticated(true)} /> </div> <Cursor /> </main> ) : ( <> <WelcomeHero config={filteredClientConfig} /> <main className="container mx-auto min-h-screen flex flex-col"> <div className="space-y-8 px-4 py-8 md:space-y-12 md:py-12 flex-grow"> <div className="bg-muted/20 p-4 rounded-lg flex items-center gap-4"> <div className="h-10 w-10 rounded-full bg-indigo-600/20 flex items-center justify-center"> <Share2 className="h-5 w-5 text-indigo-500" /> </div> <div> <h2 className="text-lg font-semibold">Geteilte Auswahl</h2> <p className="text-sm text-muted-foreground"> Diese Auswahl wurde speziell für Sie zusammengestellt. </p> </div> </div> <Gallery sections={filteredSections} clientConfig={filteredClientConfig} isSelectionView={true} // IMPORTANT: Changed to false to enable toolbar and all features /> {/* Invisible ref element to detect when we're at the bottom */} <div ref={footerRef} className="h-1" /> </div> <FooterWithContact clientConfig={clientConfig} /> </main> <Cursor /> </> )} </ClientThemeProvider> ); }
Editor is loading...
Leave a Comment