consolidation of auth mechanism
This commit is contained in:
		
							parent
							
								
									32fca8a06f
								
							
						
					
					
						commit
						72bcc04309
					
				@ -19,7 +19,13 @@ const { title, description = 'CC24-Guide - A comprehensive directory of digital
 | 
			
		||||
  <meta name="description" content={description}>
 | 
			
		||||
  <title>{title} - CC24-Guide</title>
 | 
			
		||||
  <link rel="icon" type="image/x-icon" href="/favicon.ico">
 | 
			
		||||
  
 | 
			
		||||
  <!-- CONSOLIDATED: Load theme script -->
 | 
			
		||||
  <script src="/src/scripts/theme.js"></script>
 | 
			
		||||
  
 | 
			
		||||
  <!-- CONSOLIDATED: Load client-side auth utilities -->
 | 
			
		||||
  <script src="/src/scripts/client-auth.js"></script>
 | 
			
		||||
  
 | 
			
		||||
  <script>
 | 
			
		||||
    // Initialize theme immediately to prevent flash
 | 
			
		||||
    (window as any).themeUtils?.initTheme();
 | 
			
		||||
 | 
			
		||||
@ -6,7 +6,6 @@ import ToolMatrix from '../components/ToolMatrix.astro';
 | 
			
		||||
import AIQueryInterface from '../components/AIQueryInterface.astro';
 | 
			
		||||
import { getToolsData } from '../utils/dataService.js';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// Load tools data
 | 
			
		||||
const data = await getToolsData();
 | 
			
		||||
const tools = data.tools;
 | 
			
		||||
@ -53,8 +52,8 @@ const tools = data.tools;
 | 
			
		||||
        KI befragen
 | 
			
		||||
      </button>
 | 
			
		||||
      
 | 
			
		||||
      <!-- NEW: Contribution Button -->
 | 
			
		||||
      <a href="/contribute" class="btn" style="padding: 0.75rem 1.5rem; background-color: var(--color-warning); color: white; border-color: var(--color-warning);" data-contribute-button="new">
 | 
			
		||||
      <!-- Contribution Button - FIXED: Use data-contribute-button -->
 | 
			
		||||
      <a href="/contribute" class="btn" style="padding: 0.75rem 1.5rem; background-color: var(--color-warning); color: white; border-color: var(--color-warning);" data-contribute-button>
 | 
			
		||||
        <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 0.5rem;">
 | 
			
		||||
          <path d="M16 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/>
 | 
			
		||||
          <circle cx="8.5" cy="7" r="4"/>
 | 
			
		||||
@ -78,13 +77,12 @@ const tools = data.tools;
 | 
			
		||||
  
 | 
			
		||||
  <!-- Filters Section -->
 | 
			
		||||
  <section id="filters-section" style="padding: 2rem 0;">
 | 
			
		||||
    <ToolFilters />
 | 
			
		||||
    <ToolFilters data={data} />
 | 
			
		||||
  </section>
 | 
			
		||||
 | 
			
		||||
  <!-- AI Query Interface -->
 | 
			
		||||
  <AIQueryInterface />
 | 
			
		||||
 | 
			
		||||
  
 | 
			
		||||
  <!-- Tools Grid -->
 | 
			
		||||
  <section id="tools-grid" style="padding-bottom: 2rem;">
 | 
			
		||||
    <div class="grid-auto-fit" id="tools-container">
 | 
			
		||||
@ -100,36 +98,22 @@ const tools = data.tools;
 | 
			
		||||
  </section>
 | 
			
		||||
  
 | 
			
		||||
  <!-- Matrix View -->
 | 
			
		||||
  <ToolMatrix />
 | 
			
		||||
  <ToolMatrix data={data} />
 | 
			
		||||
</BaseLayout>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
  // Extend Window interface for custom properties
 | 
			
		||||
  declare global {
 | 
			
		||||
    interface Window {
 | 
			
		||||
      toolsData: any[];
 | 
			
		||||
      showToolDetails: (toolName: string, modalType?: string) => void;
 | 
			
		||||
      hideToolDetails: (modalType?: string) => void;
 | 
			
		||||
      hideAllToolDetails: () => void;
 | 
			
		||||
      clearAllFilters?: () => void;
 | 
			
		||||
      restoreAIResults?: () => void;
 | 
			
		||||
      switchToAIView?: () => void;
 | 
			
		||||
      showShareDialog: (shareButton: HTMLElement) => void;
 | 
			
		||||
      navigateToGrid: (toolName: string) => void;
 | 
			
		||||
      navigateToMatrix: (toolName: string) => void;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
<script define:vars={{ toolsData: data.tools }}>
 | 
			
		||||
  // Store tools data globally
 | 
			
		||||
  window.toolsData = toolsData;
 | 
			
		||||
  
 | 
			
		||||
  import { requireClientAuth  } from '../utils/auth.js';
 | 
			
		||||
  // Handle view changes and filtering
 | 
			
		||||
  document.addEventListener('DOMContentLoaded', () => {
 | 
			
		||||
    const toolsContainer = document.getElementById('tools-container') as HTMLElement;
 | 
			
		||||
    const toolsGrid = document.getElementById('tools-grid') as HTMLElement;
 | 
			
		||||
    const matrixContainer = document.getElementById('matrix-container') as HTMLElement;
 | 
			
		||||
    const aiInterface = document.getElementById('ai-interface') as HTMLElement;
 | 
			
		||||
    const filtersSection = document.getElementById('filters-section') as HTMLElement;
 | 
			
		||||
    const noResults = document.getElementById('no-results') as HTMLElement;
 | 
			
		||||
    const aiQueryBtn = document.getElementById('ai-query-btn') as HTMLButtonElement;
 | 
			
		||||
    const toolsContainer = document.getElementById('tools-container');
 | 
			
		||||
    const toolsGrid = document.getElementById('tools-grid');
 | 
			
		||||
    const matrixContainer = document.getElementById('matrix-container');
 | 
			
		||||
    const aiInterface = document.getElementById('ai-interface');
 | 
			
		||||
    const filtersSection = document.getElementById('filters-section');
 | 
			
		||||
    const noResults = document.getElementById('no-results');
 | 
			
		||||
    const aiQueryBtn = document.getElementById('ai-query-btn');
 | 
			
		||||
    
 | 
			
		||||
    // Guard against null elements
 | 
			
		||||
    if (!toolsContainer || !toolsGrid || !matrixContainer || !noResults || !aiInterface || !filtersSection) {
 | 
			
		||||
@ -138,7 +122,7 @@ const tools = data.tools;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    // Simple sorting function
 | 
			
		||||
    function sortTools(tools: any[], sortBy = 'default') {
 | 
			
		||||
    function sortTools(tools, sortBy = 'default') {
 | 
			
		||||
      const sorted = [...tools];
 | 
			
		||||
      
 | 
			
		||||
      switch (sortBy) {
 | 
			
		||||
@ -147,12 +131,12 @@ const tools = data.tools;
 | 
			
		||||
        case 'difficulty':
 | 
			
		||||
          const difficultyOrder = { 'novice': 0, 'beginner': 1, 'intermediate': 2, 'advanced': 3, 'expert': 4 };
 | 
			
		||||
          return sorted.sort((a, b) => 
 | 
			
		||||
            (difficultyOrder[a.skillLevel as keyof typeof difficultyOrder] || 999) - (difficultyOrder[b.skillLevel as keyof typeof difficultyOrder] || 999)
 | 
			
		||||
            (difficultyOrder[a.skillLevel] || 999) - (difficultyOrder[b.skillLevel] || 999)
 | 
			
		||||
          );
 | 
			
		||||
        case 'type':
 | 
			
		||||
          const typeOrder = { 'concept': 0, 'method': 1, 'software': 2 };
 | 
			
		||||
          return sorted.sort((a, b) => 
 | 
			
		||||
            (typeOrder[a.type as keyof typeof typeOrder] || 999) - (typeOrder[b.type as keyof typeof typeOrder] || 999)
 | 
			
		||||
            (typeOrder[a.type] || 999) - (typeOrder[b.type] || 999)
 | 
			
		||||
          );
 | 
			
		||||
        case 'default':
 | 
			
		||||
        default:
 | 
			
		||||
@ -160,34 +144,22 @@ const tools = data.tools;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    // Authentication check function
 | 
			
		||||
    async function checkAuthentication() {
 | 
			
		||||
      try {
 | 
			
		||||
        const response = await fetch('/api/auth/status');
 | 
			
		||||
        const data = await response.json();
 | 
			
		||||
        return {
 | 
			
		||||
          authenticated: data.authenticated,
 | 
			
		||||
          authRequired: data.authRequired
 | 
			
		||||
        };
 | 
			
		||||
      } catch (error) {
 | 
			
		||||
        console.error('Auth check failed:', error);
 | 
			
		||||
        return {
 | 
			
		||||
          authenticated: false,
 | 
			
		||||
          authRequired: true
 | 
			
		||||
        };
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    // AI Query Button Handler
 | 
			
		||||
    // FIXED: AI Query Button Handler using global client-side auth function
 | 
			
		||||
    if (aiQueryBtn) {
 | 
			
		||||
      aiQueryBtn.addEventListener('click', async () => {
 | 
			
		||||
        await requireClientAuth(() => switchToView('ai'), `${window.location.pathname}?view=ai`);
 | 
			
		||||
        // Wait for client-side auth functions to be available
 | 
			
		||||
        if (typeof window.requireClientAuth === 'function') {
 | 
			
		||||
          await window.requireClientAuth(() => switchToView('ai'), `${window.location.pathname}?view=ai`);
 | 
			
		||||
        } else {
 | 
			
		||||
          console.error('requireClientAuth not available - client-auth.js may not be loaded');
 | 
			
		||||
          // Fallback - try switching anyway
 | 
			
		||||
          switchToView('ai');
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Function to switch between different views
 | 
			
		||||
    function switchToView(view: string) {
 | 
			
		||||
    function switchToView(view) {
 | 
			
		||||
      // Hide all views first
 | 
			
		||||
      toolsGrid.style.display = 'none';
 | 
			
		||||
      matrixContainer.style.display = 'none';
 | 
			
		||||
@ -209,7 +181,7 @@ const tools = data.tools;
 | 
			
		||||
          if (window.restoreAIResults) {
 | 
			
		||||
            window.restoreAIResults();
 | 
			
		||||
          }
 | 
			
		||||
          const aiInput = document.getElementById('ai-query-input') as HTMLTextAreaElement;
 | 
			
		||||
          const aiInput = document.getElementById('ai-query-input');
 | 
			
		||||
          if (aiInput) {
 | 
			
		||||
            setTimeout(() => aiInput.focus(), 100);
 | 
			
		||||
          }
 | 
			
		||||
@ -243,19 +215,19 @@ const tools = data.tools;
 | 
			
		||||
      ];
 | 
			
		||||
      
 | 
			
		||||
      elements.forEach(selector => {
 | 
			
		||||
        const element = document.querySelector(selector) as HTMLElement;
 | 
			
		||||
        const element = document.querySelector(selector);
 | 
			
		||||
        if (element) element.style.display = 'none';
 | 
			
		||||
      });
 | 
			
		||||
      
 | 
			
		||||
      const allInputs = filtersSection.querySelectorAll('input, select, textarea');
 | 
			
		||||
      allInputs.forEach(input => (input as HTMLElement).style.display = 'none');
 | 
			
		||||
      allInputs.forEach(input => input.style.display = 'none');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function showFilterControls() {
 | 
			
		||||
      const domainPhaseContainer = document.querySelector('.domain-phase-container') as HTMLElement;
 | 
			
		||||
      const searchInput = document.getElementById('search-input') as HTMLElement;
 | 
			
		||||
      const tagCloud = document.querySelector('.tag-cloud') as HTMLElement;
 | 
			
		||||
      const tagHeader = document.querySelector('.tag-header') as HTMLElement;
 | 
			
		||||
      const domainPhaseContainer = document.querySelector('.domain-phase-container');
 | 
			
		||||
      const searchInput = document.getElementById('search-input');
 | 
			
		||||
      const tagCloud = document.querySelector('.tag-cloud');
 | 
			
		||||
      const tagHeader = document.querySelector('.tag-header');
 | 
			
		||||
      const checkboxWrappers = document.querySelectorAll('.checkbox-wrapper');
 | 
			
		||||
      const allInputs = filtersSection.querySelectorAll('input, select, textarea');
 | 
			
		||||
      
 | 
			
		||||
@ -264,12 +236,12 @@ const tools = data.tools;
 | 
			
		||||
      if (tagCloud) tagCloud.style.display = 'flex';
 | 
			
		||||
      if (tagHeader) tagHeader.style.display = 'flex';
 | 
			
		||||
      
 | 
			
		||||
      allInputs.forEach(input => (input as HTMLElement).style.display = 'block');
 | 
			
		||||
      checkboxWrappers.forEach(wrapper => (wrapper as HTMLElement).style.display = 'flex');
 | 
			
		||||
      allInputs.forEach(input => input.style.display = 'block');
 | 
			
		||||
      checkboxWrappers.forEach(wrapper => wrapper.style.display = 'flex');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Create tool slug from name
 | 
			
		||||
    function createToolSlug(toolName: string): string {
 | 
			
		||||
    function createToolSlug(toolName) {
 | 
			
		||||
      return toolName.toLowerCase()
 | 
			
		||||
        .replace(/[^a-z0-9\s-]/g, '')
 | 
			
		||||
        .replace(/\s+/g, '-')
 | 
			
		||||
@ -278,15 +250,15 @@ const tools = data.tools;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Find tool by name or slug
 | 
			
		||||
    function findTool(identifier: string) {
 | 
			
		||||
    function findTool(identifier) {
 | 
			
		||||
      return window.toolsData.find(tool => 
 | 
			
		||||
        tool.name === identifier || 
 | 
			
		||||
        createToolSlug(tool.name) === identifier.toLowerCase()
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Navigation functions for sharing
 | 
			
		||||
    window.navigateToGrid = function(toolName: string) {
 | 
			
		||||
    // RESTORED: Navigation functions for sharing - EXACT ORIGINAL VERSIONS
 | 
			
		||||
    window.navigateToGrid = function(toolName) {
 | 
			
		||||
      console.log('Navigating to grid for tool:', toolName);
 | 
			
		||||
      
 | 
			
		||||
      // Switch to grid view first
 | 
			
		||||
@ -302,7 +274,7 @@ const tools = data.tools;
 | 
			
		||||
        // Wait for filters to clear and re-render
 | 
			
		||||
        setTimeout(() => {
 | 
			
		||||
          const toolCards = document.querySelectorAll('.tool-card');
 | 
			
		||||
          let targetCard: Element | null = null;
 | 
			
		||||
          let targetCard = null;
 | 
			
		||||
          
 | 
			
		||||
          toolCards.forEach(card => {
 | 
			
		||||
            const cardTitle = card.querySelector('h3');
 | 
			
		||||
@ -317,13 +289,12 @@ const tools = data.tools;
 | 
			
		||||
          
 | 
			
		||||
          if (targetCard) {
 | 
			
		||||
            console.log('Found target card, scrolling...');
 | 
			
		||||
            // Cast to Element to fix TypeScript issue
 | 
			
		||||
            (targetCard as Element).scrollIntoView({ behavior: 'smooth', block: 'center' });
 | 
			
		||||
            (targetCard as HTMLElement).style.animation = 'highlight-flash 2s ease-out';
 | 
			
		||||
            targetCard.scrollIntoView({ behavior: 'smooth', block: 'center' });
 | 
			
		||||
            targetCard.style.animation = 'highlight-flash 2s ease-out';
 | 
			
		||||
            
 | 
			
		||||
            setTimeout(() => {
 | 
			
		||||
              if (targetCard) {
 | 
			
		||||
                (targetCard as HTMLElement).style.animation = '';
 | 
			
		||||
                targetCard.style.animation = '';
 | 
			
		||||
              }
 | 
			
		||||
            }, 2000);
 | 
			
		||||
          } else {
 | 
			
		||||
@ -333,7 +304,7 @@ const tools = data.tools;
 | 
			
		||||
      }, 200);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    window.navigateToMatrix = function(toolName: string) {
 | 
			
		||||
    window.navigateToMatrix = function(toolName) {
 | 
			
		||||
      console.log('Navigating to matrix for tool:', toolName);
 | 
			
		||||
      
 | 
			
		||||
      // Switch to matrix view
 | 
			
		||||
@ -342,7 +313,7 @@ const tools = data.tools;
 | 
			
		||||
      // Wait for view switch and matrix to render
 | 
			
		||||
      setTimeout(() => {
 | 
			
		||||
        const toolChips = document.querySelectorAll('.tool-chip');
 | 
			
		||||
        let firstMatch: Element | null = null;
 | 
			
		||||
        let firstMatch = null;
 | 
			
		||||
        let matchCount = 0;
 | 
			
		||||
        
 | 
			
		||||
        toolChips.forEach(chip => {
 | 
			
		||||
@ -350,7 +321,7 @@ const tools = data.tools;
 | 
			
		||||
          const chipText = chip.textContent?.replace(/📖/g, '').replace(/[^\w\s\-\.]/g, '').trim();
 | 
			
		||||
          if (chipText === toolName) {
 | 
			
		||||
            // Highlight this occurrence
 | 
			
		||||
            (chip as HTMLElement).style.animation = 'highlight-flash 2s ease-out';
 | 
			
		||||
            chip.style.animation = 'highlight-flash 2s ease-out';
 | 
			
		||||
            matchCount++;
 | 
			
		||||
            
 | 
			
		||||
            // Remember the first match for scrolling
 | 
			
		||||
@ -360,22 +331,21 @@ const tools = data.tools;
 | 
			
		||||
            
 | 
			
		||||
            // Clean up animation after it completes
 | 
			
		||||
            setTimeout(() => {
 | 
			
		||||
              (chip as HTMLElement).style.animation = '';
 | 
			
		||||
              chip.style.animation = '';
 | 
			
		||||
            }, 8000);
 | 
			
		||||
          }
 | 
			
		||||
        });
 | 
			
		||||
        
 | 
			
		||||
        if (firstMatch) {
 | 
			
		||||
          console.log(`Found ${matchCount} occurrences of tool, highlighting all and scrolling to first`);
 | 
			
		||||
          // Cast to Element to fix TypeScript issue
 | 
			
		||||
          (firstMatch as Element).scrollIntoView({ behavior: 'smooth', block: 'center' });
 | 
			
		||||
          firstMatch.scrollIntoView({ behavior: 'smooth', block: 'center' });
 | 
			
		||||
        } else {
 | 
			
		||||
          console.warn('Tool chip not found in matrix:', toolName);
 | 
			
		||||
        }
 | 
			
		||||
      }, 500);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // Handle URL parameters on page load
 | 
			
		||||
    // RESTORED: Handle URL parameters on page load - EXACT ORIGINAL VERSION
 | 
			
		||||
    function handleSharedURL() {
 | 
			
		||||
      const urlParams = new URLSearchParams(window.location.search);
 | 
			
		||||
      const toolParam = urlParams.get('tool');
 | 
			
		||||
@ -425,8 +395,7 @@ const tools = data.tools;
 | 
			
		||||
 | 
			
		||||
    // Handle filtered results
 | 
			
		||||
    window.addEventListener('toolsFiltered', (event) => {
 | 
			
		||||
      const customEvent = event as CustomEvent;
 | 
			
		||||
      const filtered = customEvent.detail;
 | 
			
		||||
      const filtered = event.detail;
 | 
			
		||||
      const currentView = document.querySelector('.view-toggle.active')?.getAttribute('data-view');
 | 
			
		||||
      
 | 
			
		||||
      if (currentView === 'matrix' || currentView === 'ai') {
 | 
			
		||||
@ -443,7 +412,7 @@ const tools = data.tools;
 | 
			
		||||
        
 | 
			
		||||
        const sortedTools = sortTools(filtered, 'default');
 | 
			
		||||
        
 | 
			
		||||
        sortedTools.forEach((tool: any) => {
 | 
			
		||||
        sortedTools.forEach((tool) => {
 | 
			
		||||
          const toolCard = createToolCard(tool);
 | 
			
		||||
          toolsContainer.appendChild(toolCard);
 | 
			
		||||
        });
 | 
			
		||||
@ -452,8 +421,7 @@ const tools = data.tools;
 | 
			
		||||
    
 | 
			
		||||
    // Handle view changes
 | 
			
		||||
    window.addEventListener('viewChanged', (event) => {
 | 
			
		||||
      const customEvent = event as CustomEvent;
 | 
			
		||||
      const view = customEvent.detail;
 | 
			
		||||
      const view = event.detail;
 | 
			
		||||
      switchToView(view);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
@ -461,7 +429,7 @@ const tools = data.tools;
 | 
			
		||||
    window.switchToAIView = () => switchToView('ai');
 | 
			
		||||
 | 
			
		||||
    // Tool card creation function
 | 
			
		||||
    function createToolCard(tool: any): HTMLElement {
 | 
			
		||||
    function createToolCard(tool) {
 | 
			
		||||
      const isMethod = tool.type === 'method';
 | 
			
		||||
      const isConcept = tool.type === 'concept';
 | 
			
		||||
      const hasValidProjectUrl = tool.projectUrl !== undefined && 
 | 
			
		||||
@ -545,7 +513,7 @@ const tools = data.tools;
 | 
			
		||||
        </div>
 | 
			
		||||
        
 | 
			
		||||
        <div class="tool-tags-container">
 | 
			
		||||
          ${(tool.tags || []).slice(0, 8).map((tag: string) => `<span class="tag">${tag}</span>`).join('')}
 | 
			
		||||
          ${(tool.tags || []).slice(0, 8).map((tag) => `<span class="tag">${tag}</span>`).join('')}
 | 
			
		||||
        </div>
 | 
			
		||||
        
 | 
			
		||||
        <div class="tool-card-buttons" onclick="event.stopPropagation();">
 | 
			
		||||
@ -577,7 +545,7 @@ const tools = data.tools;
 | 
			
		||||
      return cardDiv;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Initialize URL handling
 | 
			
		||||
    // RESTORED: Initialize URL handling - EXACT ORIGINAL
 | 
			
		||||
    handleSharedURL();
 | 
			
		||||
  });
 | 
			
		||||
</script>
 | 
			
		||||
@ -1,30 +0,0 @@
 | 
			
		||||
// src/scripts/auth-utils.js
 | 
			
		||||
export async function checkAuthAndRedirect(targetUrl) {
 | 
			
		||||
  try {
 | 
			
		||||
    const response = await fetch('/api/auth/status');
 | 
			
		||||
    const data = await response.json();
 | 
			
		||||
    
 | 
			
		||||
    if (data.authRequired && !data.authenticated) {
 | 
			
		||||
      const returnUrl = encodeURIComponent(targetUrl);
 | 
			
		||||
      window.location.href = `/api/auth/login?returnTo=${returnUrl}`;
 | 
			
		||||
      return false;
 | 
			
		||||
    } else {
 | 
			
		||||
      window.location.href = targetUrl;
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    console.error('Auth check failed:', error);
 | 
			
		||||
    window.location.href = targetUrl; // Fallback
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function setupAuthButtons(selector = '[data-contribute-button]') {
 | 
			
		||||
  document.addEventListener('click', async (e) => {
 | 
			
		||||
    const button = e.target.closest(selector);
 | 
			
		||||
    if (!button) return;
 | 
			
		||||
    
 | 
			
		||||
    e.preventDefault();
 | 
			
		||||
    await checkAuthAndRedirect(button.href);
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
@ -1,7 +1,8 @@
 | 
			
		||||
// src/scripts/client-auth.js - Client-side auth utilities
 | 
			
		||||
// src/scripts/client-auth.js - CONSOLIDATED client-side auth utilities
 | 
			
		||||
// This file REPLACES auth-utils.js and any client-side auth functions
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Consolidated client-side auth status check
 | 
			
		||||
 * Check authentication status
 | 
			
		||||
 */
 | 
			
		||||
async function checkClientAuth() {
 | 
			
		||||
  try {
 | 
			
		||||
@ -30,8 +31,12 @@ async function requireClientAuth(callback, returnUrl) {
 | 
			
		||||
  if (authStatus.authRequired && !authStatus.authenticated) {
 | 
			
		||||
    const targetUrl = returnUrl || window.location.href;
 | 
			
		||||
    window.location.href = `/api/auth/login?returnTo=${encodeURIComponent(targetUrl)}`;
 | 
			
		||||
    return false;
 | 
			
		||||
  } else {
 | 
			
		||||
    callback();
 | 
			
		||||
    if (typeof callback === 'function') {
 | 
			
		||||
      callback();
 | 
			
		||||
    }
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -49,7 +54,30 @@ async function showIfAuthenticated(selector) {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Handle contribute button clicks with auth check
 | 
			
		||||
 */
 | 
			
		||||
function setupAuthButtons(selector = '[data-contribute-button]') {
 | 
			
		||||
  document.addEventListener('click', async (e) => {
 | 
			
		||||
    const button = e.target.closest(selector);
 | 
			
		||||
    if (!button) return;
 | 
			
		||||
    
 | 
			
		||||
    e.preventDefault();
 | 
			
		||||
    await requireClientAuth(() => {
 | 
			
		||||
      window.location.href = button.href;
 | 
			
		||||
    }, button.href);
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Make functions available globally
 | 
			
		||||
window.checkClientAuth = checkClientAuth;
 | 
			
		||||
window.requireClientAuth = requireClientAuth;
 | 
			
		||||
window.showIfAuthenticated = showIfAuthenticated;
 | 
			
		||||
window.showIfAuthenticated = showIfAuthenticated;
 | 
			
		||||
window.setupAuthButtons = setupAuthButtons;
 | 
			
		||||
 | 
			
		||||
// Auto-setup contribute buttons when DOM is ready
 | 
			
		||||
document.addEventListener('DOMContentLoaded', () => {
 | 
			
		||||
  setupAuthButtons('[data-contribute-button]');
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
console.log('Client auth utilities loaded');
 | 
			
		||||
@ -1,9 +1,8 @@
 | 
			
		||||
// src/utils/auth.ts - Enhanced with Email Support
 | 
			
		||||
// src/utils/auth.ts - SERVER-SIDE ONLY (remove client-side functions)
 | 
			
		||||
import { SignJWT, jwtVerify, type JWTPayload } from 'jose';
 | 
			
		||||
import { serialize, parse } from 'cookie';
 | 
			
		||||
import { config } from 'dotenv';
 | 
			
		||||
import type { AstroGlobal, APIRoute } from 'astro';
 | 
			
		||||
 | 
			
		||||
import type { AstroGlobal } from 'astro';
 | 
			
		||||
 | 
			
		||||
// Load environment variables
 | 
			
		||||
config();
 | 
			
		||||
@ -210,8 +209,9 @@ export interface AuthContext {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Consolidated auth check for Astro pages
 | 
			
		||||
 * Replaces repeated auth patterns in contribute pages
 | 
			
		||||
 * CONSOLIDATED: Replace repeated auth patterns in .astro pages
 | 
			
		||||
 * Usage: const authResult = await withAuth(Astro);
 | 
			
		||||
 *        if (authResult instanceof Response) return authResult;
 | 
			
		||||
 */
 | 
			
		||||
export async function withAuth(Astro: AstroGlobal): Promise<AuthContext | Response> {
 | 
			
		||||
  const authRequired = process.env.AUTHENTICATION_NECESSARY !== 'false';
 | 
			
		||||
@ -254,10 +254,15 @@ export async function withAuth(Astro: AstroGlobal): Promise<AuthContext | Respon
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Consolidated auth check for API endpoints
 | 
			
		||||
 * Replaces repeated auth patterns in API routes
 | 
			
		||||
 * CONSOLIDATED: Replace repeated auth patterns in API endpoints
 | 
			
		||||
 * Usage: const authResult = await withAPIAuth(request);
 | 
			
		||||
 *        if (!authResult.authenticated) return createAuthErrorResponse();
 | 
			
		||||
 */
 | 
			
		||||
export async function withAPIAuth(request: Request): Promise<{ authenticated: boolean; userId: string; session?: SessionData }> {
 | 
			
		||||
export async function withAPIAuth(request: Request): Promise<{ 
 | 
			
		||||
  authenticated: boolean; 
 | 
			
		||||
  userId: string; 
 | 
			
		||||
  session?: SessionData 
 | 
			
		||||
}> {
 | 
			
		||||
  const authRequired = process.env.AUTHENTICATION_NECESSARY !== 'false';
 | 
			
		||||
  
 | 
			
		||||
  if (!authRequired) {
 | 
			
		||||
@ -292,50 +297,4 @@ export function createAuthErrorResponse(message: string = 'Authentication requir
 | 
			
		||||
    status: 401,
 | 
			
		||||
    headers: { 'Content-Type': 'application/json' }
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function checkClientAuth() {
 | 
			
		||||
  try {
 | 
			
		||||
    const response = await fetch('/api/auth/status');
 | 
			
		||||
    const data = await response.json();
 | 
			
		||||
    return {
 | 
			
		||||
      authenticated: data.authenticated,
 | 
			
		||||
      authRequired: data.authRequired,
 | 
			
		||||
      expires: data.expires
 | 
			
		||||
    };
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    console.error('Auth check failed:', error);
 | 
			
		||||
    return {
 | 
			
		||||
      authenticated: false,
 | 
			
		||||
      authRequired: true
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Redirect to login if not authenticated, otherwise execute callback
 | 
			
		||||
 */
 | 
			
		||||
export async function requireClientAuth(callback, returnUrl) {
 | 
			
		||||
  const authStatus = await checkClientAuth();
 | 
			
		||||
  
 | 
			
		||||
  if (authStatus.authRequired && !authStatus.authenticated) {
 | 
			
		||||
    const targetUrl = returnUrl || window.location.href;
 | 
			
		||||
    window.location.href = `/api/auth/login?returnTo=${encodeURIComponent(targetUrl)}`;
 | 
			
		||||
  } else {
 | 
			
		||||
    callback();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Show/hide element based on authentication
 | 
			
		||||
 */
 | 
			
		||||
export async function showIfAuthenticated(selector) {
 | 
			
		||||
  const authStatus = await checkClientAuth();
 | 
			
		||||
  const element = document.querySelector(selector);
 | 
			
		||||
  
 | 
			
		||||
  if (element) {
 | 
			
		||||
    element.style.display = (!authStatus.authRequired || authStatus.authenticated) 
 | 
			
		||||
      ? 'inline-flex' 
 | 
			
		||||
      : 'none';
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user