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): implement expandable project cards and fetch public p… #152

Merged
merged 8 commits into from
Mar 5, 2025

Conversation

NarwhalChen
Copy link
Collaborator

@NarwhalChen NarwhalChen commented Mar 4, 2025

…rojects query

Summary by CodeRabbit

  • New Features

    • Introduced an interactive ExpandableCard component for project previews, featuring a modal overlay with smooth animations.
    • Added a custom hook for detecting clicks outside of elements to enhance user interactions.
  • Refactor

    • Streamlined the public projects display by integrating a new GraphQL query and simplifying the component structure for improved data retrieval and presentation.
  • Chores

    • Removed unused props from the SidebarProps interface and the ChatSideBar component for a cleaner codebase.

Copy link
Contributor

coderabbitai bot commented Mar 4, 2025

Walkthrough

This PR introduces a new React component called ExpandableCard to display project details in an interactive, modal-like overlay. The ProjectsSection component was refactored to replace the previous grid of project cards with the new expandable view using the ExpandableCard component. A new GraphQL query (FETCH_PUBLIC_PROJECTS) was added to fetch public project data, and related cosmetic updates were made to the GraphQL schema. Additionally, a custom hook (useOutsideClick) was introduced to detect clicks outside a specified element, enhancing the modal’s behavior.

Changes

File(s) Changed Change Summary
frontend/.../expand-card.tsx
frontend/.../projects-section.tsx
Introduces new ExpandableCard component with modal view and state management, and refactors ProjectsSection to utilize the new component and imported GraphQL query.
frontend/.../graphql/request.ts
frontend/.../graphql/schema.gql
Adds a new FETCH_PUBLIC_PROJECTS GraphQL query to retrieve public projects and updates schema comment formatting for clarity.
frontend/.../hooks/use-outside-click.tsx Implements a new custom React hook to detect clicks outside a designated element and invoke a callback accordingly.
frontend/.../components/sidebar.tsx Removes unused props from SidebarProps and ChatSideBar component, simplifying the component's interface.

Sequence Diagram(s)

sequenceDiagram
    participant U as User
    participant EC as ExpandableCard
    participant API as getWebUrl API
    participant Hook as useOutsideClick

    U->>EC: Clicks on project card
    EC->>API: Calls getWebUrl(projectId)
    API-->>EC: Returns project URL
    EC->>U: Opens modal with iframe view
    U->>Hook: Clicks outside modal or presses 'Escape'
    Hook->>EC: Triggers callback to close modal
    EC->>U: Closes modal, resets active project
Loading

Possibly related PRs

  • Refactor fd structure #135: Refactors the project display logic by replacing ProjectCard with the new ExpandableCard component, directly linking the modal behavior implementation between the two PRs.

Suggested reviewers

  • Sma1lboy
  • ZHallen122
  • PengyuChen01

Poem

I'm a little rabbit, hopping in code so bright,
Bringing new cards to life with a modal light.
I nibble on data and dance with a click,
With GraphQL and hooks, the code does the trick.
A happy hop to new features, oh what a sight!
🐇✨ CodeRabbit leads with delight.


📜 Recent review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 9e7740e and 356125e.

📒 Files selected for processing (1)
  • frontend/src/components/sidebar.tsx (0 hunks)
💤 Files with no reviewable changes (1)
  • frontend/src/components/sidebar.tsx
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Run tests and collect coverage

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.

@NarwhalChen NarwhalChen marked this pull request as ready for review March 5, 2025 00:19
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: 5

🧹 Nitpick comments (6)
frontend/src/hooks/use-outside-click.tsx (1)

10-10: Add type assertion for event.target

When using more specific event types, you'll need to add a type assertion for event.target when checking if it's contained within the ref.

-      if (!ref.current || ref.current.contains(event.target)) {
+      if (!ref.current || ref.current.contains(event.target as Node)) {
frontend/src/components/root/expand-card.tsx (5)

9-10: Add proper typing for state variables

State variables should have explicit type definitions to improve type safety.

-  const [active, setActive] = useState(null);
-  const [iframeUrl, setIframeUrl] = useState('');
+  const [active, setActive] = useState<Project | null>(null);
+  const [iframeUrl, setIframeUrl] = useState<string>('');

35-36: Remove console.log statement

Console logging statements should be removed from production code.

-    console.log('project:', project);

110-114: Add error handling and loading state for iframe

Add error handling for the iframe and display loading state to improve user experience.

-                    <iframe
-                      src={iframeUrl}
-                      className="w-full h-[100%]"
-                      title="Project Preview"
-                    />
+                    {isLoading ? (
+                      <div className="w-full h-full flex items-center justify-center">
+                        <div className="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-primary-600"></div>
+                      </div>
+                    ) : error ? (
+                      <div className="w-full h-full flex items-center justify-center text-red-500">
+                        {error}
+                      </div>
+                    ) : (
+                      <iframe
+                        src={iframeUrl}
+                        className="w-full h-[100%]"
+                        title="Project Preview"
+                        onError={() => setError('Failed to load project preview.')}
+                        sandbox="allow-scripts allow-same-origin"
+                      />
+                    )}

21-25: Simplify active state check

The current check for active state is unnecessarily complex. Since we're now typing active as Project | null, we can simplify this check.

-    if (active && typeof active === 'object') {
+    if (active) {
      document.body.style.overflow = 'hidden';
    } else {
      document.body.style.overflow = 'auto';
    }

123-173: Add accessibility attributes for better user experience

The project cards should have proper accessibility attributes for better user experience, especially for screen readers.

Consider adding the following improvements:

  1. Add aria-label attributes to clickable elements
  2. Use semantic HTML elements where appropriate
  3. Ensure proper keyboard navigation

Here's an example for the project card:

            onClick={() => getWebUrl(project)}
-           className="group cursor-pointer"
+           className="group cursor-pointer"
+           role="button"
+           aria-label={`View project ${project.name}`}
+           tabIndex={0}
+           onKeyDown={(e) => {
+             if (e.key === 'Enter' || e.key === ' ') {
+               getWebUrl(project);
+             }
+           }}
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 7136304 and 1f97076.

📒 Files selected for processing (5)
  • frontend/src/components/root/expand-card.tsx (1 hunks)
  • frontend/src/components/root/projects-section.tsx (3 hunks)
  • frontend/src/graphql/request.ts (1 hunks)
  • frontend/src/graphql/schema.gql (2 hunks)
  • frontend/src/hooks/use-outside-click.tsx (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • frontend/src/graphql/schema.gql
🧰 Additional context used
🪛 Biome (1.9.4)
frontend/src/hooks/use-outside-click.tsx

[error] 5-5: Don't use 'Function' as a type.

Prefer explicitly define the function shape. This type accepts any function-like value, which can be a common source of bugs.

(lint/complexity/noBannedTypes)

🪛 GitHub Actions: autofix.ci
frontend/src/hooks/use-outside-click.tsx

[error] 5-5: The Function type accepts any function-like value. Prefer explicitly defining any function parameters and return type.


[warning] 8-8: Unexpected any. Specify a different type.

🔇 Additional comments (3)
frontend/src/graphql/request.ts (1)

20-34: New GraphQL query implementation looks good

The implementation of the FETCH_PUBLIC_PROJECTS query is clean and well-structured, clearly defining the fields needed for the public projects feature.

frontend/src/components/root/projects-section.tsx (2)

1-7: Imports are well organized

The imports are well structured, bringing in the required components and functions for the implementation.


19-30: Proper data transformation logic

The transformation of fetched data into the format expected by the ExpandableCard component is well implemented. Adding the path property ensures the component has all the necessary data.

callback: Function
) => {
useEffect(() => {
const listener = (event: any) => {
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

Replace any type with a more specific event type

Using any for the event parameter reduces type safety and is flagged in the pipeline failures.

-    const listener = (event: any) => {
+    const listener = (event: MouseEvent | TouchEvent) => {
📝 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 listener = (event: any) => {
const listener = (event: MouseEvent | TouchEvent) => {
🧰 Tools
🪛 GitHub Actions: autofix.ci

[warning] 8-8: Unexpected any. Specify a different type.

Comment on lines 3 to 6
export const useOutsideClick = (
ref: React.RefObject<HTMLDivElement>,
callback: Function
) => {
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

Replace Function type with a specific function signature

The Function type is too generic and can lead to type-related bugs. This is causing pipeline failures as shown in the static analysis hints.

-export const useOutsideClick = (
-  ref: React.RefObject<HTMLDivElement>,
-  callback: Function
-) => {
+export const useOutsideClick = (
+  ref: React.RefObject<HTMLDivElement>,
+  callback: (event: MouseEvent | TouchEvent) => void
+) => {
📝 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
export const useOutsideClick = (
ref: React.RefObject<HTMLDivElement>,
callback: Function
) => {
export const useOutsideClick = (
ref: React.RefObject<HTMLDivElement>,
callback: (event: MouseEvent | TouchEvent) => void
) => {
🧰 Tools
🪛 Biome (1.9.4)

[error] 5-5: Don't use 'Function' as a type.

Prefer explicitly define the function shape. This type accepts any function-like value, which can be a common source of bugs.

(lint/complexity/noBannedTypes)

🪛 GitHub Actions: autofix.ci

[error] 5-5: The Function type accepts any function-like value. Prefer explicitly defining any function parameters and return type.

<ProjectCard key={project.id} project={project} />
))}
</div>
<ExpandableCard projects={transformedProjects} />
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Missing type definition for props

The ExpandableCard component doesn't have TypeScript type definitions, which could lead to runtime errors if the wrong data structure is passed.


🏁 Script executed:

#!/bin/bash
# Check if the ExpandableCard component has TypeScript type definitions in its implementation
rg -t tsx "export function ExpandableCard\(.+\): " --include=./frontend/src/components/root/expand-card.tsx

Length of output: 551


I'll re-run the verification using a revised shell script that avoids the unsupported --include flag and also searches for any type/interface definitions (e.g., ExpandableCardProps) in the file. This should help us confirm whether the ExpandableCard component has TypeScript props definitions.


🏁 Script executed:

#!/bin/bash
echo "Searching for the ExpandableCard component definition in expand-card.tsx..."
rg -t tsx "export function ExpandableCard\\(" frontend/src/components/root/expand-card.tsx

echo ""
echo "Searching for a type or interface definition for the ExpandableCard props (e.g., ExpandableCardProps)..."
rg -t tsx "(interface|type) ExpandableCardProps" frontend/src/components/root/expand-card.tsx

Length of output: 634


Action Required: Add Type Definitions for ExpandableCard Props

Our verification checks indicate that the ExpandableCard component in frontend/src/components/root/expand-card.tsx does not include an explicit type or interface for its props. This omission can lead to runtime errors if an unexpected data structure is passed. Please add a proper type definition (for example, by defining an interface such as ExpandableCardProps) to ensure type safety.

  • Location: frontend/src/components/root/expand-card.tsx

import { useOutsideClick } from '@/hooks/use-outside-click';
import { X } from 'lucide-react';

export function ExpandableCard({ projects }) {
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

Add TypeScript interface for projects prop

The component accepts a projects prop without type definition, which reduces type safety and could lead to runtime errors.

-export function ExpandableCard({ projects }) {
+interface Project {
+  id: string;
+  name: string;
+  path: string;
+  createDate: string;
+  author: string;
+  forkNum: number;
+  image: string;
+}
+
+interface ExpandableCardProps {
+  projects: Project[];
+}
+
+export function ExpandableCard({ projects }: ExpandableCardProps) {
📝 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
export function ExpandableCard({ projects }) {
interface Project {
id: string;
name: string;
path: string;
createDate: string;
author: string;
forkNum: number;
image: string;
}
interface ExpandableCardProps {
projects: Project[];
}
export function ExpandableCard({ projects }: ExpandableCardProps) {

Comment on lines +38 to +55
try {
const response = await fetch(
`/api/runProject?projectPath=${encodeURIComponent(projectPath)}`,
{
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
}
);
const json = await response.json();
const baseUrl = `http://${json.domain}`;
setIframeUrl(baseUrl);
setActive(project);
} catch (error) {
console.error('fetching url error:', error);
}
};
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

Improve error handling and add loading state for URL fetching

The current implementation only logs errors to the console without providing user feedback. Add proper error handling and loading state to improve the user experience.

+  const [isLoading, setIsLoading] = useState(false);
+  const [error, setError] = useState<string | null>(null);

  const getWebUrl = async (project) => {
    if (!project) return;
-    console.log('project:', project);
    const projectPath = project.path;

+    setIsLoading(true);
+    setError(null);
    try {
      const response = await fetch(
        `/api/runProject?projectPath=${encodeURIComponent(projectPath)}`,
        {
          method: 'GET',
          headers: {
            'Content-Type': 'application/json',
          },
        }
      );
+      if (!response.ok) {
+        throw new Error(`Failed to fetch project URL (${response.status})`);
+      }
      const json = await response.json();
      const baseUrl = `http://${json.domain}`;
      setIframeUrl(baseUrl);
      setActive(project);
    } catch (error) {
      console.error('fetching url error:', error);
+      setError('Failed to load project preview. Please try again.');
+    } finally {
+      setIsLoading(false);
    }
  };
📝 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
try {
const response = await fetch(
`/api/runProject?projectPath=${encodeURIComponent(projectPath)}`,
{
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
}
);
const json = await response.json();
const baseUrl = `http://${json.domain}`;
setIframeUrl(baseUrl);
setActive(project);
} catch (error) {
console.error('fetching url error:', error);
}
};
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const getWebUrl = async (project) => {
if (!project) return;
const projectPath = project.path;
setIsLoading(true);
setError(null);
try {
const response = await fetch(
`/api/runProject?projectPath=${encodeURIComponent(projectPath)}`,
{
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
}
);
if (!response.ok) {
throw new Error(`Failed to fetch project URL (${response.status})`);
}
const json = await response.json();
const baseUrl = `http://${json.domain}`;
setIframeUrl(baseUrl);
setActive(project);
} catch (error) {
console.error('fetching url error:', error);
setError('Failed to load project preview. Please try again.');
} finally {
setIsLoading(false);
}
};

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: 1

♻️ Duplicate comments (2)
frontend/src/components/root/expand-card.tsx (2)

7-7: 🛠️ Refactor suggestion

Add TypeScript interface for projects prop

The component accepts a projects prop without type definition, which reduces type safety and could lead to runtime errors.

-export function ExpandableCard({ projects }) {
+interface Project {
+  id: string;
+  name: string;
+  path: string;
+  createDate: string;
+  author: string;
+  forkNum: number;
+  image: string;
+}
+
+interface ExpandableCardProps {
+  projects: Project[];
+}
+
+export function ExpandableCard({ projects }: ExpandableCardProps) {

29-51: 🛠️ Refactor suggestion

Improve error handling and add loading state for URL fetching

The current implementation only logs errors to the console without providing user feedback. Add proper error handling and loading state to improve the user experience.

+  const [isLoading, setIsLoading] = useState(false);
+  const [error, setError] = useState<string | null>(null);

  const getWebUrl = async (project) => {
    if (!project) return;
-    console.log('project:', project);
    const projectPath = project.path;

+    setIsLoading(true);
+    setError(null);
    try {
      const response = await fetch(
        `/api/runProject?projectPath=${encodeURIComponent(projectPath)}`,
        {
          method: 'GET',
          headers: {
            'Content-Type': 'application/json',
          },
        }
      );
+      if (!response.ok) {
+        throw new Error(`Failed to fetch project URL (${response.status})`);
+      }
      const json = await response.json();
      const baseUrl = `http://${json.domain}`;
      setIframeUrl(baseUrl);
      setActive(project);
    } catch (error) {
      console.error('fetching url error:', error);
+      setError('Failed to load project preview. Please try again.');
+    } finally {
+      setIsLoading(false);
    }
  };
🧰 Tools
🪛 GitHub Actions: autofix.ci

[warning] 31-31: Unexpected console statement no-console

🧹 Nitpick comments (5)
frontend/src/components/root/expand-card.tsx (3)

12-27: Add a useOutsideClick custom hook for better modal interaction

The current implementation handles the Escape key correctly, but best practices for modals also include closing when clicking outside the modal content. This improves user experience.

+// Add this hook at the top of the file or in a separate hooks directory
+function useOutsideClick(ref, callback) {
+  useEffect(() => {
+    function handleClickOutside(event) {
+      if (ref.current && !ref.current.contains(event.target)) {
+        callback();
+      }
+    }
+    
+    document.addEventListener('mousedown', handleClickOutside);
+    return () => {
+      document.removeEventListener('mousedown', handleClickOutside);
+    };
+  }, [ref, callback]);
+}

 useEffect(() => {
   function onKeyDown(event: KeyboardEvent) {
     if (event.key === 'Escape') {
       setActive(null);
     }
   }

   if (active && typeof active === 'object') {
     document.body.style.overflow = 'hidden';
   } else {
     document.body.style.overflow = 'auto';
   }

   window.addEventListener('keydown', onKeyDown);
   return () => window.removeEventListener('keydown', onKeyDown);
 }, [active]);

+// Then use it with your ref
+useOutsideClick(ref, () => active && setActive(null));

86-91: Add aria attributes for improved accessibility

The modal lacks proper accessibility attributes, which can make it difficult for screen reader users to navigate.

            <motion.div
              layoutId={`card-${active.id}`}
              ref={ref}
              className="w-full h-full flex flex-col bg-white dark:bg-neutral-900 rounded-2xl overflow-hidden"
              style={{ willChange: 'transform, opacity' }}
+              role="dialog"
+              aria-modal="true"
+              aria-labelledby={`project-title-${active.id}`}
            >

Then update the title element:

                  <motion.h3
                    layoutId={`title-${active.id}`}
                    className="text-xl font-semibold text-gray-900 dark:text-gray-100"
+                    id={`project-title-${active.id}`}
                  >

125-127: Add keyboard accessibility for project cards

The project cards are not accessible via keyboard navigation. Add proper keyboard support to improve accessibility.

          <motion.div
            key={project.id}
            layoutId={`card-${project.id}`}
            onClick={() => getWebUrl(project)}
+            onKeyDown={(e) => {
+              if (e.key === 'Enter' || e.key === ' ') {
+                e.preventDefault();
+                getWebUrl(project);
+              }
+            }}
+            tabIndex={0}
+            role="button"
+            aria-label={`View ${project.name} project`}
            className="group cursor-pointer"
          >
frontend/src/components/root/projects-section.tsx (2)

48-48: Add error boundary for the ExpandableCard component

The ExpandableCard component might throw errors during rendering or when fetching project URLs. Wrapping it in an error boundary would prevent the entire page from crashing.

+// Add at the top of the file
+import { ErrorBoundary } from 'react-error-boundary';
+
+function FallbackComponent({ error, resetErrorBoundary }) {
+  return (
+    <div className="text-center py-4 px-6 bg-red-50 rounded-lg border border-red-200 dark:bg-red-900/20 dark:border-red-800">
+      <h3 className="text-lg font-medium text-red-800 dark:text-red-200">Something went wrong:</h3>
+      <p className="text-sm text-red-600 dark:text-red-300 mt-2">{error.message}</p>
+      <button
+        onClick={resetErrorBoundary}
+        className="mt-4 px-4 py-2 bg-red-600 text-white rounded-md hover:bg-red-700 transition-colors"
+      >
+        Try again
+      </button>
+    </div>
+  );
+}

// Then in the JSX
-              <ExpandableCard projects={transformedProjects} />
+              <ErrorBoundary
+                FallbackComponent={FallbackComponent}
+                onReset={() => {
+                  // Reset the state that caused the error
+                }}
+              >
+                <ExpandableCard projects={transformedProjects} />
+              </ErrorBoundary>

41-45: Improve loading and error states

The current loading and error states are very basic. Enhancing them with better visual feedback would improve the user experience.

        {loading ? (
-          <div className="text-center py-10">Loading...</div>
+          <div className="text-center py-10">
+            <div className="inline-block animate-spin rounded-full h-8 w-8 border-b-2 border-primary-600 mb-2"></div>
+            <p className="text-gray-600 dark:text-gray-400">Loading projects...</p>
+          </div>
        ) : error ? (
-          <div className="text-center py-10">Error: {error.message}</div>
+          <div className="text-center py-10 bg-red-50 rounded-lg p-4 dark:bg-red-900/20">
+            <div className="flex items-center justify-center mb-2">
+              <svg className="w-6 h-6 text-red-600 dark:text-red-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
+                <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
+              </svg>
+            </div>
+            <p className="text-red-800 dark:text-red-200 font-medium">Unable to load projects</p>
+            <p className="text-red-600 dark:text-red-300 text-sm mt-1">{error.message}</p>
+            <button 
+              onClick={() => window.location.reload()} 
+              className="mt-4 px-4 py-2 bg-red-600 text-white rounded-md hover:bg-red-700 transition-colors"
+            >
+              Refresh
+            </button>
+          </div>
        ) : (
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 1f97076 and 9e7740e.

📒 Files selected for processing (4)
  • frontend/src/components/root/expand-card.tsx (1 hunks)
  • frontend/src/components/root/projects-section.tsx (3 hunks)
  • frontend/src/graphql/request.ts (1 hunks)
  • frontend/src/graphql/schema.gql (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • frontend/src/graphql/request.ts
  • frontend/src/graphql/schema.gql
🧰 Additional context used
🪛 GitHub Actions: autofix.ci
frontend/src/components/root/expand-card.tsx

[warning] 31-31: Unexpected console statement no-console

🔇 Additional comments (2)
frontend/src/components/root/projects-section.tsx (2)

1-3: LGTM! Good import organization.

The imports are well-organized, with the GraphQL query now imported from a dedicated file and the new ExpandableCard component properly imported.


18-18: Good addition of the path property

The path property is correctly added to the transformed project data, which is essential for the ExpandableCard component to fetch project details.

Comment on lines +107 to +111
<iframe
src={iframeUrl}
className="w-full h-[100%]"
title="Project Preview"
/>
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

Enhance iframe security and add loading state

The iframe implementation lacks security attributes and doesn't show any loading indicator while the content is being fetched.

                    <iframe
                      src={iframeUrl}
                      className="w-full h-[100%]"
                      title="Project Preview"
+                      sandbox="allow-scripts allow-same-origin"
+                      loading="lazy"
+                      referrerPolicy="no-referrer"
                    />
+                    {isLoading && (
+                      <div className="absolute inset-0 flex items-center justify-center bg-white/80 dark:bg-black/80">
+                        <div className="animate-spin rounded-full h-10 w-10 border-b-2 border-primary-600"></div>
+                      </div>
+                    )}
+                    {error && (
+                      <div className="absolute inset-0 flex items-center justify-center bg-white/90 dark:bg-black/90">
+                        <div className="text-red-500 text-center p-4">
+                          <p>{error}</p>
+                          <button 
+                            className="mt-4 px-4 py-2 bg-primary-600 text-white rounded-lg" 
+                            onClick={(e) => {
+                              e.stopPropagation();
+                              getWebUrl(active);
+                            }}
+                          >
+                            Retry
+                          </button>
+                        </div>
+                      </div>
+                    )}
📝 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
<iframe
src={iframeUrl}
className="w-full h-[100%]"
title="Project Preview"
/>
<iframe
src={iframeUrl}
className="w-full h-[100%]"
title="Project Preview"
sandbox="allow-scripts allow-same-origin"
loading="lazy"
referrerPolicy="no-referrer"
/>
{isLoading && (
<div className="absolute inset-0 flex items-center justify-center bg-white/80 dark:bg-black/80">
<div className="animate-spin rounded-full h-10 w-10 border-b-2 border-primary-600"></div>
</div>
)}
{error && (
<div className="absolute inset-0 flex items-center justify-center bg-white/90 dark:bg-black/90">
<div className="text-red-500 text-center p-4">
<p>{error}</p>
<button
className="mt-4 px-4 py-2 bg-primary-600 text-white rounded-lg"
onClick={(e) => {
e.stopPropagation();
getWebUrl(active);
}}
>
Retry
</button>
</div>
</div>
)}

@Sma1lboy Sma1lboy merged commit 34a86c6 into main Mar 5, 2025
3 of 4 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