diff --git a/src/components/ProjectCard.astro b/src/components/ProjectCard.astro index b1b96fc..c2e1407 100644 --- a/src/components/ProjectCard.astro +++ b/src/components/ProjectCard.astro @@ -12,9 +12,8 @@ const apiKey = import.meta.env.PUBLIC_SCREENSHOT_API_KEY; console.log('API Key in component:', apiKey); // Debug log // Function to generate screenshot URL with error handling -function generateScreenshotUrl(url: string, isMobile = false) { +function generateScreenshotUrl(url: string, isMobile = false, apiKey: string) { try { - const baseUrl = 'https://api.screenshotone.com/take'; const params = new URLSearchParams({ 'access_key': apiKey, 'url': url, @@ -30,15 +29,16 @@ function generateScreenshotUrl(url: string, isMobile = false) { } ) }); - return `${baseUrl}?${params.toString()}`; + return `https://api.screenshotone.com/take?${params}`; } catch (error) { console.error('Error generating screenshot URL:', error); return null; } } -const screenshotUrl = generateScreenshotUrl(project.url); -const mobileScreenshotUrl = generateScreenshotUrl(project.url, true); +const screenshotUrl = generateScreenshotUrl(project.url, false, apiKey); +const mobileScreenshotUrl = generateScreenshotUrl(project.url, true, apiKey); + ---
@@ -196,310 +196,290 @@ const mobileScreenshotUrl = generateScreenshotUrl(project.url, true); const apiKey = button.dataset.apiKey; const title = button.dataset.title; - console.log('Button clicked with:', { url, apiKey: apiKey?.substring(0, 5) + '...' }); // Debug log - if (url && apiKey) { try { - // First, test if the target URL is accessible - const testResponse = await fetch(url, { method: 'HEAD' }).catch(() => null); - if (!testResponse?.ok) { - throw new Error(`Target website is not accessible: ${url}`); - } - await openScreenshots(event, url, apiKey, title); } catch (error) { - console.error('Error in click handler:', error); - showError(error); + console.error('Error generating preview:', error); + showError(new Error('Unable to generate preview at this time. Please try visiting the project URL directly.')); } - } else { - console.error('Missing required data:', { url, hasApiKey: !!apiKey }); - showError(new Error('Missing URL or API key')); } }); } }); } - function showError(error: Error) { - const modal = document.createElement('div'); - modal.style.cssText = ` - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - background: rgba(0, 0, 0, 0.9); - display: flex; - justify-content: center; - align-items: center; - z-index: 1000; - `; + async function openScreenshots(event: Event, url: string, apiKey: string, title: string) { + const loadingModal = showLoading(); + + try { + // Test the screenshot API first + const testUrl = 'https://api.screenshotone.com/take?' + new URLSearchParams({ + 'access_key': apiKey, + 'url': url, + 'format': 'jpg' + }); - modal.innerHTML = ` -
-

Error Loading Preview

-

${error.message}

-
-
${error.stack || 'No stack trace available'}
-
- -
- `; + const testResponse = await fetch(testUrl); + if (!testResponse.ok) { + throw new Error('Screenshot service is unavailable. Please try again later.'); + } - document.body.appendChild(modal); + // Continue with screenshot preview... + const desktopUrl = generateScreenshotUrl(url, false, apiKey); + const mobileUrl = generateScreenshotUrl(url, true, apiKey); + + loadingModal.remove(); + showPreviewModal(title, desktopUrl, mobileUrl); + } catch (error) { + loadingModal.remove(); + throw error; + } } - async function openScreenshots(event: Event, url: string, apiKey: string, title: string) { - console.log('Opening screenshots for:', url); - console.log('API key length:', apiKey?.length); - - // Create loading modal - const modal = document.createElement('div'); - modal.style.cssText = ` - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - background: rgba(0, 0, 0, 0.9); - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - z-index: 1000; - `; + function generateScreenshotUrl(url: string, isMobile: boolean, apiKey: string) { + const params = new URLSearchParams({ + 'access_key': apiKey, + 'url': url, + 'full_page': 'false', + 'format': 'jpg', + 'quality': '100', + ...(isMobile + ? { 'device': 'iPhone 12 Pro' } + : { + 'viewport_width': '1920', + 'viewport_height': '1080', + 'device_scale_factor': '1' + } + ) + }); + return `https://api.screenshotone.com/take?${params}`; + } - // Create title - const titleElement = document.createElement('h2'); - titleElement.textContent = title; - titleElement.style.cssText = ` - color: white; - margin-bottom: 20px; - font-size: 1.5rem; + function showLoading() { + const modal = document.createElement('div'); + modal.className = 'loading-modal'; + modal.innerHTML = ` +
+
+

Generating preview...

+
`; - // Create loading message - const loadingMessage = document.createElement('div'); - loadingMessage.style.cssText = ` - color: white; - margin-top: 20px; - font-size: 1rem; - `; - loadingMessage.textContent = 'Generating preview...'; - - // Create loading spinner - const spinner = document.createElement('div'); - spinner.style.cssText = ` - width: 50px; - height: 50px; - border: 3px solid #f3f3f3; - border-top: 3px solid var(--accent); - border-radius: 50%; - animation: spin 1s linear infinite; - `; - - // Add spinner animation const style = document.createElement('style'); style.textContent = ` + .loading-modal { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.7); + display: flex; + justify-content: center; + align-items: center; + z-index: 1000; + } + .loading-content { + background: white; + padding: 2rem; + border-radius: 1rem; + text-align: center; + } + .loading-spinner { + width: 40px; + height: 40px; + margin: 0 auto 1rem; + border: 4px solid #f3f3f3; + border-top: 4px solid #3498db; + border-radius: 50%; + animation: spin 1s linear infinite; + } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } `; document.head.appendChild(style); - - modal.appendChild(titleElement); - modal.appendChild(spinner); - modal.appendChild(loadingMessage); document.body.appendChild(modal); + return modal; + } - try { - // Test the API key first - const testUrl = 'https://api.screenshotone.com/take?' + new URLSearchParams({ - 'access_key': apiKey, - 'url': 'https://example.com', - 'format': 'jpg' - }).toString(); + function showPreviewModal(title: string, desktopUrl: string, mobileUrl: string) { + const modal = document.createElement('div'); + modal.className = 'preview-modal'; + modal.innerHTML = ` +
+

${title}

+
+ + +
+
+ Desktop preview + Mobile preview +
+
+ `; - const testResponse = await fetch(testUrl); - if (!testResponse.ok) { - const errorText = await testResponse.text(); - throw new Error(`API key test failed: ${testResponse.status} ${testResponse.statusText}\n${errorText}`); + const style = document.createElement('style'); + style.textContent = ` + .preview-modal { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.9); + display: flex; + justify-content: center; + align-items: center; + z-index: 1000; } + .preview-content { + background: white; + padding: 2rem; + border-radius: 1rem; + max-width: 90%; + max-height: 90vh; + overflow: auto; + } + .preview-content h3 { + margin: 0 0 1rem; + text-align: center; + } + .preview-tabs { + display: flex; + justify-content: center; + gap: 1rem; + margin-bottom: 1rem; + } + .tab-button { + padding: 0.5rem 1rem; + border: none; + background: #f3f3f3; + border-radius: 0.5rem; + cursor: pointer; + } + .tab-button.active { + background: #3498db; + color: white; + } + .preview-frame { + position: relative; + width: 100%; + height: 60vh; + } + .preview-image { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + object-fit: contain; + display: none; + } + .preview-image.active { + display: block; + } + `; + document.head.appendChild(style); + document.body.appendChild(modal); - // Generate screenshot URLs - const baseUrl = 'https://api.screenshotone.com/take'; - const desktopParams = new URLSearchParams({ - 'access_key': apiKey, - 'url': url, - 'full_page': 'false', - 'viewport_width': '1920', - 'viewport_height': '1080', - 'device_scale_factor': '1', - 'format': 'jpg', - 'quality': '100' - }); - - const mobileParams = new URLSearchParams({ - 'access_key': apiKey, - 'url': url, - 'full_page': 'false', - 'device': 'iPhone 12 Pro', - 'format': 'jpg', - 'quality': '100' + // Handle tab switching + const tabs = modal.querySelectorAll('.tab-button'); + tabs.forEach(tab => { + tab.addEventListener('click', () => { + const view = tab.getAttribute('data-view'); + tabs.forEach(t => t.classList.remove('active')); + tab.classList.add('active'); + modal.querySelectorAll('.preview-image').forEach(img => { + img.classList.remove('active'); + if (img.classList.contains(view!)) { + img.classList.add('active'); + } + }); }); + }); - const screenshotUrl = `${baseUrl}?${desktopParams.toString()}`; - const mobileScreenshotUrl = `${baseUrl}?${mobileParams.toString()}`; + // Close on outside click + modal.addEventListener('click', (e) => { + if (e.target === modal) { + modal.remove(); + style.remove(); + } + }); + } - console.log('Desktop URL:', screenshotUrl); - console.log('Mobile URL:', mobileScreenshotUrl); + function showError(error: Error) { + const modal = document.createElement('div'); + modal.className = 'error-modal'; + modal.innerHTML = ` +
+

Preview Unavailable

+

${error.message}

+ +
+ `; - // Create image container - const imageContainer = document.createElement('div'); - imageContainer.style.cssText = ` - max-width: 90%; - max-height: 90%; - position: relative; + const style = document.createElement('style'); + style.textContent = ` + .error-modal { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.7); display: flex; - gap: 20px; - `; - - // Create close button - const closeButton = document.createElement('button'); - closeButton.innerHTML = '×'; - closeButton.style.cssText = ` - position: absolute; - top: -40px; - right: 0; - background: none; + justify-content: center; + align-items: center; + z-index: 1000; + } + .error-content { + background: white; + padding: 2rem; + border-radius: 1rem; + max-width: 500px; + width: 90%; + text-align: center; + } + .error-content h3 { + color: #dc2626; + margin: 0 0 1rem 0; + } + .error-content p { + margin: 0 0 1.5rem 0; + color: #4b5563; + } + .close-button { + padding: 0.5rem 1rem; + background: #e5e7eb; border: none; - color: white; - font-size: 2rem; + border-radius: 0.5rem; cursor: pointer; - z-index: 1001; - `; - - // Create desktop preview - const desktopContainer = document.createElement('div'); - desktopContainer.style.cssText = ` - flex: 2; - position: relative; - `; - - const desktopLabel = document.createElement('div'); - desktopLabel.textContent = 'Desktop View'; - desktopLabel.style.cssText = ` - color: white; - text-align: center; - margin-bottom: 10px; - font-size: 0.9rem; - `; - - const desktopImg = document.createElement('img'); - desktopImg.style.cssText = ` - max-width: 100%; - max-height: calc(80vh - 30px); - object-fit: contain; - opacity: 0; - transition: opacity 0.3s ease; - `; - - // Create mobile preview - const mobileContainer = document.createElement('div'); - mobileContainer.style.cssText = ` - flex: 1; - position: relative; - `; - - const mobileLabel = document.createElement('div'); - mobileLabel.textContent = 'Mobile View'; - mobileLabel.style.cssText = ` - color: white; - text-align: center; - margin-bottom: 10px; - font-size: 0.9rem; - `; - - const mobileImg = document.createElement('img'); - mobileImg.style.cssText = ` - max-width: 100%; - max-height: calc(80vh - 30px); - object-fit: contain; - opacity: 0; - transition: opacity 0.3s ease; - `; - - // Error handling for image loading - let loadedImages = 0; - const handleImageLoad = () => { - loadedImages++; - if (loadedImages === 2) { - modal.innerHTML = ''; - modal.appendChild(imageContainer); - // Fade in images - setTimeout(() => { - desktopImg.style.opacity = '1'; - mobileImg.style.opacity = '1'; - }, 100); - } - }; - - const handleImageError = async (error: Error) => { - console.error('Error loading image:', error); - - // Try to get more details about the error - try { - const response = await fetch(screenshotUrl); - const errorText = await response.text(); - error = new Error(`Failed to load image: ${response.status} ${response.statusText}\n${errorText}`); - } catch (fetchError) { - console.error('Error fetching error details:', fetchError); - } - - showError(error); - }; - - // Set up image loading - desktopImg.onload = handleImageLoad; - mobileImg.onload = handleImageLoad; - desktopImg.onerror = () => handleImageError(new Error('Failed to load desktop screenshot')); - mobileImg.onerror = () => handleImageError(new Error('Failed to load mobile screenshot')); - - // Set image sources - desktopImg.src = screenshotUrl; - mobileImg.src = mobileScreenshotUrl; - - // Event listeners - closeButton.onclick = () => modal.remove(); - modal.onclick = (e) => { - if (e.target === modal) modal.remove(); - }; - - // Assemble the preview - desktopContainer.appendChild(desktopLabel); - desktopContainer.appendChild(desktopImg); - mobileContainer.appendChild(mobileLabel); - mobileContainer.appendChild(mobileImg); - - imageContainer.appendChild(closeButton); - imageContainer.appendChild(desktopContainer); - imageContainer.appendChild(mobileContainer); - - // Set a timeout to show an error if images don't load within 30 seconds - const timeout = setTimeout(() => { - if (loadedImages < 2) { - handleImageError(new Error('Preview generation timed out')); - } - }, 30000); + font-weight: 500; + color: #374151; + } + .close-button:hover { + background: #d1d5db; + } + `; + document.head.appendChild(style); + document.body.appendChild(modal); - } catch (error) { - console.error('Error in screenshot preview:', error); - showError(error instanceof Error ? error : new Error(String(error))); - } + // Close on button click or outside click + const closeButton = modal.querySelector('.close-button'); + closeButton?.addEventListener('click', () => { + modal.remove(); + style.remove(); + }); + modal.addEventListener('click', (e) => { + if (e.target === modal) { + modal.remove(); + style.remove(); + } + }); } // Initialize when the DOM is ready