Untitled

 avatar
unknown
plain_text
4 months ago
13 kB
5
Indexable
"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 { ScrollToolbar } from "@/components/ScrollToolbar"
import { Share2 } from "lucide-react"
import { useInView } from "framer-motion"

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([])
  
  // Toolbar related states
  const [gridSize, setGridSize] = useState<number>(3)
  const [favorites, setFavorites] = useState<{[key: string]: boolean}>({})
  const [showFavoritesOnly, setShowFavoritesOnly] = useState<boolean>(false)
  const [commentCounts, setCommentCounts] = useState<{[key: string]: number}>({})
  const [showCommentsOnly, setShowCommentsOnly] = useState<boolean>(false)
  
  // For scroll toolbar visibility
  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;
            
            // Initialize favorites state by marking selected images as favorites
            const favoritesState = {};
            Object.entries(favorites).forEach(([sectionTitle, indices]) => {
              indices.forEach(index => {
                favoritesState[`${sectionTitle}-${index}`] = true;
              });
            });
            setFavorites(favoritesState);
            
            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]);
  
  // Fetch comment counts
  useEffect(() => {
    const fetchCommentCounts = async () => {
      try {
        const response = await fetch(`/api/comments?clientId=${encodeURIComponent(params.clientId)}&index=true`);
        if (response.ok) {
          const counts = await response.json();
          setCommentCounts(counts);
        }
      } catch (error) {
        console.error('Error fetching comment counts:', error);
      }
    };

    fetchCommentCounts();
  }, [params.clientId]);

  // Define handler functions
  const handleDownloadAllFavorites = async () => {
    // Since these are all already favorites, we can just download all visible images
    const downloadableImages = [];
    
    filteredSections.forEach(section => {
      section.images.forEach(image => {
        downloadableImages.push({
          src: image.src,
          filename: image.src.split('/').pop() || `image-${Math.random().toString(36).substring(7)}.jpg`
        });
      });
    });
    
    if (downloadableImages.length === 0) return;
    
    // This implementation matches the one from the main gallery component
    for (const image of downloadableImages) {
      try {
        await new Promise(resolve => setTimeout(resolve, 300));
        
        const response = await fetch(image.src, {
          method: 'GET',
          headers: {
            'Cache-Control': 'no-cache'
          }
        });
        
        if (!response.ok) continue;
        
        const blob = await response.blob();
        if (blob.size === 0) continue;
        
        const url = window.URL.createObjectURL(blob);
        const link = document.createElement('a');
        link.href = url;
        link.download = image.filename;
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);
        window.URL.revokeObjectURL(url);
      } catch (error) {
        console.error(`Failed to download ${image.filename}:`, error);
      }
    }
  };

  const handleResetAllFavorites = () => {
    // This is a view-only operation in a selection, so we'll just reset the filtering
    setShowFavoritesOnly(false);
    setShowCommentsOnly(false);
  };

  const handleCreateSharedLink = async (password: string, requirePassword: boolean) => {
    // This functionality is not needed in selection view since it's already a shared link
    // Just return the current URL
    return window.location.href;
  };
  
  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;
  }
  
  if (!isAuthenticated && selection?.data.requirePassword) {
    return (
      <main className="relative flex min-h-screen flex-col items-center justify-center p-4 bg-black">
        <BackgroundBeams className="opacity-70" />
        
        <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. 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>
    );
  }
  
  // After authentication, render the filtered layout
  const filteredClientConfig = {
    ...clientConfig,
    title: `${clientConfig.title} - Auswahl`,
  };
  
  return (
    <>
      <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}
            customGridSize={gridSize}
            customShowFavoritesOnly={showFavoritesOnly}
            customShowCommentsOnly={showCommentsOnly}
            customFavorites={favorites}
            customCommentCounts={commentCounts}
          />
          
          <ScrollToolbar
            gridSize={gridSize}
            setGridSize={setGridSize}
            sections={filteredSections}
            inView={inView}
            clientConfig={filteredClientConfig}
            favorites={favorites}
            showFavoritesOnly={showFavoritesOnly}
            setShowFavoritesOnly={setShowFavoritesOnly}
            commentCounts={commentCounts}
            showCommentsOnly={showCommentsOnly}
            setShowCommentsOnly={setShowCommentsOnly}
            handleDownloadAllFavorites={handleDownloadAllFavorites}
            handleResetAllFavorites={handleResetAllFavorites}
            handleCreateSharedLink={handleCreateSharedLink}
            clientId={params.clientId}
          />
          
          {/* Invisible ref element to detect when we're at the bottom */}
          <div ref={footerRef} className="h-1" />
        </div>
        <FooterWithContact clientConfig={clientConfig} />
      </main>
      <Cursor />
    </>
  );
}
Editor is loading...
Leave a Comment