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}>
|
<meta name="description" content={description}>
|
||||||
<title>{title} - CC24-Guide</title>
|
<title>{title} - CC24-Guide</title>
|
||||||
<link rel="icon" type="image/x-icon" href="/favicon.ico">
|
<link rel="icon" type="image/x-icon" href="/favicon.ico">
|
||||||
|
|
||||||
|
<!-- CONSOLIDATED: Load theme script -->
|
||||||
<script src="/src/scripts/theme.js"></script>
|
<script src="/src/scripts/theme.js"></script>
|
||||||
|
|
||||||
|
<!-- CONSOLIDATED: Load client-side auth utilities -->
|
||||||
|
<script src="/src/scripts/client-auth.js"></script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// Initialize theme immediately to prevent flash
|
// Initialize theme immediately to prevent flash
|
||||||
(window as any).themeUtils?.initTheme();
|
(window as any).themeUtils?.initTheme();
|
||||||
|
@ -6,7 +6,6 @@ import ToolMatrix from '../components/ToolMatrix.astro';
|
|||||||
import AIQueryInterface from '../components/AIQueryInterface.astro';
|
import AIQueryInterface from '../components/AIQueryInterface.astro';
|
||||||
import { getToolsData } from '../utils/dataService.js';
|
import { getToolsData } from '../utils/dataService.js';
|
||||||
|
|
||||||
|
|
||||||
// Load tools data
|
// Load tools data
|
||||||
const data = await getToolsData();
|
const data = await getToolsData();
|
||||||
const tools = data.tools;
|
const tools = data.tools;
|
||||||
@ -53,8 +52,8 @@ const tools = data.tools;
|
|||||||
KI befragen
|
KI befragen
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<!-- NEW: Contribution Button -->
|
<!-- 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="new">
|
<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;">
|
<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"/>
|
<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"/>
|
<circle cx="8.5" cy="7" r="4"/>
|
||||||
@ -78,13 +77,12 @@ const tools = data.tools;
|
|||||||
|
|
||||||
<!-- Filters Section -->
|
<!-- Filters Section -->
|
||||||
<section id="filters-section" style="padding: 2rem 0;">
|
<section id="filters-section" style="padding: 2rem 0;">
|
||||||
<ToolFilters />
|
<ToolFilters data={data} />
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- AI Query Interface -->
|
<!-- AI Query Interface -->
|
||||||
<AIQueryInterface />
|
<AIQueryInterface />
|
||||||
|
|
||||||
|
|
||||||
<!-- Tools Grid -->
|
<!-- Tools Grid -->
|
||||||
<section id="tools-grid" style="padding-bottom: 2rem;">
|
<section id="tools-grid" style="padding-bottom: 2rem;">
|
||||||
<div class="grid-auto-fit" id="tools-container">
|
<div class="grid-auto-fit" id="tools-container">
|
||||||
@ -100,36 +98,22 @@ const tools = data.tools;
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- Matrix View -->
|
<!-- Matrix View -->
|
||||||
<ToolMatrix />
|
<ToolMatrix data={data} />
|
||||||
</BaseLayout>
|
</BaseLayout>
|
||||||
|
|
||||||
<script>
|
<script define:vars={{ toolsData: data.tools }}>
|
||||||
// Extend Window interface for custom properties
|
// Store tools data globally
|
||||||
declare global {
|
window.toolsData = toolsData;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
import { requireClientAuth } from '../utils/auth.js';
|
|
||||||
// Handle view changes and filtering
|
// Handle view changes and filtering
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
const toolsContainer = document.getElementById('tools-container') as HTMLElement;
|
const toolsContainer = document.getElementById('tools-container');
|
||||||
const toolsGrid = document.getElementById('tools-grid') as HTMLElement;
|
const toolsGrid = document.getElementById('tools-grid');
|
||||||
const matrixContainer = document.getElementById('matrix-container') as HTMLElement;
|
const matrixContainer = document.getElementById('matrix-container');
|
||||||
const aiInterface = document.getElementById('ai-interface') as HTMLElement;
|
const aiInterface = document.getElementById('ai-interface');
|
||||||
const filtersSection = document.getElementById('filters-section') as HTMLElement;
|
const filtersSection = document.getElementById('filters-section');
|
||||||
const noResults = document.getElementById('no-results') as HTMLElement;
|
const noResults = document.getElementById('no-results');
|
||||||
const aiQueryBtn = document.getElementById('ai-query-btn') as HTMLButtonElement;
|
const aiQueryBtn = document.getElementById('ai-query-btn');
|
||||||
|
|
||||||
// Guard against null elements
|
// Guard against null elements
|
||||||
if (!toolsContainer || !toolsGrid || !matrixContainer || !noResults || !aiInterface || !filtersSection) {
|
if (!toolsContainer || !toolsGrid || !matrixContainer || !noResults || !aiInterface || !filtersSection) {
|
||||||
@ -138,7 +122,7 @@ const tools = data.tools;
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Simple sorting function
|
// Simple sorting function
|
||||||
function sortTools(tools: any[], sortBy = 'default') {
|
function sortTools(tools, sortBy = 'default') {
|
||||||
const sorted = [...tools];
|
const sorted = [...tools];
|
||||||
|
|
||||||
switch (sortBy) {
|
switch (sortBy) {
|
||||||
@ -147,12 +131,12 @@ const tools = data.tools;
|
|||||||
case 'difficulty':
|
case 'difficulty':
|
||||||
const difficultyOrder = { 'novice': 0, 'beginner': 1, 'intermediate': 2, 'advanced': 3, 'expert': 4 };
|
const difficultyOrder = { 'novice': 0, 'beginner': 1, 'intermediate': 2, 'advanced': 3, 'expert': 4 };
|
||||||
return sorted.sort((a, b) =>
|
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':
|
case 'type':
|
||||||
const typeOrder = { 'concept': 0, 'method': 1, 'software': 2 };
|
const typeOrder = { 'concept': 0, 'method': 1, 'software': 2 };
|
||||||
return sorted.sort((a, b) =>
|
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':
|
case 'default':
|
||||||
default:
|
default:
|
||||||
@ -160,34 +144,22 @@ const tools = data.tools;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Authentication check function
|
// FIXED: AI Query Button Handler using global client-side auth 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
|
|
||||||
if (aiQueryBtn) {
|
if (aiQueryBtn) {
|
||||||
aiQueryBtn.addEventListener('click', async () => {
|
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 to switch between different views
|
||||||
function switchToView(view: string) {
|
function switchToView(view) {
|
||||||
// Hide all views first
|
// Hide all views first
|
||||||
toolsGrid.style.display = 'none';
|
toolsGrid.style.display = 'none';
|
||||||
matrixContainer.style.display = 'none';
|
matrixContainer.style.display = 'none';
|
||||||
@ -209,7 +181,7 @@ const tools = data.tools;
|
|||||||
if (window.restoreAIResults) {
|
if (window.restoreAIResults) {
|
||||||
window.restoreAIResults();
|
window.restoreAIResults();
|
||||||
}
|
}
|
||||||
const aiInput = document.getElementById('ai-query-input') as HTMLTextAreaElement;
|
const aiInput = document.getElementById('ai-query-input');
|
||||||
if (aiInput) {
|
if (aiInput) {
|
||||||
setTimeout(() => aiInput.focus(), 100);
|
setTimeout(() => aiInput.focus(), 100);
|
||||||
}
|
}
|
||||||
@ -243,19 +215,19 @@ const tools = data.tools;
|
|||||||
];
|
];
|
||||||
|
|
||||||
elements.forEach(selector => {
|
elements.forEach(selector => {
|
||||||
const element = document.querySelector(selector) as HTMLElement;
|
const element = document.querySelector(selector);
|
||||||
if (element) element.style.display = 'none';
|
if (element) element.style.display = 'none';
|
||||||
});
|
});
|
||||||
|
|
||||||
const allInputs = filtersSection.querySelectorAll('input, select, textarea');
|
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() {
|
function showFilterControls() {
|
||||||
const domainPhaseContainer = document.querySelector('.domain-phase-container') as HTMLElement;
|
const domainPhaseContainer = document.querySelector('.domain-phase-container');
|
||||||
const searchInput = document.getElementById('search-input') as HTMLElement;
|
const searchInput = document.getElementById('search-input');
|
||||||
const tagCloud = document.querySelector('.tag-cloud') as HTMLElement;
|
const tagCloud = document.querySelector('.tag-cloud');
|
||||||
const tagHeader = document.querySelector('.tag-header') as HTMLElement;
|
const tagHeader = document.querySelector('.tag-header');
|
||||||
const checkboxWrappers = document.querySelectorAll('.checkbox-wrapper');
|
const checkboxWrappers = document.querySelectorAll('.checkbox-wrapper');
|
||||||
const allInputs = filtersSection.querySelectorAll('input, select, textarea');
|
const allInputs = filtersSection.querySelectorAll('input, select, textarea');
|
||||||
|
|
||||||
@ -264,12 +236,12 @@ const tools = data.tools;
|
|||||||
if (tagCloud) tagCloud.style.display = 'flex';
|
if (tagCloud) tagCloud.style.display = 'flex';
|
||||||
if (tagHeader) tagHeader.style.display = 'flex';
|
if (tagHeader) tagHeader.style.display = 'flex';
|
||||||
|
|
||||||
allInputs.forEach(input => (input as HTMLElement).style.display = 'block');
|
allInputs.forEach(input => input.style.display = 'block');
|
||||||
checkboxWrappers.forEach(wrapper => (wrapper as HTMLElement).style.display = 'flex');
|
checkboxWrappers.forEach(wrapper => wrapper.style.display = 'flex');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create tool slug from name
|
// Create tool slug from name
|
||||||
function createToolSlug(toolName: string): string {
|
function createToolSlug(toolName) {
|
||||||
return toolName.toLowerCase()
|
return toolName.toLowerCase()
|
||||||
.replace(/[^a-z0-9\s-]/g, '')
|
.replace(/[^a-z0-9\s-]/g, '')
|
||||||
.replace(/\s+/g, '-')
|
.replace(/\s+/g, '-')
|
||||||
@ -278,15 +250,15 @@ const tools = data.tools;
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Find tool by name or slug
|
// Find tool by name or slug
|
||||||
function findTool(identifier: string) {
|
function findTool(identifier) {
|
||||||
return window.toolsData.find(tool =>
|
return window.toolsData.find(tool =>
|
||||||
tool.name === identifier ||
|
tool.name === identifier ||
|
||||||
createToolSlug(tool.name) === identifier.toLowerCase()
|
createToolSlug(tool.name) === identifier.toLowerCase()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Navigation functions for sharing
|
// RESTORED: Navigation functions for sharing - EXACT ORIGINAL VERSIONS
|
||||||
window.navigateToGrid = function(toolName: string) {
|
window.navigateToGrid = function(toolName) {
|
||||||
console.log('Navigating to grid for tool:', toolName);
|
console.log('Navigating to grid for tool:', toolName);
|
||||||
|
|
||||||
// Switch to grid view first
|
// Switch to grid view first
|
||||||
@ -302,7 +274,7 @@ const tools = data.tools;
|
|||||||
// Wait for filters to clear and re-render
|
// Wait for filters to clear and re-render
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const toolCards = document.querySelectorAll('.tool-card');
|
const toolCards = document.querySelectorAll('.tool-card');
|
||||||
let targetCard: Element | null = null;
|
let targetCard = null;
|
||||||
|
|
||||||
toolCards.forEach(card => {
|
toolCards.forEach(card => {
|
||||||
const cardTitle = card.querySelector('h3');
|
const cardTitle = card.querySelector('h3');
|
||||||
@ -317,13 +289,12 @@ const tools = data.tools;
|
|||||||
|
|
||||||
if (targetCard) {
|
if (targetCard) {
|
||||||
console.log('Found target card, scrolling...');
|
console.log('Found target card, scrolling...');
|
||||||
// Cast to Element to fix TypeScript issue
|
targetCard.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||||
(targetCard as Element).scrollIntoView({ behavior: 'smooth', block: 'center' });
|
targetCard.style.animation = 'highlight-flash 2s ease-out';
|
||||||
(targetCard as HTMLElement).style.animation = 'highlight-flash 2s ease-out';
|
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (targetCard) {
|
if (targetCard) {
|
||||||
(targetCard as HTMLElement).style.animation = '';
|
targetCard.style.animation = '';
|
||||||
}
|
}
|
||||||
}, 2000);
|
}, 2000);
|
||||||
} else {
|
} else {
|
||||||
@ -333,7 +304,7 @@ const tools = data.tools;
|
|||||||
}, 200);
|
}, 200);
|
||||||
};
|
};
|
||||||
|
|
||||||
window.navigateToMatrix = function(toolName: string) {
|
window.navigateToMatrix = function(toolName) {
|
||||||
console.log('Navigating to matrix for tool:', toolName);
|
console.log('Navigating to matrix for tool:', toolName);
|
||||||
|
|
||||||
// Switch to matrix view
|
// Switch to matrix view
|
||||||
@ -342,7 +313,7 @@ const tools = data.tools;
|
|||||||
// Wait for view switch and matrix to render
|
// Wait for view switch and matrix to render
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const toolChips = document.querySelectorAll('.tool-chip');
|
const toolChips = document.querySelectorAll('.tool-chip');
|
||||||
let firstMatch: Element | null = null;
|
let firstMatch = null;
|
||||||
let matchCount = 0;
|
let matchCount = 0;
|
||||||
|
|
||||||
toolChips.forEach(chip => {
|
toolChips.forEach(chip => {
|
||||||
@ -350,7 +321,7 @@ const tools = data.tools;
|
|||||||
const chipText = chip.textContent?.replace(/📖/g, '').replace(/[^\w\s\-\.]/g, '').trim();
|
const chipText = chip.textContent?.replace(/📖/g, '').replace(/[^\w\s\-\.]/g, '').trim();
|
||||||
if (chipText === toolName) {
|
if (chipText === toolName) {
|
||||||
// Highlight this occurrence
|
// Highlight this occurrence
|
||||||
(chip as HTMLElement).style.animation = 'highlight-flash 2s ease-out';
|
chip.style.animation = 'highlight-flash 2s ease-out';
|
||||||
matchCount++;
|
matchCount++;
|
||||||
|
|
||||||
// Remember the first match for scrolling
|
// Remember the first match for scrolling
|
||||||
@ -360,22 +331,21 @@ const tools = data.tools;
|
|||||||
|
|
||||||
// Clean up animation after it completes
|
// Clean up animation after it completes
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
(chip as HTMLElement).style.animation = '';
|
chip.style.animation = '';
|
||||||
}, 8000);
|
}, 8000);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (firstMatch) {
|
if (firstMatch) {
|
||||||
console.log(`Found ${matchCount} occurrences of tool, highlighting all and scrolling to first`);
|
console.log(`Found ${matchCount} occurrences of tool, highlighting all and scrolling to first`);
|
||||||
// Cast to Element to fix TypeScript issue
|
firstMatch.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||||
(firstMatch as Element).scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
||||||
} else {
|
} else {
|
||||||
console.warn('Tool chip not found in matrix:', toolName);
|
console.warn('Tool chip not found in matrix:', toolName);
|
||||||
}
|
}
|
||||||
}, 500);
|
}, 500);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle URL parameters on page load
|
// RESTORED: Handle URL parameters on page load - EXACT ORIGINAL VERSION
|
||||||
function handleSharedURL() {
|
function handleSharedURL() {
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
const toolParam = urlParams.get('tool');
|
const toolParam = urlParams.get('tool');
|
||||||
@ -425,8 +395,7 @@ const tools = data.tools;
|
|||||||
|
|
||||||
// Handle filtered results
|
// Handle filtered results
|
||||||
window.addEventListener('toolsFiltered', (event) => {
|
window.addEventListener('toolsFiltered', (event) => {
|
||||||
const customEvent = event as CustomEvent;
|
const filtered = event.detail;
|
||||||
const filtered = customEvent.detail;
|
|
||||||
const currentView = document.querySelector('.view-toggle.active')?.getAttribute('data-view');
|
const currentView = document.querySelector('.view-toggle.active')?.getAttribute('data-view');
|
||||||
|
|
||||||
if (currentView === 'matrix' || currentView === 'ai') {
|
if (currentView === 'matrix' || currentView === 'ai') {
|
||||||
@ -443,7 +412,7 @@ const tools = data.tools;
|
|||||||
|
|
||||||
const sortedTools = sortTools(filtered, 'default');
|
const sortedTools = sortTools(filtered, 'default');
|
||||||
|
|
||||||
sortedTools.forEach((tool: any) => {
|
sortedTools.forEach((tool) => {
|
||||||
const toolCard = createToolCard(tool);
|
const toolCard = createToolCard(tool);
|
||||||
toolsContainer.appendChild(toolCard);
|
toolsContainer.appendChild(toolCard);
|
||||||
});
|
});
|
||||||
@ -452,8 +421,7 @@ const tools = data.tools;
|
|||||||
|
|
||||||
// Handle view changes
|
// Handle view changes
|
||||||
window.addEventListener('viewChanged', (event) => {
|
window.addEventListener('viewChanged', (event) => {
|
||||||
const customEvent = event as CustomEvent;
|
const view = event.detail;
|
||||||
const view = customEvent.detail;
|
|
||||||
switchToView(view);
|
switchToView(view);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -461,7 +429,7 @@ const tools = data.tools;
|
|||||||
window.switchToAIView = () => switchToView('ai');
|
window.switchToAIView = () => switchToView('ai');
|
||||||
|
|
||||||
// Tool card creation function
|
// Tool card creation function
|
||||||
function createToolCard(tool: any): HTMLElement {
|
function createToolCard(tool) {
|
||||||
const isMethod = tool.type === 'method';
|
const isMethod = tool.type === 'method';
|
||||||
const isConcept = tool.type === 'concept';
|
const isConcept = tool.type === 'concept';
|
||||||
const hasValidProjectUrl = tool.projectUrl !== undefined &&
|
const hasValidProjectUrl = tool.projectUrl !== undefined &&
|
||||||
@ -545,7 +513,7 @@ const tools = data.tools;
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="tool-tags-container">
|
<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>
|
||||||
|
|
||||||
<div class="tool-card-buttons" onclick="event.stopPropagation();">
|
<div class="tool-card-buttons" onclick="event.stopPropagation();">
|
||||||
@ -577,7 +545,7 @@ const tools = data.tools;
|
|||||||
return cardDiv;
|
return cardDiv;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize URL handling
|
// RESTORED: Initialize URL handling - EXACT ORIGINAL
|
||||||
handleSharedURL();
|
handleSharedURL();
|
||||||
});
|
});
|
||||||
</script>
|
</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() {
|
async function checkClientAuth() {
|
||||||
try {
|
try {
|
||||||
@ -30,8 +31,12 @@ async function requireClientAuth(callback, returnUrl) {
|
|||||||
if (authStatus.authRequired && !authStatus.authenticated) {
|
if (authStatus.authRequired && !authStatus.authenticated) {
|
||||||
const targetUrl = returnUrl || window.location.href;
|
const targetUrl = returnUrl || window.location.href;
|
||||||
window.location.href = `/api/auth/login?returnTo=${encodeURIComponent(targetUrl)}`;
|
window.location.href = `/api/auth/login?returnTo=${encodeURIComponent(targetUrl)}`;
|
||||||
|
return false;
|
||||||
} else {
|
} 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
|
// Make functions available globally
|
||||||
window.checkClientAuth = checkClientAuth;
|
window.checkClientAuth = checkClientAuth;
|
||||||
window.requireClientAuth = requireClientAuth;
|
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 { SignJWT, jwtVerify, type JWTPayload } from 'jose';
|
||||||
import { serialize, parse } from 'cookie';
|
import { serialize, parse } from 'cookie';
|
||||||
import { config } from 'dotenv';
|
import { config } from 'dotenv';
|
||||||
import type { AstroGlobal, APIRoute } from 'astro';
|
import type { AstroGlobal } from 'astro';
|
||||||
|
|
||||||
|
|
||||||
// Load environment variables
|
// Load environment variables
|
||||||
config();
|
config();
|
||||||
@ -210,8 +209,9 @@ export interface AuthContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Consolidated auth check for Astro pages
|
* CONSOLIDATED: Replace repeated auth patterns in .astro pages
|
||||||
* Replaces repeated auth patterns in contribute pages
|
* Usage: const authResult = await withAuth(Astro);
|
||||||
|
* if (authResult instanceof Response) return authResult;
|
||||||
*/
|
*/
|
||||||
export async function withAuth(Astro: AstroGlobal): Promise<AuthContext | Response> {
|
export async function withAuth(Astro: AstroGlobal): Promise<AuthContext | Response> {
|
||||||
const authRequired = process.env.AUTHENTICATION_NECESSARY !== 'false';
|
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
|
* CONSOLIDATED: Replace repeated auth patterns in API endpoints
|
||||||
* Replaces repeated auth patterns in API routes
|
* 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';
|
const authRequired = process.env.AUTHENTICATION_NECESSARY !== 'false';
|
||||||
|
|
||||||
if (!authRequired) {
|
if (!authRequired) {
|
||||||
@ -292,50 +297,4 @@ export function createAuthErrorResponse(message: string = 'Authentication requir
|
|||||||
status: 401,
|
status: 401,
|
||||||
headers: { 'Content-Type': 'application/json' }
|
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