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](${desktopUrl})
+
![Mobile preview](${mobileUrl})
+
+
+ `;
- 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