Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(frontend): adding project screenshot #159

Merged
merged 5 commits into from
Mar 5, 2025

Conversation

NarwhalChen
Copy link
Collaborator

@NarwhalChen NarwhalChen commented Mar 5, 2025

Summary by CodeRabbit

  • New Features

    • Introduced a new API endpoint that enables capturing and returning screenshots of web pages.
    • Enhanced project management with automatic retrieval of project URLs and streamlined updates to project photos.
    • Added functionality for email confirmation and improved account verification workflows, including the ability to resend confirmation messages and query remaining project limits.
  • Chores

    • Upgraded the browser automation dependency for more robust headless operations.
    • Improved file upload URL construction for better compatibility across different operating systems.

Copy link
Contributor

coderabbitai bot commented Mar 5, 2025

Important

Review skipped

Review was skipped due to path filters

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml

CodeRabbit blocks several paths by default. You can override this behavior by explicitly including those paths in the path filters. For example, including **/dist/** will override the default block on the dist directory, by removing the pattern from both the lists.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

The changes update the backend and frontend code to improve file URL handling and introduce new features. The backend now uses path.join in the UploadService for constructing file URLs. On the frontend, a new Puppeteer dependency is added to support browser automation. New API endpoints for running projects and taking screenshots have been introduced. Additionally, project context and UI components have been updated to use new methods for URL retrieval and screenshot capture. The GraphQL schema and type definitions have been enhanced to support email confirmation and project photo URL updates.

Changes

Files Summary of Changes
backend/src/upload/upload.service.ts Modified URL construction using path.join for both buffer uploads and GraphQL FileUpload handling.
frontend/package.json Added Puppeteer dependency ("puppeteer": "^24.3.1").
frontend/src/app/api/runProject/route.ts
frontend/src/app/api/screenshot/route.ts
Updated API endpoints: runProject now returns additional container details; new screenshot endpoint uses Puppeteer to capture webpage screenshots and responds with an image.
frontend/src/components/chat/code-engine/project-context.tsx
frontend/src/components/chat/code-engine/web-view.tsx
frontend/src/components/root/expand-card.tsx
Enhanced project context and components: introduced getWebUrl and takeProjectScreenshot methods, refactored URL fetching logic, improved error handling, state management, and caching of project URLs.
frontend/src/graphql/request.ts
frontend/src/graphql/schema.gql
frontend/src/graphql/type.tsx
GraphQL enhancements: added new mutation (UPDATE_PROJECT_PHOTO_URL), introduced new types for email confirmations and input, updated User type fields, and added query for remaining project limits.

Sequence Diagram(s)

sequenceDiagram
  participant C as Client
  participant A as Screenshot API Route
  participant B as Puppeteer Browser
  participant P as Web Page
  C->>A: GET /api/screenshot?url={target URL}
  A->>B: Launch headless browser
  B->>P: Open new page & set viewport (1280x720)
  P->>P: Navigate to URL (wait for network idle)
  P->>B: Capture screenshot (PNG)
  B->>A: Return screenshot buffer
  A->>C: Respond with image/png and cache-control header
Loading
sequenceDiagram
  participant U as User
  participant E as ExpandableCard Component
  participant PC as ProjectContext
  U->>E: Clicks on a project card
  E->>E: Check for cached URL
  alt URL is cached
    E->>E: Use cached URL to update iframe and activate project
  else URL not cached
    E->>PC: Call getWebUrl(project path)
    PC-->>E: Return { domain, containerId }
    E->>PC: Invoke takeProjectScreenshot(projectId, URL)
    E->>E: Cache URL and update active project state
  end
Loading

Possibly related PRs

Suggested reviewers

  • Sma1lboy
  • ZHallen122

Poem

I hopped through codes and leafy trails,
With changes sprouting like bright spring bales.
URLs now formed with a careful join,
Puppeteer's magic in every coin.
In screens and schemas, my joy unfurls—
A rabbit’s cheer for our blooming worlds! 🐰


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

🔭 Outside diff range comments (2)
frontend/src/components/root/expand-card.tsx (1)

135-141: 🛠️ Refactor suggestion

Switch from img to Next.js Image component.

Using a standard img tag instead of Next.js Image component loses important optimizations like lazy loading, automatic responsive sizing, and image optimization.

Restore the Next.js Image component:

-<img
-  src={project.image}
-  alt={project.name}
-  width={600}
-  height={200}
-  className="w-full h-48 object-cover transition duration-300 group-hover:scale-105"
-/>
+<Image
+  src={project.image}
+  alt={project.name}
+  width={600}
+  height={200}
+  className="w-full h-48 object-cover transition duration-300 group-hover:scale-105"
+/>
frontend/src/components/chat/code-engine/project-context.tsx (1)

423-461: 🛠️ Refactor suggestion

Avoid repeating the runProject + screenshot pattern in pollChatProject.
Currently, the code runs /api/runProject and calls takeProjectScreenshot similarly to getWebUrl. Consider refactoring to reuse getWebUrl inside pollChatProject for consistency, unless there is a specific need to repeat this logic.

🧹 Nitpick comments (7)
frontend/src/app/api/runProject/route.ts (1)

301-301: Unnecessary empty line.

This empty line doesn't affect functionality but is an unnecessary change.

Consider removing it to keep the code concise.

frontend/src/app/api/screenshot/route.ts (1)

49-55: Error handling could be enhanced.

While the current error handling is functional, it could be improved to provide more specific error messages based on the type of failure.

Consider enhancing the error handling:

} catch (error: any) {
-  console.error('Screenshot error:', error);
+  console.error('Screenshot capture failed:', error);
+  
+  // Provide more specific error messages based on error type
+  const errorMessage = error.message || 'Failed to capture screenshot';
+  const statusCode = error.name === 'TimeoutError' ? 504 : 500;
+  
  return NextResponse.json(
-    { error: error.message || 'Failed to capture screenshot' },
-    { status: 500 }
+    { error: errorMessage },
+    { status: statusCode }
  );
}
frontend/src/components/chat/code-engine/web-view.tsx (1)

33-62: Consider adding user feedback during errors.

The error is logged to the console, but there's no feedback to the user when URL fetching fails.

Consider updating the UI to show error states:

try {
  const { domain } = await getWebUrl(projectPath);
  containerRef.current = {
    projectPath,
    domain,
  };

  const baseUrl = `http://${domain}`;
  console.log('baseUrl:', baseUrl);
  setBaseUrl(baseUrl);
  setDisplayPath('/');
} catch (error) {
  console.error('Error getting web URL:', error);
+  // Show error to user
+  setBaseUrl('');
+  // You could also set an error state here to display in the UI
+  // setError('Failed to load project preview. Please try again later.');
}
frontend/src/components/root/expand-card.tsx (1)

38-45: Add error handling feedback to the user.

When URL fetching fails, the user should be notified of the error instead of just logging to the console.

Add user feedback for errors:

try {
  const data = await getWebUrl(project.path);
  const url = `http://${data.domain}`;
  cachedUrls.current.set(project.id, url);
  setIframeUrl(url);
} catch (error) {
  console.error('Error fetching project URL:', error);
+  // Provide user feedback
+  setIframeUrl('');
+  // You could also set an error state to display in the UI
+  // setError(`Failed to load project preview: ${error.message}`);
+  // Or show a toast notification
+  // toast.error('Failed to load project preview. Please try again later.');
}
frontend/src/components/chat/code-engine/project-context.tsx (3)

44-48: Added methods in interface look aligned with feature requirements.
Declaring getWebUrl and takeProjectScreenshot in the ProjectContextType interface provides clarity on the new functionalities. Consider adding inline documentation to explain these methods for easier onboarding.


227-258: Consider providing user feedback on screenshot failure.
While takeProjectScreenshot uses checkUrlStatus, if the screenshot or status check fails, the user only sees a console error. For better UX, add a toast or relevant UI notification.

} catch (error) {
  console.error('Error:', error);
+ toast.error('Failed to capture screenshot. Please retry or check your network.');
}

260-294: DRY up repeated logic for runProject calls.
getWebUrl duplicates similar runProject invocation and screenshot capturing logic also present in pollChatProject. Centralizing this logic could simplify maintenance and prevent divergence.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6be8368 and 4bc0028.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (10)
  • backend/src/upload/upload.service.ts (2 hunks)
  • frontend/package.json (1 hunks)
  • frontend/src/app/api/runProject/route.ts (2 hunks)
  • frontend/src/app/api/screenshot/route.ts (1 hunks)
  • frontend/src/components/chat/code-engine/project-context.tsx (7 hunks)
  • frontend/src/components/chat/code-engine/web-view.tsx (3 hunks)
  • frontend/src/components/root/expand-card.tsx (3 hunks)
  • frontend/src/graphql/request.ts (1 hunks)
  • frontend/src/graphql/schema.gql (7 hunks)
  • frontend/src/graphql/type.tsx (18 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: autofix
  • GitHub Check: Install and Build Backend
  • GitHub Check: Install and Build Frontend
🔇 Additional comments (35)
frontend/package.json (1)

54-54: Puppeteer dependency added for screenshot functionality.

This addition supports the project screenshot feature mentioned in the PR title. Puppeteer is a Node.js library that provides a high-level API to control headless browsers, which is perfect for taking screenshots of web pages.

Note that Puppeteer is a relatively heavy dependency as it downloads and manages a browser instance. This will increase your package size, but it's an appropriate choice for the screenshot functionality.

frontend/src/graphql/request.ts (1)

228-236: GraphQL mutation for updating project photo URLs looks good.

The mutation follows the established pattern in the codebase and properly exposes the fields needed for the screenshot feature. This aligns well with the PR objective of adding project screenshots.

frontend/src/app/api/screenshot/route.ts (6)

1-3: Import structure looks good.

The imports are clean and appropriate for the functionality provided by this file.


5-14: Parameter validation is implemented correctly.

The function properly checks for the required URL parameter and returns an appropriate error response when it's missing.


16-20: Headless browser launch configuration is appropriate.

The configuration for launching Puppeteer is correctly set to headless mode, which is essential for server-side screenshot generation.


22-26: Viewport configuration is well-defined.

Setting a reasonable viewport size ensures consistent screenshot dimensions and quality.


28-31: Navigation settings are properly configured.

The waitUntil: 'networkidle0' setting ensures that screenshots are taken only after the page is fully loaded, and the timeout value is appropriate.


43-48: Response and caching configuration is well implemented.

The screenshot is correctly returned with the appropriate content type and caching headers.

frontend/src/components/chat/code-engine/web-view.tsx (4)

17-20: Good defensive programming with context validation.

Validating that both curProject and getWebUrl are available from the context before proceeding prevents runtime errors.


32-65: Improved URL fetching logic.

The refactored initWebUrl function is more efficient by directly using the context's getWebUrl function instead of making a separate API call.


60-60: Better error message wording.

The updated error message is more specific and helpful for debugging.


64-65: Updated dependencies are appropriate.

Adding getWebUrl to the dependencies array ensures the effect runs when the URL retrieval method changes.

frontend/src/components/root/expand-card.tsx (3)

3-6: Imports are organized appropriately.

The imports are well-structured, and the addition of useContext is appropriate for using the ProjectContext.


12-13: Context usage is implemented correctly.

The destructuring of getWebUrl and takeProjectScreenshot from the context, along with the URL caching mechanism, is a good approach.


30-46: The handleCardClick function has good caching logic.

Caching URLs by project ID is an excellent performance optimization to avoid redundant API calls.

frontend/src/graphql/schema.gql (8)

60-63: EmailConfirmationResponse type looks good.

The new type with required message and optional success fields is appropriately designed for email confirmation responses.


109-109: Updated confirmEmail mutation signature is correct.

The mutation now returns the EmailConfirmationResponse type, which provides better information to the client.


119-119: New resendConfirmationEmail mutation is appropriately defined.

The mutation accepts an input type and returns an EmailConfirmationResponse, maintaining consistency with the confirmEmail mutation.


179-179: New getRemainingProjectLimit query is appropriate.

Returning an integer for project limits is the correct approach.


199-201: ResendEmailInput type is correctly defined.

The input type with a required email field matches the needs of the resendConfirmationEmail mutation.


238-239: User type extensions are appropriate.

The addition of isEmailConfirmed and lastEmailSendTime fields to the User type provides valuable information for email confirmation status.


241-241: Good use of deprecation annotation.

Using the @deprecated directive with a reason is the correct approach to mark fields that should no longer be used.


1-3:

✅ Verification successful

Verify that this auto-generated schema is properly maintained.

The comment at the top indicates this file is auto-generated. Manual edits to this file might be overwritten by the schema generator.


🏁 Script executed:

#!/bin/bash

# Check if there are any scripts or configuration that generate this file
echo "Checking for schema generation scripts..."
rg -l "schema.gql" --type=ts --type=js -g "!node_modules" -g "!dist" | grep -i "generat\|build\|schema"

# Look for any indications that this file should not be edited manually
echo "Checking if the file has markers indicating it shouldn't be manually edited..."
cat frontend/src/graphql/schema.gql | head -5

Length of output: 597


Auto-generated schema maintenance verified

The schema file (frontend/src/graphql/schema.gql) is correctly marked as auto-generated, as confirmed by the header. There’s no indication that manual changes are expected; any necessary modifications should be made via the schema generator. No further action is required.

frontend/src/components/chat/code-engine/project-context.tsx (2)

17-17: All good here.
The addition of UPDATE_PROJECT_PHOTO_URL to the import list looks consistent with the newly introduced functionality below.


196-225: Mutation for updating project photo URL is well-structured.
The updateProjectPhotoMutation logic and local state updates look correct and handle potential concurrency via the functional setProjects. The error handling with toast.error is standard.

frontend/src/graphql/type.tsx (10)

99-103: New type definition for email confirmation looks good.
The introduction of EmailConfirmationResponse is coherent. Marking success as optional allows flexibility in returning partial responses.


153-153: New confirmEmail mutation recognized.
Definition is consistent with the newly added type. Ensure relevant resolvers and client calls are implemented.


163-163: resendConfirmationEmail field properly introduced.
Pairs well with confirmEmail and the EmailConfirmationResponse. Good addition to handle multi-step email confirmations.


175-177: Arguments for confirmEmail are straightforward.
No issues found. The token argument is well-named.


215-217: Arguments for resendConfirmationEmail ensure clarity.
The input struct is consistent with typical GraphQL patterns.


339-341: New ResendEmailInput type is minimal and clear.
Appropriately typed with email: string. Looks aligned with the usage.


374-375: Fields for user email confirmation status are well-defined.
isEmailConfirmed and lastEmailSendTime complement the new email confirmation flow.


647-655: EmailConfirmationResponse resolvers integrated correctly.
Resolvers match the schema and handle optional success property.


985-991: User resolvers for email confirmation fields are consistent with schema.
Implementation for isEmailConfirmed and lastEmailSendTime ensures consistent access to new fields in resolvers.


1012-1012: Addition of EmailConfirmationResponse to resolvers is coherent.
Keeps the generated types aligned with the new schema.

@@ -3,6 +3,10 @@ import { exec } from 'child_process';
import * as path from 'path';
import * as net from 'net';
import { getProjectPath } from 'codefox-common';
import puppetter from 'puppeteer';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix typo in puppeteer import.

There's a typo in the puppeteer import that could cause runtime errors.

-import puppetter from 'puppeteer';
+import puppeteer from 'puppeteer';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import puppetter from 'puppeteer';
import puppeteer from 'puppeteer';

Comment on lines +7 to +9
import { useMutation } from '@apollo/client/react/hooks/useMutation';
import { toast } from 'sonner';
import { UPDATE_PROJECT_PHOTO_URL } from '@/graphql/request';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Remove unused imports.

These imports are not used in this file and should be removed. In particular, useMutation is a client-side React hook that shouldn't be imported in a server-side API route.

-import { useMutation } from '@apollo/client/react/hooks/useMutation';
-import { toast } from 'sonner';
-import { UPDATE_PROJECT_PHOTO_URL } from '@/graphql/request';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { useMutation } from '@apollo/client/react/hooks/useMutation';
import { toast } from 'sonner';
import { UPDATE_PROJECT_PHOTO_URL } from '@/graphql/request';

@@ -99,7 +99,7 @@ export class UploadService {
// Get the appropriate URL for the uploaded file
const bucketUrl = this.getBucketUrl();

return { url: `${bucketUrl}/${key}`, key };
return { url: path.join(bucketUrl, key), key };
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

URL construction should not use path.join.

Using path.join for URLs is problematic because it uses OS-specific path separators (backslashes on Windows), while URLs always use forward slashes regardless of the OS.

-        return { url: path.join(bucketUrl, key), key };
+        return { url: `${bucketUrl}/${key}`, key };

For proper URL construction, use one of these alternatives:

  1. String template literals: `${bucketUrl}/${key}`
  2. URL constructor: new URL(key, bucketUrl).toString()
  3. URL-specific join utility: URLJoin(bucketUrl, key) from a package like 'url-join'

This change is needed in both places where path.join is used for URL construction.

Also applies to: 138-138

Comment on lines +33 to +38
// Take screenshot
const screenshot = await page.screenshot({
path: `dsadas.png`,
type: 'png',
fullPage: true,
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Remove hardcoded filename in screenshot configuration.

The path: 'dsadas.png' appears to be a development/testing artifact. This will save files to disk unnecessarily in production.

Apply this diff to fix the issue:

const screenshot = await page.screenshot({
-  path: `dsadas.png`,
  type: 'png',
  fullPage: true,
});
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Take screenshot
const screenshot = await page.screenshot({
path: `dsadas.png`,
type: 'png',
fullPage: true,
});
// Take screenshot
const screenshot = await page.screenshot({
type: 'png',
fullPage: true,
});

Comment on lines +1 to +56
import { randomUUID } from 'crypto';
import { NextResponse } from 'next/server';
import puppeteer from 'puppeteer';

export async function GET(req: Request) {
const { searchParams } = new URL(req.url);
const url = searchParams.get('url');

if (!url) {
return NextResponse.json(
{ error: 'URL parameter is required' },
{ status: 400 }
);
}

try {
const browser = await puppeteer.launch({
headless: true,
});
const page = await browser.newPage();

// Set viewport to a reasonable size
await page.setViewport({
width: 1280,
height: 720,
});

await page.goto(url, {
waitUntil: 'networkidle0',
timeout: 30000,
});

// Take screenshot
const screenshot = await page.screenshot({
path: `dsadas.png`,
type: 'png',
fullPage: true,
});

await browser.close();

// Return the screenshot as a PNG image
return new Response(screenshot, {
headers: {
'Content-Type': 'image/png',
'Cache-Control': 's-maxage=3600',
},
});
} catch (error: any) {
console.error('Screenshot error:', error);
return NextResponse.json(
{ error: error.message || 'Failed to capture screenshot' },
{ status: 500 }
);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add URL validation to prevent potential security issues.

This endpoint accepts any URL without validation, which could lead to security issues like server-side request forgery (SSRF).

Add URL validation before processing:

import { randomUUID } from 'crypto';
import { NextResponse } from 'next/server';
import puppeteer from 'puppeteer';

+// Helper function to validate URL
+function isValidUrl(urlString: string): boolean {
+  try {
+    const url = new URL(urlString);
+    // Only allow http and https protocols
+    return url.protocol === 'http:' || url.protocol === 'https:';
+  } catch {
+    return false;
+  }
+}

export async function GET(req: Request) {
  const { searchParams } = new URL(req.url);
  const url = searchParams.get('url');

  if (!url) {
    return NextResponse.json(
      { error: 'URL parameter is required' },
      { status: 400 }
    );
  }
  
+  if (!isValidUrl(url)) {
+    return NextResponse.json(
+      { error: 'Invalid URL provided' },
+      { status: 400 }
+    );
+  }

  try {
    // ... rest of the function remains the same
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { randomUUID } from 'crypto';
import { NextResponse } from 'next/server';
import puppeteer from 'puppeteer';
export async function GET(req: Request) {
const { searchParams } = new URL(req.url);
const url = searchParams.get('url');
if (!url) {
return NextResponse.json(
{ error: 'URL parameter is required' },
{ status: 400 }
);
}
try {
const browser = await puppeteer.launch({
headless: true,
});
const page = await browser.newPage();
// Set viewport to a reasonable size
await page.setViewport({
width: 1280,
height: 720,
});
await page.goto(url, {
waitUntil: 'networkidle0',
timeout: 30000,
});
// Take screenshot
const screenshot = await page.screenshot({
path: `dsadas.png`,
type: 'png',
fullPage: true,
});
await browser.close();
// Return the screenshot as a PNG image
return new Response(screenshot, {
headers: {
'Content-Type': 'image/png',
'Cache-Control': 's-maxage=3600',
},
});
} catch (error: any) {
console.error('Screenshot error:', error);
return NextResponse.json(
{ error: error.message || 'Failed to capture screenshot' },
{ status: 500 }
);
}
}
import { randomUUID } from 'crypto';
import { NextResponse } from 'next/server';
import puppeteer from 'puppeteer';
// Helper function to validate URL
function isValidUrl(urlString: string): boolean {
try {
const url = new URL(urlString);
// Only allow http and https protocols
return url.protocol === 'http:' || url.protocol === 'https:';
} catch {
return false;
}
}
export async function GET(req: Request) {
const { searchParams } = new URL(req.url);
const url = searchParams.get('url');
if (!url) {
return NextResponse.json(
{ error: 'URL parameter is required' },
{ status: 400 }
);
}
if (!isValidUrl(url)) {
return NextResponse.json(
{ error: 'Invalid URL provided' },
{ status: 400 }
);
}
try {
const browser = await puppeteer.launch({
headless: true,
});
const page = await browser.newPage();
// Set viewport to a reasonable size
await page.setViewport({
width: 1280,
height: 720,
});
await page.goto(url, {
waitUntil: 'networkidle0',
timeout: 30000,
});
// Take screenshot
const screenshot = await page.screenshot({
path: `dsadas.png`,
type: 'png',
fullPage: true,
});
await browser.close();
// Return the screenshot as a PNG image
return new Response(screenshot, {
headers: {
'Content-Type': 'image/png',
'Cache-Control': 's-maxage=3600',
},
});
} catch (error: any) {
console.error('Screenshot error:', error);
return NextResponse.json(
{ error: error.message || 'Failed to capture screenshot' },
{ status: 500 }
);
}
}

Comment on lines +16 to +41
try {
const browser = await puppeteer.launch({
headless: true,
});
const page = await browser.newPage();

// Set viewport to a reasonable size
await page.setViewport({
width: 1280,
height: 720,
});

await page.goto(url, {
waitUntil: 'networkidle0',
timeout: 30000,
});

// Take screenshot
const screenshot = await page.screenshot({
path: `dsadas.png`,
type: 'png',
fullPage: true,
});

await browser.close();

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider resource cleanup in case of errors.

The browser should be closed even if an error occurs during screenshot capture to prevent memory leaks.

Refactor to ensure the browser is always closed:

try {
  const browser = await puppeteer.launch({
    headless: true,
  });
  const page = await browser.newPage();

+  try {
    // Set viewport to a reasonable size
    await page.setViewport({
      width: 1280,
      height: 720,
    });

    await page.goto(url, {
      waitUntil: 'networkidle0',
      timeout: 30000,
    });

    // Take screenshot
    const screenshot = await page.screenshot({
      type: 'png',
      fullPage: true,
    });

    await browser.close();

    // Return the screenshot as a PNG image
    return new Response(screenshot, {
      headers: {
        'Content-Type': 'image/png',
        'Cache-Control': 's-maxage=3600',
      },
    });
+  } catch (error) {
+    await browser.close();
+    throw error;
+  }
} catch (error: any) {
  console.error('Screenshot error:', error);
  return NextResponse.json(
    { error: error.message || 'Failed to capture screenshot' },
    { status: 500 }
  );
}

@@ -11,9 +11,13 @@ import {
ZoomIn,
ZoomOut,
} from 'lucide-react';
import puppeteer from 'puppeteer';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Remove unused Puppeteer import.

The puppeteer import is not used in this component and should be removed.

-import puppeteer from 'puppeteer';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import puppeteer from 'puppeteer';

Comment on lines +119 to +127
onClick={async () => {
const data = await getWebUrl(project.path);

console.log(project.image);
const url = `http://${data.domain}`;
setIframeUrl(url);
handleCardClick(project);
setActive(project);
}}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix redundant API calls in click handler.

The click handler makes the same API call twice: once directly and once through handleCardClick. This causes unnecessary network requests and potential race conditions.

Apply this diff to fix the redundant API calls:

onClick={async () => {
-  const data = await getWebUrl(project.path);
-
-  console.log(project.image);
-  const url = `http://${data.domain}`;
-  setIframeUrl(url);
  handleCardClick(project);
-  setActive(project);
}}

Comment on lines +53 to +68
const checkUrlStatus = async (url: string) => {
let status = 0;
while (status !== 200) {
try {
const res = await fetch(url, { method: 'HEAD' });
status = res.status;
if (status !== 200) {
console.log(`URL status: ${status}. Retrying...`);
await new Promise((resolve) => setTimeout(resolve, 1000));
}
} catch (err) {
console.error('Error checking URL status:', err);
await new Promise((resolve) => setTimeout(resolve, 1000));
}
}
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Prevent potential infinite loop by adding a max retry limit.
The current checkUrlStatus function loops indefinitely until a 200 response is returned. This can lead to a hang if the URL never becomes valid. Incorporate a maximum retry or timeout to avoid blocking.

-  while (status !== 200) {
+  let attempts = 0;
+  const maxAttempts = 20;
+  while (status !== 200 && attempts < maxAttempts) {
     try {
       // ...
     } catch (err) {
       // ...
     }
+    attempts++;
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const checkUrlStatus = async (url: string) => {
let status = 0;
while (status !== 200) {
try {
const res = await fetch(url, { method: 'HEAD' });
status = res.status;
if (status !== 200) {
console.log(`URL status: ${status}. Retrying...`);
await new Promise((resolve) => setTimeout(resolve, 1000));
}
} catch (err) {
console.error('Error checking URL status:', err);
await new Promise((resolve) => setTimeout(resolve, 1000));
}
}
};
const checkUrlStatus = async (url: string) => {
let status = 0;
let attempts = 0;
const maxAttempts = 20;
while (status !== 200 && attempts < maxAttempts) {
try {
const res = await fetch(url, { method: 'HEAD' });
status = res.status;
if (status !== 200) {
console.log(`URL status: ${status}. Retrying...`);
await new Promise((resolve) => setTimeout(resolve, 1000));
}
} catch (err) {
console.error('Error checking URL status:', err);
await new Promise((resolve) => setTimeout(resolve, 1000));
}
attempts++;
}
};

@Sma1lboy Sma1lboy changed the title Feat adding project screenshot feat(frontend): adding project screenshot Mar 5, 2025
@Sma1lboy Sma1lboy merged commit bd98049 into main Mar 5, 2025
4 of 5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants