8000 Feature/search and filters by Ankur1493 · Pull Request #44 · tyaga001/devtoolsacademy · GitHub
[go: up one dir, main page]

Skip to content

Feature/search and filters #44

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

Merged
merged 17 commits into from
Dec 16, 2024

Conversation

Ankur1493
Copy link
Contributor
@Ankur1493 Ankur1493 commented Nov 30, 2024

Features Integrated

  • Added Tools Card
    This was me creating tool card skeletons and showing the cards design, had to checkout other competitors and create a UI design which suits theme
  • Added Explore Categories
    Created a exploration feature on the below of tools page, which let users checkout the different categories we have stored and number of tools in those categories, UI inspiration was Lu.ma/discover
  • Created a client component to fetch tools
    Why client side when it can be done on the server side, but it would be a way in UX, we can show a proper skeletons here, and decrease the load on the server for fetching these tools, we can integrate react query or something later for caching but yeah, this is what I felt as adding filters sorting would be simpler this way
  • Added Filters
    Filters are based on categories and tags as of now but we can embed more later after tools collection, also added sorting based on the popularity (defined by stars) and recent (defined by last commited)
  • Added Algolia for search
    Used Algolia for searching tools and replacing the tools which were sent by the api routes earlier
  • Created a tools details page
    this can be improved based on the UI as we can add more details later, also with how many images we want to showcase of the product
  • Created a similar tools component
    In the tool details page added a similar tools feature which showcase tools with similar categories and tags, but this is fetched on the client side as the complete tooldetails page is loaded on the server side, so we can reduce the payload size by calling this on client
  • Added proper error management
    Created proper error based skeletons and checks

What can be improved

  • We are using a lot of hardcoded data as of now, we will have to make a decision later on how we are planning to add all the details of the tools like description and images, are we going to ask user to submit those, are we going to use gpt to create that after approving the request or write it ourself
  • UI can be improved and responsiveness is missing as of now

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced a new configuration file for centralized project settings.
    • Added new API endpoints for retrieving categories and managing tools.
    • Implemented loading screens for better user experience during data fetching.
    • Introduced a search component for finding tools using Algolia.
    • Added pagination and filtering components for improved navigation through tools.
    • New components for displaying tool details and handling not found scenarios.
    • Added a new navigation item for acc 8000 essing the tools section.
  • Bug Fixes

    • Enhanced error handling across various components and API calls.
  • Documentation

    • Updated metadata for improved SEO and clarity.
  • Chores

    • Added new dependencies to support additional functionalities.
    • Introduced a new JSON data structure containing various tools and their details.

Copy link
vercel bot commented Nov 30, 2024

@Ankur1493 is attempting to deploy a commit to the Ankur 's projects Team on Vercel.

A member of the Team first needs to authorize it.

Copy link
Contributor
coderabbitai bot commented Nov 30, 2024

Walkthrough

The changes introduced in this pull request include the addition of a new configuration file components.json, updates to package.json for new dependencies, and several SQL migration scripts to create new database tabl 8000 es (Tool and Categories) and modify existing ones. New API endpoints were created to handle tools and categories, along with various React components for displaying tools, loading states, and pagination. Additionally, several utility functions and TypeScript interfaces were added to enhance the application’s structure and functionality.

Changes

File Change Summary
components.json New configuration file added with properties for style, support for RSC and TSX, Tailwind CSS configuration, path aliases, and icon library.
package.json Added dependencies: @radix-ui/react-popover, algoliasearch, react-instantsearch-dom, zod. Retained existing dependency vaul.
prisma/migrations/.../migration.sql New SQL migration files created: Tool table with multiple columns and indexes; Categories table with unique index; modifications to Tool table to add features and headline columns.
prisma/schema.prisma New models Tool and Categories added with various fields and indexes.
src/app/api/tools/categories/route.ts New API endpoint for retrieving categories.
src/app/api/tools/route.ts New API endpoint for creating multiple tool entries via POST.
src/app/api/tools/similar/route.ts New API endpoint for retrieving similar tools based on categories and tags.
src/app/layout.tsx Metadata object updated with a new description field and formatting changes.
src/app/page.tsx Featured posts updated with a new post; formatting changes for consistency.
src/app/tools/[slug]/loading.tsx New loading component for displaying loading state.
src/app/tools/[slug]/page.tsx New component for displaying tool details with metadata.
src/app/tools/loading.tsx New loading spinner component for tools page.
src/app/tools/page.tsx New component for browsing tools with search and pagination.
src/components/tools/AlgoliaSearch.tsx New component for searching tools using Algolia.
src/components/tools/Categories.tsx New component for displaying tool categories.
src/components/tools/LoadingCategories.tsx New loading component for categories section.
src/components/tools/SearchBar.tsx New search input component for tools.
src/components/tools/ToolCard.tsx New component for displaying individual tool cards.
src/components/tools/ToolSkeleton.tsx New skeleton loading UI component for tools.
src/components/tools/ToolsFilter.tsx New component for filtering and sorting tools.
src/components/tools/ToolsPage.tsx New component for displaying a list of tools based on search parameters.
src/components/tools/ToolsPagination.tsx New component for handling pagination of tools.
src/components/tools/toolDetails/SimilarTools.tsx New component for displaying similar tools.
src/components/tools/toolDetails/ToolDetailsPage.tsx New component for displaying detailed information about a tool.
src/components/tools/toolDetails/ToolNotFound.tsx New component for displaying a message when a tool is not found.
src/components/ui/pagination.tsx New pagination component with subcomponents for navigation.
src/components/ui/popover.tsx New Popover component using Radix UI.
src/lib/tools.ts New utility functions for fetching tool details and categories.
src/lib/types.ts New TypeScript interfaces for tool data structures.
tailwind.config.ts Minor formatting change (newline added).
src/lib/ZodSchema.ts New schema for validating similar route data.
src/components/Navbar.tsx New navigation item added for tools; formatting changes.
src/lib/toolData.json New JSON file containing a list of open-source tools with detailed information.

Tip

CodeRabbit's docstrings feature is now available as part of our Early Access Program! Simply use the command @coderabbitai generate docstrings to have CodeRabbit automatically generate docstrings for your pull request. This feature will be included in our Pro Plan when released.


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ 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. (Beta)
  • @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.

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

🧹 Outside diff range and nitpick comments (42)
prisma/migrations/20241123141312_categories_table_temp_added/migration.sql (1)

3-3: Consider using the UUID data type for the "id" column

Currently, the "id" column is defined as TEXT. If you are storing UUIDs, consider using the UUID data type for better data integrity and performance.

src/lib/types.ts (3)

2-2: Re-evaluate the optionality of the "id" field

In both ToolCardInterface and ToolDetailsInterface, the id field is optional (id?: string). If the id is always present for tools, consider making it required for consistency and to prevent potential undefined errors.

Also applies to: 12-12


8-8: Ensure consistent optionality of the "lastUpdated" field

In ToolCardInterface, lastUpdated is optional (lastUpdated?: Date), but it's required (lastUpdated: Date) in ToolDetailsInterface. Unless there's a specific reason, consider making the optionality consistent across both interfaces.

Also applies to: 18-18


22-23: Use undefined instead of null for optional properties

The websiteUrl and documentation properties are typed as string | null. In TypeScript, it's more idiomatic to use undefined for optional properties:

- websiteUrl?: string | null
- documentation?: string | null
+ websiteUrl?: string
+ documentation?: string

This change can simplify checks for the existence of these properties.

src/components/tools/AlgoliaSearch.tsx (1)

25-40: Use the getTools function within the component

The getTools function is defined but not utilized within the AlgoliaSearch component. To fetch and display tools based on the search query, consider using this function inside your component.

Apply this diff to integrate getTools:

 const AlgoliaSearch: React.FC<AlgoliaSearchProps> = async ({ searchParams }) => {
+
+  const tools = await getTools(searchParams.query || '')
+
   return (
     <div className="w-full max-w-4xl mx-auto">
       <ToolsFilter />
+      {/* Render tools here */}
+      <ul>
+        {tools.map((tool) => (
+          <li key={tool.name}>{tool.name}</li>
+        ))}
+      </ul>
     </div>
   )
 }

This will fetch tools based on the query and render them in a list.

prisma/schema.prisma (2)

35-55: Consider using relations for categories and tags

Defining c 8000 ategories and tags as String[] in the Tool model may limit flexibility and data integrity. Using relational models can help normalize data and establish proper relationships.

Would you like assistance in refactoring these fields to use relations with Category and Tag models?


57-64: Rename Categories model to singular Category

For consistency and clarity, consider renaming the Categories model to Category. This aligns with conventional naming practices in Prisma models.

Apply this diff to rename the model:

-model Categories {
+model Category {
   id          String @id @default(uuid())
   name        String @unique
   count       Int

   createdAt   DateTime @default(now())
   updatedAt   DateTime @updatedAt
 }
src/components/ui/popover.tsx (1)

23-26: Extract long class names into a constant for readability

The className string in PopoverContent is quite long, which may affect readability and maintainability. Consider extracting it into a constant or utilizing a styling solution like CSS modules or styled-components.

Apply this diff to extract the class names:

+const popoverContentClasses = cn(
+  "z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none",
+  "data-[state=open]:animate-in data-[state=closed]:animate-out",
+  "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
+  "data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
+  "data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2",
+  "data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2"
+)
 
 const PopoverContent = React.forwardRef<
   React.ElementRef<typeof PopoverPrimitive.Content>,
   React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
 >(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
   <PopoverPrimitive.Portal>
     <PopoverPrimitive.Content
       ref={ref}
       align={align}
       sideOffset={sideOffset}
-      className={cn(
-        "z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
-        className
-      )}
+      className={cn(popoverContentClasses, className)}
       {...props}
     />
   </PopoverPrimitive.Portal>
 ))
src/components/tools/ToolSkeleton.tsx (1)

6-6: Simplify className by removing unnecessary template literals

The className properties on lines 6 and 8 use template literals with interpolation syntax, but no variables are interpolated. You can simplify the code by using plain string literals.

Apply this diff to simplify your code:

-      className={`relative w-full h-full max-w-sm bg-gradient-to-br from-[#050817] to-gray-950 border-white border-opacity-10 rounded-xl overflow-hidden`}
+      className="relative w-full h-full max-w-sm bg-gradient-to-br from-[#050817] to-gray-950 border-white border-opacity-10 rounded-xl overflow-hidden"

and

-      <div className={`absolute inset-0 skeleton-shimmer`}></div>
+      <div className="absolute inset-0 skeleton-shimmer"></div>

Also applies to: 8-8

src/app/api/tools/similar/route.ts (2)

11-11: Simplify notIn to not for a single value

Since slug is a single string, you can simplify the condition by using not: slug instead of notIn: [slug].

Apply this diff:

-               { name: { notIn: [slug] } },
+               { name: { not: slug } },

Also applies to: 25-25


11-11: Remove outdated comments

The comments // Changed from 'not' to 'notIn' on lines 11 and 25 appear to be outdated or irrelevant in the current code context. Consider removing them to keep the code clean.

Also applies to: 25-25

src/lib/tools.ts (2)

35-35: Log the error in the catch block for better debugging

In the catch block, you're returning an error response but not logging the error. Add console.error(err); to help with debugging.

Add the following line:

} catch (err) {
+   console.error('Error in getToolDetails:', err);
    return {

62-62: Simplify error logging by logging the entire error object

The error handling code assumes that error.response may exist, which may not be the case. Log the entire error object to ensure all relevant information is captured.

Modify the logging line:

- console.error('Error in API:', error.response?.data || error.message);
+ console.error('Error in getToolCategories:', error);
src/app/tools/page.tsx (1)

32-33: Correct the grammatical error and remove unnecessary string interpolation

The phrase "well researched" should be hyphenated to "well-researched". Also, the use of {" "} to insert a space is unnecessary; you can include the space directly in the string.

Apply this diff to correct the issues:

-          Discover new devtools from a well researched collection for hassle{" "}
-          free development of your next product
+          Discover new devtools from a well-researched collection for hassle-free development of your next product
src/app/api/tools/route.ts (2)

18-18: Avoid using 'any' type; define a proper type for 'where' condition

Using any bypasses TypeScript's type checking. Defining a specific type for where will improve code safety and maintainability.

Consider defining the where variable with an appropriate type from Prisma, such as Prisma.ToolWhereInput:

-    const where: any = {};
+    const where: Prisma.ToolWhereInput = {};

Don't forget to import Prisma from the Prisma client:

import { Prisma } from '@prisma/client';

47-47: Remove unnecessary 'recent' case in switch statement

The case 'recent': is redundant because the default case covers the same logic. Removing it simplifies the code without changing functionality.

Apply this diff to remove the redundant case:

       break;
-      case 'recent':
       default:
         orderBy = { lastUpdated: 'desc' };
🧰 Tools
🪛 Biome (1.9.4)

[error] 47-47: Useless case clause.

because the default clause is present:

Unsafe fix: Remove the useless case.

(lint/complexity/noUselessSwitchCase)

src/components/tools/toolDetails/SimilarTools.tsx (2)

40-43: Implement user-friendly error handling

Currently, errors during the API call are only logged to the console. Consider displaying an error message to inform the user that similar tools could not be loaded.

Add an error state and update the catch block:

+ const [error, setError] = useState(false);

try {
  // existing code
} catch (error) {
  console.error("Error fetching similar tools:", error);
+ setError(true);
} finally {
  setIsLoading(false);
}

Display the error message in the render:

if (isLoading) {
  return (/* existing loading skeleton */);
+ } else if (error) {
+   return <div>Error loading similar tools. Please try again later.</div>;
} else {
  // existing render code
}

65-87: Show message when no similar tools are found

If both similarTagTools and similarCategoriesTools are empty, the component renders nothing, which might confuse users. Consider displaying a message indicating that no similar tools are available.

Add a fallback message:

</div>
+ {similarTagTools.length === 0 && similarCategoriesTools.length === 0 && (
+   <p>No similar tools found.</p>
+ )}
</div>
src/components/tools/Categories.tsx (1)

111-111: Correct the label to reflect the item count

The label reads {category.count || 0} Categories, which may be misleading if category.count represents the number of tools in a category. Update the label to accurately reflect the count.

Change the text to:

<p className="text-sm text-muted-foreground">
- {category.count || 0} Categories
+ {category.count || 0} Tools
</p>
src/components/tools/toolDetails/ToolDetailsPage.tsx (7)

22-35: Consider making the features list dynamic.

The features array is hardcoded within the component. To enhance reusability and scalability, consider fetching features dynamically from the tool data or an API, especially if different tools have different features.


37-42: Make language colors dynamic based on actual languages used.

The languageColors object is hardcoded and may not cover all languages used by different tools. Consider fetching language colors dynamically or using a library that maps programming languages to colors to ensure accuracy.


44-49: Retrieve language statistics dynamically for accuracy.

The languages object is currently hardcoded, which may not accurately reflect the language breakdown for each tool. Consider obtaining this data from the tool prop or an API to display accurate language statistics for each tool.


221-225: Avoid hardcoding image URLs; use dynamic images instead.

The image source is hardcoded to a specific URL. To enhance flexibility and maintainability, consider using an image URL from the tool data or fetching it dynamically.

Here's a suggested fix:

- <img
-   src="https://tailwind-elements.com/docs/standard/designblocks/hero-sections/assets/hero-3-dark.webp"
-   alt={`${tool.name} interface`}
-   className="rounded mb-6"
- />
+ {tool.image && (
+   <img
+     src={tool.image}
+     alt={`${tool.name} interface`}
+     className="rounded mb-6"
+   />
+ )}

2-12: Optimize icon imports to reduce bundle size.

Currently, multiple icons are imported from lucide-react, which could increase the bundle size if unused icons are included. Consider importing only the icons used in this component or using tree-shaking to optimize performance.


75-75: Avoid redundant display of tool.description.

The tool.description is displayed both in the summary section and the "About" section. To improve user experience, consider removing one instance or providing additional unique content.

Also applies to: 205-205


208-219: Externalize static content for better maintainability.

The "About" section contains hardcoded text with multiple references to {tool.name}. If this content is generic or used across multiple tools, consider externalizing it to a separate file or fetching it from an API to improve maintainability.

src/app/api/tools/categories/route.ts (1)

11-11: Correct the Error Message for No Categories Found

The message 'No tools found' should be updated to 'No categories found' to accurately reflect the resource being accessed.

Apply this change:

       return NextResponse.json(
-        { message: 'No tools found' },
+        { message: 'No categories found' },
         { status: 404 }
       );
src/app/tools/[slug]/page.tsx (1)

8-11: Enhance SEO with Dynamic Metadata

Currently, the metadata is static. Consider setting the metadata dynamically based on the toolDetails to improve SEO and provide more descriptive titles and descriptions for each tool's page.

You can achieve this by using the generateMetadata function provided by Next.js. Here's how you might implement it:

-import { Metadata } from 'next';
+import { Metadata, ResolvingMetadata } from 'next';

...

-export const metadata: Metadata = {
-  title: 'Dev Tools Academy',
-  description: 'DevToolsAcademy Tool details',
-};

-export default async function ToolDetailRoute({ params }: { params: { slug: string } }) {
+export async function generateMetadata(
+  { params }: { params: { slug: string } },
+  parent: ResolvingMetadata
+) {
+  const response = await getToolDetails(params.slug);
+  const toolDetails = response.toolDetails;
+
+  if (!toolDetails) {
+    return {
+      title: 'Tool Not Found - Dev Tools Academy',
+      description: 'The requested tool could not be found.',
+    };
+  }
+
+  return {
+    title: `${toolDetails.name} - Dev Tools Academy`,
+    description: toolDetails.description,
+  };
+}
+
+export default async function ToolDetailRoute({ params }: { params: { slug: string } }) {

   const response = await getToolDetails(params.slug)
   const toolDetails = response.toolDetails

   if (!toolDetails) {
     return (
       <ToolNotFound />
     )
   }

   return (
     <main className="min-h-screen w-full">
       <ToolDetailsPage tool={toolDetails} />
     </main>
   );
 }

This way, the page's metadata is dynamically generated based on the tool's details.

prisma/migrations/20241119120057_tool_temp_added/migration.sql (2)

16-16: Ensure 'updatedAt' Column Automatically Updates

The "updatedAt" column does not have a default value or an automatic update mechanism. To accurately track when a record is modified, consider implementing a trigger or updating this field in your application logic whenever the row is updated.

In your Prisma schema, you can define the updatedAt field with the @updatedAt attribute:

  updatedAt TIMESTAMP(3) NOT NULL,
+ /* In Prisma schema, this would be: */
+ updatedAt DateTime @updatedAt

This will automatically update the updatedAt field whenever the record is modified.


25-25: Consider Database Design for Categories and Tags

Indexing array columns like categories and tags may not provide efficient query performance. For better scalability and query optimization, consider normalizing your database schema using many-to-many relationships with join tables for categories and tags.

Note: As per the retrieved learnings, the Tool model and its indexes are temporary placeholders. Keep this advice in mind when designing the final database schema.

Also applies to: 28-28, 31-31

src/app/tools/[slug]/loading.tsx (3)

3-4: Impr 8000 ove mobile responsiveness with proper padding

The container lacks proper padding for mobile screens. Consider adding base padding for mobile views.

-    <div className="w-full h-screen flex flex-col justify-start items-start md:px-72 pt-16">
+    <div className="w-full h-screen flex flex-col justify-start items-start px-4 md:px-72 pt-16">

13-30: Consider extracting repeated layout patterns into components

The flex container structure is duplicated. Consider creating a reusable component for these sections and using CSS Grid for better layout control.

Example component structure:

type SkeletonGroupProps = {
  title: string;
  count: number;
};

function SkeletonGroup({ title, count }: SkeletonGroupProps) {
  return (
    <div className="flex flex-col flex-1 gap-2">
      <div className={`${skeletonClass} w-20 h-6`}></div>
      <div className="flex items-center gap-4 rounded-lg w-full">
        {Array.from({ length: count }).map((_, i) => (
          <div key={i} className={`${skeletonClass} w-20 h-6`}></div>
        ))}
      </div>
    </div>
  );
}

1-2: Enhance code organization and type safety

Consider adding TypeScript types and proper documentation to improve maintainability.

+import { FC } from 'react';
+
+/**
+ * Loading skeleton component for the tool details page.
+ * Displays a placeholder UI while the actual content is being loaded.
+ */
-export default function Loading() {
+const Loading: FC = () => {
   return (
     // ... component content
   );
-}
+};
+
+export default Loading;

Also applies to: 58-59

src/app/layout.tsx (2)

33-40: Consider adding more favicon sizes for better device support.

The current favicon configuration only includes a 32x32 size. Consider adding more sizes (16x16, 48x48, 192x192, 512x512) to support various devices and scenarios (PWA, mobile bookmarks, etc.).

  icons: [
    {
      rel: 'icon',
      type: 'image/png',
      sizes: '32x32',
      url: '/favicon.png',
    },
+   {
+     rel: 'icon',
+     type: 'image/png',
+     sizes: '16x16',
+     url: '/favicon-16x16.png',
+   },
+   {
+     rel: 'icon',
+     type: 'image/png',
+     sizes: '192x192',
+     url: '/favicon-192x192.png',
+   },
+   {
+     rel: 'apple-touch-icon',
+     sizes: '180x180',
+     url: '/apple-touch-icon.png',
+   },
  ],

48-53: Consider adding JSDoc documentation.

The function handles different title formats well, but could benefit from documentation explaining the different cases it handles.

+/**
+ * Extracts the title from the metadata object.
+ * @param title - The title from metadata, can be either a string or an object with a default property
+ * @returns The extracted title string or a default value
+ */
 function getTitle(title: Metadata['title']): string {
src/components/tools/ToolsPagination.tsx (5)

26-26: Remove console.log statement.

Debug logging should be removed before production deployment.

-console.log({ totalPages })

21-25: Add validation for basePath parameter.

The URL creation doesn't validate the basePath format, which could lead to invalid URLs if the input is malformed.

 const createPageUrl = (pageNumber: number) => {
+  const cleanBasePath = basePath.startsWith('/') ? basePath : `/${basePath}`;
   const params = new URLSearchParams(searchParams.toString());
   params.set('page', pageNumber.toString());
-  return `${basePath}?${params.toString()}`;
+  return `${cleanBasePath}?${params.toString()}`;
 };

28-49: Improve maintainability and performance of page number calculation.

Consider the following improvements:

  1. Define constants for ellipsis markers
  2. Memoize the calculation to prevent unnecessary recalculations
+const ELLIPSIS_START = -1;
+const ELLIPSIS_END = -2;
+
+const getPageNumbers = React.useMemo(() => {
-const getPageNumbers = () => {
   const pages = [];

   if (totalPages > 0) pages.push(1);

   const startPage = Math.max(2, currentPage - 2);
   const endPage = Math.min(totalPages, currentPage + 2);

-  if (startPage > 2) pages.push(-1);
+  if (startPage > 2) pages.push(ELLIPSIS_START);

   for (let i = startPage; i <= endPage; i++) {
     if (i !== 1 && i !== totalPages) {
       pages.push(i);
     }
   }

-  if (endPage < totalPages - 1) pages.push(-2);
+  if (endPage < totalPages - 1) pages.push(ELLIPSIS_END);

   if (totalPages > 1) pages.push(totalPages);

   return pages;
-};
+}, [currentPage, totalPages]);

51-94: Enhance accessibility with descriptive labels.

While the current implementation includes basic accessibility attributes, it could be improved with more descriptive aria-labels.

 <PaginationPrevious
   href={currentPage > 1 ? createPageUrl(currentPage - 1) : undefined}
   aria-disabled={currentPage <= 1}
+  aria-label={`Go to page ${currentPage - 1}`}
   className={currentPage <= 1 ? "pointer-events-none opacity-50" : ""}
 />
 
 <PaginationNext
   href={currentPage < totalPages ? createPageUrl(currentPage + 1) : undefined}
   aria-disabled={currentPage >= totalPages}
+  aria-label={`Go to page ${currentPage + 1}`}
   className={currentPage >= totalPages ? "pointer-events-none opacity-50" : ""}
 />

17-96: Consider adding error boundary for pagination failures.

As this component handles URL parameters and calculations that could potentially fail, consider wrapping it in an error boundary component to gracefully handle and recover from runtime errors.

src/components/tools/SearchBar.tsx (2)

12-14: Consider adding input validation

The search input could benefit from basic sanitization to prevent injection or handle special characters appropriately.

 const handleSearchChange = (e: React.ChangeEvent<HTMLInputElement>) => {
-  setSearchValue(e.target.value)
+  const sanitizedValue = e.target.value.replace(/[<>]/g, '')
+  setSearchValue(sanitizedValue)
 }

34-48: LGTM! Well-implemented effects with proper cleanup

The debouncing implementation is solid. Consider extracting the debounce time as a constant for easier configuration:

+const SEARCH_DEBOUNCE_MS = 300
+
 useEffect(() => {
   const debounceSearch = setTimeout(() => {
     updateSearchQuery(searchValue.trim())
-  }, 300) // Increased debounce time for better performance
+  }, SEARCH_DEBOUNCE_MS)

   return () => clearTimeout(debounceSearch)
 }, [searchValue, updateSearchQuery])
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 9199bdc and 26c2d67.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (32)
  • components.json (1 hunks)
  • package.json (2 hunks)
  • prisma/migrations/20241119120057_tool_temp_added/migration.sql (1 hunks)
  • prisma/migrations/20241123141312_categories_table_temp_added/migration.sql (1 hunks)
  • prisma/schema.prisma (1 hunks)
  • src/app/api/tools/categories/route.ts (1 hunks)
  • src/app/api/tools/route.ts (1 hunks)
  • src/app/api/tools/similar/route.ts (1 hunks)
  • src/app/layout.tsx (1 hunks)
  • src/app/page.tsx (1 hunks)
  • src/app/tools/[slug]/loading.tsx (1 hunks)
  • src/app/tools/[slug]/page.tsx (1 hunks)
  • src/app/tools/layout.tsx (1 hunks)
  • src/app/tools/loading.tsx (1 hunks)
  • src/app/tools/page.tsx (1 hunks)
  • src/components/tools/AlgoliaSearch.tsx (1 hunks)
  • src/components/tools/Categories.tsx (1 hunks)
  • src/components/tools/LoadingCategories.tsx (1 hunks)
  • src/components/tools/SearchBar.tsx (1 hunks)
  • src/components/tools/ToolCard.tsx (1 hunks)
  • src/components/tools/ToolSkeleton.tsx (1 hunks)
  • src/components/tools/ToolsFilter.tsx (1 hunks)
  • src/components/tools/ToolsPage.tsx (1 hunks)
  • src/components/tools/ToolsPagination.tsx (1 hunks)
  • src/components/tools/toolDetails/SimilarTools.tsx (1 hunks)
  • src/components/tools/toolDetails/ToolDetailsPage.tsx (1 hunks)
  • src/components/tools/toolDetails/ToolNotFound.tsx (1 hunks)
  • src/components/ui/pagination.tsx (1 hunks)
  • src/components/ui/popover.tsx (1 hunks)
  • src/lib/tools.ts (1 hunks)
  • src/lib/types.ts (1 hunks)
  • tailwind.config.ts (1 hunks)
✅ Files skipped from review due to trivial changes (6)
  • tailwind.config.ts
  • src/app/page.tsx
  • src/app/tools/loading.tsx
  • src/components/tools/LoadingCategories.tsx
  • src/components/tools/toolDetails/ToolNotFound.tsx
  • components.json
🧰 Additional context used
📓 Learnings (3)
prisma/schema.prisma (1)
Learnt from: Ankur1493
PR: tyaga001/devtoolsacademy#39
File: prisma/schema.prisma:52-54
Timestamp: 2024-11-23T12:12:03.854Z
Learning: In `prisma/schema.prisma`, the `Tool` model and its indexes are temporary placeholders, and the proper database structure will be added later.
src/app/tools/[slug]/page.tsx (2)
Learnt from: Ankur1493
PR: tyaga001/devtoolsacademy#39
File: src/app/tools/[slug]/page.tsx:13-35
Timestamp: 2024-11-23T12:00:26.545Z
Learning: In `src/app/tools/[slug]/page.tsx`, the page is server-side rendered and `toolDetails` are passed as props, so adding client-side loading states using React Suspense isn't necessary.
Learnt from: Ankur1493
PR: tyaga001/devtoolsacademy#39
File: src/app/tools/[slug]/page.tsx:15-16
Timestamp: 2024-11-23T12:04:31.686Z
Learning: In `src/app/tools/[slug]/page.tsx`, since `getToolDetails` is a server action, explicit error handling with try-catch is not necessary.
prisma/migrations/20241119120057_tool_temp_added/migration.sql (1)
Learnt from: Ankur1493
PR: tyaga001/devtoolsacademy#39
File: prisma/schema.prisma:52-54
Timestamp: 2024-11-23T12:12:03.854Z
Learning: In `prisma/schema.prisma`, the `Tool` model and its indexes are temporary placeholders, and the proper database structure will be added later.
🪛 Biome (1.9.4)
src/app/api/tools/route.ts

[error] 47-47: Useless case clause.

because the default clause is present:

Unsafe fix: Remove the useless case.

(lint/complexity/noUselessSwitchCase)

🔇 Additional comments (10)
src/app/tools/layout.tsx (1)

1-8: Code looks good!

The ToolsLayout component is correctly defined and structured. The use of children prop and the background styling is appropriate.

src/components/tools/AlgoliaSearch.tsx (1)

3-3: Verify component compatibility between server and client components

Importing ToolsFilter into AlgoliaSearch may cause issues if ToolsFilter is a client component and AlgoliaSearch is a server component. Ensure that AlgoliaSearch is declared as a client component if necessary.

Consider adding the "use client" directive at the top of the file if AlgoliaSearch needs to be a client component:

+"use client"
 import React from 'react'

Alternatively, if ToolsFilter is a server component, no action is needed.

package.json (1)

24-24: New dependencies added successfully

The new dependencies are properly added to the dependencies section.

Also applies to: 30-30, 47-47

src/components/ui/pagination.tsx (1)

1-117: Well-structured and accessible pagination components

The pagination components are well-implemented with proper accessibility attributes (aria-label, aria-current) and semantic HTML elements. The use of React.forwardRef and TypeScript typing enhances maintainability and usability.

src/app/layout.tsx (3)

44-44: LGTM!

The interface is properly typed and follows TypeScript best practices.


75-85: LGTM! Well-structured layout implementation.

The layout implementation follows Next.js best practices with:

  • Proper font configuration
  • Semantic HTML structure
  • Accessible container structure
  • Proper analytics integration

69-73: Verify Umami analytics configuration.

The analytics script uses an environment variable. Ensure it's properly configured in all environments.

src/components/tools/ToolsPagination.tsx (1)

1-15: LGTM! Well-structured imports and interface definition.

The component's dependencies and type definitions are clear and properly organized.

src/components/tools/SearchBar.tsx (2)

1-10: LGTM! Component setup is well-structured

The component is properly configured for client-side rendering with appropriate hooks and state initialization from URL parameters.


16-32: Verify UX behavior and enhance URL handling

The current implementation clears all filters when performing a search. While this might simplify the user experience, it could be unexpected for users who want to combine search with filters.

Additionally, consider enhancing URL parameter handling:

 const updateSearchQuery = useCallback((query: string) => {
   const params = new URLSearchParams(searchParams)
   const hasFilters = params.has("categories") || params.has("tags") || params.has("sort")

   if (query) {
-    if (hasFilters) {
-      params.delete("categories")
-      params.delete("tags")
-      params.delete("sort")
-    }
     params.set('query', query)
   } else {
     params.delete('query')
   }

+  // Ensure clean URLs by removing empty parameters
+  Array.from(params.keys()).forEach(key => {
+    const value = params.get(key)
+    if (!value || value.length === 0) params.delete(key)
+  })

   router.push(`/tools?${params.toString()}`)
 }, [router, searchParams])

Let's verify the current behavior's impact on user experience:

✅ Verification successful

Keep filter clearing behavior and enhance URL handling

The search results reveal that the current behavior of clearing filters during search is intentionally designed as part of a reciprocal relationship between search and filters:

  1. In ToolsFilter.tsx, when filters are applied, the search query is cleared: params.delete("query")
  2. Similarly, in SearchBar.tsx, when searching, filters are cleared.

This mutual exclusivity appears to be a deliberate UX pattern in the application. However, the URL parameter cleanup suggestion is still valuable:

 const updateSearchQuery = useCallback((query: string) => {
   const params = new URLSearchParams(searchParams)
   const hasFilters = params.has("categories") || params.has("tags") || params.has("sort")

   if (query) {
     if (hasFilters) {
       params.delete("categories")
       params.delete("tags")
       params.delete("sort")
     }
     params.set('query', query)
   } else {
     params.delete('query')
   }

+  // Ensure clean URLs by removing empty parameters
+  Array.from(params.keys()).forEach(key => {
+    const value = params.get(key)
+    if (!value || value.length === 0) params.delete(key)
+  })

   router.push(`/tools?${params.toString()}`)
 }, [router, searchParams])
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if there are any existing implementations combining search with filters
rg -l "searchParams.*categories.*query|query.*categories" --type ts

Length of output: 69


Script:

#!/bin/bash
# Let's try a broader search to understand the search and filter interaction patterns
# First, let's check for files that handle both search queries and categories/filters
rg "query.*categories|categories.*query" --type ts -A 5

# Also check for any components or hooks that might handle filtering logic
fd -e ts -e tsx -x rg -l "useFilter|handleFilter|applyFilter"

# Look for URL parameter handling patterns
rg "URLSearchParams.*set|params\.set" --type ts -A 3

Length of output: 2022

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 and nitpick comments (7)
src/lib/ZodSchema.ts (1)

3-7: Consider enhancing slug validation

While the current validation ensures non-empty slugs, consider adding format validation to ensure consistent URL-friendly slugs.

-  slug: z.string().min(1, { message: "slug cannot be less than 1 character" }),
+  slug: z.string()
+    .min(1, { message: "slug cannot be less than 1 character" })
+    .regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, { 
+      message: "slug must be lowercase alphanumeric with optional hyphens" 
+    }),
src/components/tools/ToolsFilter.tsx (1)

43-62: Add loading state when updating filters

The filter updates should show a loading state to prevent multiple rapid clicks.

+  const [isUpdating, setIsUpdating] = useState(false);

   const updateFilters = async (type: "categories" | "tags", value: string) => {
+    if (isUpdating) return;
+    setIsUpdating(true);
     const updateState =
       type === "categories" ? setSelectedCategories : setSelectedTags;
     // ... rest of the function
     updateURLWithParams(params);
+    setIsUpdating(false);
   };
src/components/tools/toolDetails/ToolDetailsPage.tsx (1)

247-253: Improve loading state for Similar Tools component.

The current loading fallback is too simple and might cause layout shifts.

Consider implementing a skeleton loader that matches the final content layout:

const SimilarToolsSkeleton = () => (
  <div className="grid grid-cols-1 md:grid-cols-3 gap-4 animate-pulse">
    {[1, 2, 3].map((i) => (
      <div key={i} className="h-48 bg-gray-800 rounded-lg" />
    ))}
  </div>
);
- <Suspense fallback={<div>loading...</div>}>
+ <Suspense fallback={<SimilarToolsSkeleton />}>
src/app/api/tools/route.ts (4)

18-37: Add type safety to the where clause construction

Using any type for the where clause loses type safety. Consider using Prisma's generated types.

Apply this diff:

-    const where: any = {};
+    const where: Prisma.ToolWhereInput = {};

Add this import at the top:

+import { Prisma } from '@prisma/client';

39-50: Simplify switch statement by removing redundant case

The 'recent' case is redundant as it's handled by the default case.

Apply this diff:

     switch (sort) {
       case 'popular':
         orderBy = { stars: 'desc' };
         break;
       case 'alphabetical':
         orderBy = { name: 'asc' };
         break;
-      case 'recent':
       default:
         orderBy = { lastUpdated: 'desc' };
     }
🧰 Tools
🪛 Biome (1.9.4)

[error] 47-47: Useless case clause.

because the default clause is present:

Unsafe fix: Remove the useless case.

(lint/complexity/noUselessSwitchCase)


52-71: Consider adding database indexes for better query performance

The query sorts by stars, name, and lastUpdated. Adding indexes for these fields would improve query performance.

Consider adding these indexes to your Prisma schema:

model Tool {
  // ... existing fields ...
  
  @@index([stars])
  @@index([name])
  @@index([lastUpdated])
}

86-92: Enhance error handling with specific error types

The current error handling is generic. Consider categorizing errors and providing more specific error messages.

Apply this diff:

-  } catch (error: any) {
-    console.error('Error in API:', error.response?.data || error.message);
-    return NextResponse.json(
-      { error: 'Error processing request. Please try again.' },
-      { status: error.response?.status || 500 }
-    );
+  } catch (error) {
+    console.error('Error in API:', error);
+    if (error instanceof Prisma.PrismaClientKnownRequestError) {
+      return NextResponse.json(
+        { error: 'Database operation failed. Please try again.' },
+        { status: 500 }
+      );
+    }
+    if (error instanceof Prisma.PrismaClientValidationError) {
+      return NextResponse.json(
+        { error: 'Invalid query parameters.' },
+        { status: 400 }
+      );
+    }
+    return NextResponse.json(
+      { error: 'Internal server error.' },
+      { status: 500 }
+    );
   }
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 26c2d67 and 826b6a2.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (8)
  • package.json (2 hunks)
  • src/app/api/tools/route.ts (1 hunks)
  • src/app/api/tools/similar/route.ts (1 hunks)
  • src/components/tools/ToolCard.tsx (1 hunks)
  • src/components/tools/ToolsFilter.tsx (1 hunks)
  • src/components/tools/toolDetails/SimilarTools.tsx (1 hunks)
  • src/components/tools/toolDetails/ToolDetailsPage.tsx (1 hunks)
  • src/lib/ZodSchema.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/app/api/tools/similar/route.ts
  • package.json
  • src/components/tools/toolDetails/SimilarTools.tsx
  • src/components/tools/ToolCard.tsx
🧰 Additional context used
🪛 Biome (1.9.4)
src/app/api/tools/route.ts

[error] 47-47: Useless case clause.

because the default clause is present:

Unsafe fix: Remove the useless case.

(lint/complexity/noUselessSwitchCase)


[error] 7-7: isNaN is unsafe. It attempts a type coercion. Use Number.isNaN instead.

See the MDN documentation for more details.
Unsafe fix: Use Number.isNaN instead.

(lint/suspicious/noGlobalIsNan)

🔇 Additional comments (4)
src/components/tools/ToolsFilter.tsx (2)

70-81: Ensure clearFilters resets all filters and uses consistent routing

The clearFilters function uses window.location.search instead of the searchParams hook.


37-41: Verify search parameter handling

Let's verify that all search parameters are properly handled and maintained.

✅ Verification successful

Search parameter handling is properly implemented and consistent

The verification shows that search parameters are handled consistently across the codebase:

  • All relevant parameters (page, query, categories, tags, sort) are properly managed in both frontend components and API routes
  • Parameter updates follow a consistent pattern:
    • ToolsFilter.tsx resets page to "1" when filters change
    • Query parameter is explicitly handled (set/delete) in SearchBar.tsx
    • URL construction is consistent using params.toString()
  • Parameter synchronization is maintained between components through URL state
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for consistent search parameter handling across the codebase

# Check for all URL parameter usage
rg -A 3 "searchParams.get\(" 

# Check for parameter update patterns
rg -A 3 "params.set\("

# Check for proper URL construction
rg -A 3 "router.push\("

Length of output: 5662

src/components/tools/toolDetails/ToolDetailsPage.tsx (1)

141-143: Ensure numerical values for tool.stars and tool.forks.

src/app/api/tools/route.ts (1)

1-4: LGTM! Clean imports and well-typed function signature.

tyaga001
tyaga001 previously approved these changes Dec 2, 2024
Copy link
vercel bot commented Dec 2, 2024

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
devtoolsacademy ✅ Ready (Inspect) Visit Preview 💬 Add feedback Dec 16, 2024 0:01am

@tyaga001
Copy link
Owner
tyaga001 commented Dec 4, 2024

@Ankur1493 when I tried to test the changes locally I found a couple of issues:

  1. tools page isn't accessible from the navbar. You'll need to add it to the navItems array in components/Navbar.tsx alongside Home and Blog.

  2. I can see the tools page UI with search, filters and sorting functionality, there's no data being displayed. Please check:

    • Data fetching implementation
    • Database connection setup
    • Sample/seed data for testing
      image

check on priority let merge this today.

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

🧹 Outside diff range and nitpick comments (1)
src/components/Navbar.tsx (1)

36-43: Add aria-label to improve accessibility of the GitHub button

While the implementation is good, consider adding an aria-label to improve accessibility for screen readers.

 <Button
   onClick={() => window.open("https://github.com/tyaga001/devtoolsacademy", "_blank", "noopener,noreferrer")}
   variant="outline"
+  aria-label="Star DevTools Academy on GitHub"
   className="flex items-center space-x-2 bg-transparent border border-purple-400 text-purple-400 hover:bg-purple-400 hover:text-gray-900 transition duration-150 ease-in-out"
 >
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 826b6a2 and c222372.

📒 Files selected for processing (1)
  • src/components/Navbar.tsx (1 hunks)
🔇 Additional comments (3)
src/components/Navbar.tsx (3)

10-18: LGTM! The Tools navigation item has been properly integrated.

The addition of the Tools navigation item aligns with the PR objectives and follows the existing pattern.


56-68: LGTM! Mobile menu button implementation is accessible and well-structured.

The mobile menu button follows accessibility best practices with proper aria attributes and focus management.


17-17: Verify Tools page routing and implementation

The Tools navigation item has been added as requested in the PR objectives. Let's verify the implementation:

✅ Verification successful

Tools page implementation and routing are properly set up

The Tools navigation item is correctly implemented with proper routing and components:

  • /src/app/tools/page.tsx exists and imports the main ToolsPage component
  • The app follows Next.js 13+ file-based routing structure with proper loading states (loading.tsx)
  • Complete implementation includes components for pagination, filtering, and tool details
  • Dynamic routing is supported via [slug] for individual tool pages
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if the Tools page component exists and is properly implemented
fd -t f "tools/page.tsx" "src/app"

# Check for any potential routing issues
rg -l "useRouter.*tools" "src/app"

Length of output: 67


Script:

#!/bin/bash
# Let's check if the Tools page exists with different possible naming patterns
fd -t f -e tsx -e ts -e jsx -e js "tools" "src/app"

# Check for any route definitions or configurations
fd -t f "route" "src/app"

# Look for any Tools-related component implementations
rg -i "tools.*page|page.*tools" --type ts --type tsx

# Check for any router configurations or route definitions
rg -i "route.*tools|tools.*route" --type ts --type tsx

Length of output: 436


Script:

#!/bin/bash
# Let's check for the Tools page component with a broader search
fd "tools" "src"

# Look for Tools page implementation without file type restrictions
rg -i "tools.*page|page.*tools"

# Check the app directory structure
fd -t d "tools" "src/app"

# Check if there are any route handlers or components in the tools directory
rg -l "export.*default" "src/app/tools"

Length of output: 1653

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

🧹 Outside diff range and nitpick comments (14)
src/components/tools/ToolsFilter.tsx (5)

39-45: Add error handling for malformed URL parameters

The current implementation assumes URL parameters are well-formed. Consider adding validation and error handling:

 const [selectedCategories, setSelectedCategories] = useState<string[]>(
-  searchParams.get("categories")?.split(",").filter(Boolean) || []
+  validateAndFilterParams(searchParams.get("categories"), TOOL_CATEGORIES)
 );
 const [selectedTags, setSelectedTags] = useState<string[]>(
-  searchParams.get("tags")?.split(",").filter(Boolean) || []
+  validateAndFilterParams(searchParams.get("tags"), TOOL_TAGS)
 );

Add this utility function:

function validateAndFilterParams(param: string | null, allowedValues: string[]): string[] {
  if (!param) return [];
  return param.split(",")
    .filter(Boolean)
    .filter(value => allowedValues.includes(value));
}

93-96: Use searchParams instead of window.location.search

For consistency with the rest of the code, use the searchParams object:

- const params = new URLSearchParams(window.location.search);
+ const params = new URLSearchParams(searchParams.toString());

55-59: Consider debouncing URL updates

Multiple rapid filter changes could lead to unnecessary route updates. Consider implementing debouncing:

import { debounce } from 'lodash';

const debouncedUpdateURL = debounce((params: URLSearchParams) => {
  params.set("page", "1");
  params.delete("query");
  router.push(`/tools?${params.toString()}`);
}, 300);

232-238: Improve accessibility of remove filter buttons

The 'x' character isn't very accessible. Consider using a more semantic close icon:

 <button
   onClick={() => updateFilters("tags", item)}
   className="hover:text-red-400 transition-colors duration-200"
   aria-label={`Remove ${item} filter`}
 >
-  x
+  <svg
+    className="w-4 h-4"
+    fill="none"
+    stroke="currentColor"
+    viewBox="0 0 24 24"
+  >
+    <path
+      strokeLinecap="round"
+      strokeLinejoin="round"
+      strokeWidth={2}
+      d="M6 18L18 6M6 6l12 12"
+    />
+  </svg>
 </button>

199-213: Add keyboard navigation for sort options

The sort options menu should be navigable via keyboard:

 {["recent", "popular", "alphabetical"].map((option, index) => (
   <button
     key={option}
+    role="menuitem"
+    tabIndex={0}
+    onKeyDown={(e) => {
+      if (e.key === 'Enter' || e.key === ' ') {
+        updateSort(option);
+        setSelectedSort(option);
+      }
+    }}
     className="px-3 py-2 text-left hover:bg-gray-100 hover:bg-opacity-10 rounded transition-colors duration-200 capitalize flex items-center justify-between text-xs md:text-sm"
     onClick={() => {
       updateSort(option);
       setSelectedSort(option);
     }}
   >
src/components/tools/Categories.tsx (2)

19-56: Move helper functions to a separate utility file

Consider extracting these utility functions to a separate file (e.g., src/lib/utils.ts) to improve code organization and reusability.

+ // src/lib/utils.ts
+ export const getRandomColor = () => { ... }
+ export const getRandomIcon = () => { ... }
+ export const capitalizeFirstLetter = (string: string) => { ... }

- // Current file
- const getRandomColor = () => { ... }
- const getRandomIcon = () => { ... }
- const capitalizeFirstLetter = (string: string) => { ... }
+ import { getRandomColor, getRandomIcon, capitalizeFirstLetter } from "@/lib/utils";

58-121: Add TypeScript interfaces and documentation

Consider adding TypeScript interfaces for better type safety and JSDoc documentation for better code documentation.

+ interface Category {
+   name: string;
+   count: number;
+ }

+ interface ToolCategoriesResponse {
+   status: boolean;
+   categories: Category[];
+ }

+ /**
+  * Categories component displays a grid of tool categories with their counts
+  * @returns JSX.Element
+  */
- const Categories = async () => {
+ const Categories = async (): Promise<JSX.Element> => {
-   const response = await getToolCategories();
+   const response: ToolCategoriesResponse = await getToolCategories();
prisma/schema.prisma (2)

35-57: Tool model structure looks good for MVP

The model includes essential fields and appropriate indexes for search functionality. Good use of cuid() for ID generation and proper timestamp handling.

Consider these future improvements:

  1. Add min/max length constraints for name, headline, and description
  2. Consider adding a relation to Categories model instead of using String[] for categories
  3. Add URL validation for githubUrl, websiteUrl, and documentation

59-66: Consider adding validation for Categories model

The model structure is clean, but could benefit from data validation.

Consider adding these constraints:

model Categories {
  id          String @id @default(uuid())
-  name        String @unique
+  name        String @unique @db.VarChar(50)
-  count       Int
+  count       Int    @default(0)

  createdAt   DateTime @default(now())
  updatedAt   DateTime @updatedAt
}
src/components/tools/ToolCard.tsx (2)

17-21: Consider adding error handling for tool logos

While the logo URLs are controlled by the backend (as per learnings), we should still handle failed image loads gracefully.

<img
  src={`/images/logo/${tool.name}.png`}
  alt={`${tool.name} logo`}
+ onError={(e) => {
+   e.currentTarget.src = '/images/default-tool-logo.png';
+ }}
  className="w-10 h-10 object-cover border border-white border-opacity-10 rounded"
/>

13-14: Add aria-label for better accessibility

The link should have a descriptive aria-label for screen readers.

- <Link href={`/tools/${encodeURIComponent(tool.name)}`} className="block">
+ <Link 
+   href={`/tools/${encodeURIComponent(tool.name)}`} 
+   className="block"
+   aria-label={`View details for ${tool.name}`}
+ >
src/components/tools/toolDetails/ToolDetailsPage.tsx (2)

142-148: Improve date handling for lastUpdated.

The current date formatting logic could be moved to a reusable utility function and include better error handling.

Apply this diff:

+const formatDate = (date: string | Date) => {
+  try {
+    return new Date(date).toLocaleDateString("en-US", {
+      month: "short",
+      day: "numeric",
+      year: "numeric",
+    });
+  } catch {
+    return "N/A";
+  }
+};

-{tool.lastUpdated
-  ? new Date(tool.lastUpdated).toLocaleDateString("en-US", {
-    month: "short",
-    day: "numeric",
-    year: "numeric",
-  })
-  : "N/A"}
+{formatDate(tool.lastUpdated)}

153-176: Remove or document commented-out code.

Large blocks of commented-out code make the codebase harder to maintain. Either remove this code if it's no longer needed or document why it's being preserved.

src/app/api/tools/route.ts (1)

1014-1020: Improve error handling with specific error messages.

The current error handling doesn't provide specific information about what went wrong, making debugging difficult.

Apply this diff:

   } catch (error: any) {
-    console.error('Error in API:', error.message);
+    const errorMessage = error instanceof Error 
+      ? error.message 
+      : 'Unknown error occurred';
+    console.error('Error in API:', errorMessage);
+    
+    if (error.code === 'P2002') {
+      return NextResponse.json(
+        { error: 'A tool with this name already exists.' },
+        { status: 409 }
+      );
+    }
+
     return NextResponse.json(
-      { error: 'Error processing request. Please try again.' },
+      { error: `Error processing request: ${errorMessage}` },
       { status: error.response?.status || 500 }
     );
   }
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c222372 and 69c1bf3.

⛔ Files ignored due to path filters (60)
  • public/images/img/Airtable.png is excluded by !**/*.png
  • public/images/img/Aiven.png is excluded by !**/*.png
  • public/images/img/AppoloGraphQL.png is excluded by !**/*.png
  • public/images/img/Appsmith.png is excluded by !**/*.png
  • public/images/img/Appwrite.png is excluded by !**/*.png
  • public/images/img/Cal.png is excluded by !**/*.png
  • public/images/img/Clerk.png is excluded by !**/*.png
  • public/images/img/Firebase.png is excluded by !**/*.png
  • public/images/img/Formbricks.png is excluded by !**/*.png
  • public/images/img/HarperDB.png is excluded by !**/*.png
  • public/images/img/Hygraph.png is excluded by !**/*.png
  • public/images/img/Liveblocks.png is excluded by !**/*.png
  • public/images/img/Livekit.png is excluded by !**/*.png
  • public/images/img/Medusa.png is excluded by !**/*.png
  • public/images/img/MindsDB.png is excluded by !**/*.png
  • public/images/img/Neon.png is excluded by !**/*.png
  • public/images/img/NocoDB.png is excluded by !**/*.png
  • public/images/img/Oneentry.png is excluded by !**/*.png
  • public/images/img/Plausible.png is excluded by !**/*.png
  • public/images/img/Posthog.png is excluded by !**/*.png
  • public/images/img/RisingWave.png is excluded by !**/*.png
  • public/images/img/Sanity.png is excluded by !**/*.png
  • public/images/img/Shopify.png is excluded by !**/*.png
  • public/images/img/Strapi.png is excluded by !**/*.png
  • public/images/img/Stream.png is excluded by !**/*.png
  • public/images/img/Supabase.png is excluded by !**/*.png
  • public/images/img/Twilio.png is excluded by !**/*.png
  • public/images/img/Typebot.png is excluded by !**/*.png
  • public/images/img/Typeform.png is excluded by !**/*.png
  • public/images/img/UseFathom.png is excluded by !**/*.png
  • public/images/logo/Airtable.png is excluded by !**/*.png
  • public/images/logo/Aiven.png is excluded by !**/*.png
  • public/images/logo/AppoloGraphQL.png is excluded by !**/*.png
  • public/images/logo/Appsmith.png is excluded by !**/*.png
  • public/images/logo/Appwrite.png is excluded by !**/*.png
  • public/images/logo/Cal.png is excluded by !**/*.png
  • public/images/logo/Clerk.png is excluded by !**/*.png
  • public/images/logo/Firebase.png is excluded by !**/*.png
  • public/images/logo/Formbricks.png is excluded by !**/*.png
  • public/images/logo/HarperDB.png is excluded by !**/*.png
  • public/images/logo/Hygraph.png is excluded by !**/*.png
  • public/images/logo/Liveblocks.png is excluded by !**/*.png
  • public/images/logo/Livekit.png is excluded by !**/*.png
  • public/images/logo/Medusa.png is excluded by !**/*.png
  • public/images/logo/MindsDB.png is excluded by !**/*.png
  • public/images/logo/Neon.png is excluded by !**/*.png
  • public/images/logo/NocoDB.png is excluded by !**/*.png
  • public/images/logo/Oneentry.png is excluded by !**/*.png
  • public/images/logo/Plausible.png is excluded by !**/*.png
  • public/images/logo/Posthog.png is excluded by !**/*.png
  • public/images/logo/RisingWave.png is excluded by !**/*.png
  • public/images/logo/Sanity.png is excluded by !**/*.png
  • public/images/logo/Shopify.png is excluded by !**/*.png
  • public/images/logo/Strapi.png is excluded by !**/*.png
  • public/images/logo/Stream.png is excluded by !**/*.png
  • public/images/logo/Supabase.png is excluded by !**/*.png
  • public/images/logo/Twilio.png is excluded by !**/*.png
  • public/images/logo/Typebot.png is excluded by !**/*.png
  • public/images/logo/Typeform.png is excluded by !**/*.png
  • public/images/logo/UseFathom.png is excluded by !**/*.png
📒 Files selected for processing (15)
  • prisma/migrations/20241214052435_features_added/migration.sql (1 hunks)
  • prisma/schema.prisma (1 hunks)
  • src/app/api/tools/route.ts (1 hunks)
  • src/app/page.tsx (1 hunks)
  • src/app/tools/[slug]/loading.tsx (1 hunks)
  • src/components/tools/Categories.tsx (1 hunks)
  • src/components/tools/LoadingCategories.tsx (1 hunks)
  • src/components/tools/SearchBar.tsx (1 hunks)
  • src/components/tools/ToolCard.tsx (1 hunks)
  • src/components/tools/ToolSkeleton.tsx (1 hunks)
  • src/components/tools/ToolsFilter.tsx (1 hunks)
  • src/components/tools/ToolsPage.tsx (1 hunks)
  • src/components/tools/toolDetails/SimilarTools.tsx (1 hunks)
  • src/components/tools/toolDetails/ToolDetailsPage.tsx (1 hunks)
  • src/lib/toolData.json (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (7)
  • src/app/tools/[slug]/loading.tsx
  • src/components/tools/ToolSkeleton.tsx
  • src/components/tools/LoadingCategories.tsx
  • src/components/tools/SearchBar.tsx
  • src/components/tools/toolDetails/SimilarTools.tsx
  • src/components/tools/ToolsPage.tsx
  • src/app/page.tsx
🧰 Additional context used
📓 Learnings (2)
prisma/schema.prisma (1)
Learnt from: Ankur1493
PR: tyaga001/devtoolsacademy#39
File: prisma/schema.prisma:52-54
Timestamp: 2024-11-23T12:12:03.854Z
Learning: In `prisma/schema.prisma`, the `Tool` model and its indexes are temporary placeholders, and the proper database structure will be added later.
src/components/tools/ToolCard.tsx (1)
Learnt from: Ankur1493
PR: tyaga001/devtoolsacademy#44
File: src/components/tools/ToolCard.tsx:19-23
Timestamp: 2024-11-30T20:29:45.779Z
Learning: In the `src/components/tools/ToolCard.tsx` file, it's acceptable to use `tool.logo` directly in the `img` `src` attribute without additional validation because the image URLs are controlled by the backend, and overengineering is to be avoided before the MVP is complete.
🪛 Biome (1.9.4)
src/app/api/tools/route.ts

[error] 47-47: Useless case clause.

because the default clause is present:

Unsafe fix: Remove the useless case.

(lint/complexity/noUselessSwitchCase)


[error] 7-7: isNaN is unsafe. It attempts a type coercion. Use Number.isNaN instead.

See the MDN documentation for more details.
Unsafe fix: Use Number.isNaN instead.

(lint/suspicious/noGlobalIsNan)

src/components/tools/ToolCard.tsx

[error] 30-38: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

🔇 Additional comments (7)
src/components/tools/ToolsFilter.tsx (1)

14-33: 🛠️ Refactor suggestion

Move categories and tags to a configuration file

Hardcoding these values makes maintenance difficult and could lead to inconsistencies. Consider:

  1. Moving these arrays to a configuration file
  2. Adding TypeScript types/enums
  3. Removing duplicate values between categories and tags (e.g., "api", "devops")

Create a new config file:

// src/config/tools.ts
export type Category = "tools" | "frontend" | "backend" | "api" | "devops" | "cloud" | "e-commerce" | "chat";
export type Tag = "postgreSQL" | "cloud-native" | "NoSQL" | "api" | "GraphQL" | "CMS" | "database" | "devops";

export const TOOL_CATEGORIES: Category[] = ["tools", "frontend", "backend", "api", "devops", "cloud", "e-commerce", "chat"];
export const TOOL_TAGS: Tag[] = ["postgreSQL", "cloud-native", "NoSQL", "api", "GraphQL", "CMS", "database", "devops"];
src/components/tools/Categories.tsx (2)

19-33: Consider using consistent category styling instead of random colors and icons

The randomization of colors and icons can lead to an inconsistent user experience, with styles changing on each render.

Also applies to: 35-52


73-76: Fix grammatical error in error message

The error message contains a grammatical error: "We couldn't able to find the categories for you."

src/components/tools/ToolCard.tsx (1)

74-76: Handle invalid date formats

This issue was previously identified in past review comments. The suggestion to handle invalid dates is still applicable.

src/components/tools/toolDetails/ToolDetailsPage.tsx (1)

22-34: ⚠️ Potential issue

Replace hardcoded language statistics with GitHub API data.

The language statistics and colors are currently hardcoded, which won't reflect the actual repository composition. Consider fetching this data from the GitHub API.

Apply this diff:

-const languageColors: { [key: string]: string } = {
-  JavaScript: "#f1e05a",
-  TypeScript: "#2b7489",
-  CSS: "#563d7c",
-  HTML: "#e34c26",
-};
-
-const languages: { [key: string]: number } = {
-  JavaScript: 70,
-  TypeScript: 20,
-  CSS: 5,
-  HTML: 5,
-};

+async function getLanguageStats(githubUrl: string) {
+  const [owner, repo] = githubUrl.split('/').slice(-2);
+  const response = await fetch(
+    `https://api.github.com/repos/${owner}/${repo}/languages`
+  );
+  const data = await response.json();
+  const total = Object.values(data).reduce((a: any, b) => a + b, 0);
+  return Object.entries(data).map(([name, bytes]) => ({
+    name,
+    percentage: ((bytes as number) / total) * 100,
+  }));
+}

Likely invalid or redundant comment.

src/lib/toolData.json (1)

298-319: ⚠️ Potential issue

Add missing categories field.

The Appwrite tool entry is missing the categories field while having tags, which could cause inconsistencies in filtering and display.

Add the categories field:

+    "categories": [
+      "open-source",
+      "backend"
+    ],
     "tags": [

Likely invalid or redundant comment.

src/app/api/tools/route.ts (1)

6-7: ⚠️ Potential issue

Replace unsafe isNaN with Number.isNaN.

Using the global isNaN function can lead to unexpected results due to type coercion.

🧰 Tools
🪛 Biome (1.9.4)

[error] 7-7: isNaN is unsafe. It attempts a type coercion. Use Number.isNaN instead.

See the MDN documentation for more details.
Unsafe fix: Use Number.isNaN instead.

(lint/suspicious/noGlobalIsNan)

Comment on lines +59 to +62
const response = await getToolCategories();
const variousCategories = response.categories;

if (!response.status) {
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 error handling for API response

The current error handling only checks response.status. Consider adding try-catch and additional error checks.

const Categories = async () => {
-  const response = await getToolCategories();
-  const variousCategories = response.categories;
-  if (!response.status) {
+  try {
+    const response = await getToolCategories();
+    if (!response?.status || !Array.isArray(response?.categories)) {
+      throw new Error('Invalid response format');
+    }
+    const variousCategories = response.categories;
+  } catch (error) {

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +92 to +114
<div className="grid gap-2 md:gap-4 grid-cols-2 lg:grid-cols-4">
{variousCategories?.map((category) => (
<Link
key={category.name}
href={`/tools?categories=${category.name}`}
>
<div className="flex items-center space-x-4 p-2 md:p-4 bg-card shadow hover:bg-[#141414] bg-opacity-20 rounded-xl transition-all duration-300 cursor-pointer">
<div
className={`${getRandomColor()} w-8 h-8 md:h-12 md:w-12 rounded-full flex items-center justify-center text-white`}
>
{getRandomIcon()}
</div>
<div>
<h3 className="text-sm md:text-lg font-semibold">
{capitalizeFirstLetter(category.name)}
</h3>
<p className="text-xs md:text-sm text-muted-foreground">
{category.count || 0} Categories
</p>
</div>
</div>
</Link>
))}
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 accessibility and add loading states

Consider the following improvements:

  1. Add ARIA labels for better screen reader support
  2. Include loading states while data is being fetched
  3. Add error boundaries to handle runtime errors
+ import { Suspense } from 'react';
+ import { ErrorBoundary } from 'react-error-boundary';
+ import LoadingCategories from './LoadingCategories';

  <div className="grid gap-2 md:gap-4 grid-cols-2 lg:grid-cols-4">
+   <ErrorBoundary fallback={<div>Something went wrong</div>}>
+   <Suspense fallback={<LoadingCategories />}>
    {variousCategories?.map((category) => (
      <Link
        key={category.name}
        href={`/tools?categories=${category.name}`}
+       aria-label={`View ${category.name} tools`}
      >

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +1 to +9
/*
Warnings:

- Added the required column `headline` to the `Tool` table without a default value. This is not possible if the table is not empty.

*/
-- AlterTable
ALTER TABLE "Tool" ADD COLUMN "features" TEXT[],
ADD COLUMN "headline" TEXT NOT NULL;
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

Migration could fail on existing data

The migration adds a required headline column without a default value, which will fail if the Tool table contains existing records. This is risky for production deployments.

Consider these safer approaches:

  1. Add column as nullable first, then backfill data:
-- Step 1: Add nullable column
ALTER TABLE "Tool" ADD COLUMN "headline" TEXT;
ALTER TABLE "Tool" ADD COLUMN "features" TEXT[];

-- Step 2: Backfill headline data (in separate migration)
-- UPDATE "Tool" SET "headline" = ...;

-- Step 3: Make headline required (in separate migration)
-- ALTER TABLE "Tool" ALTER COLUMN "headline" SET NOT NULL;
  1. Or provide a default value:
ALTER TABLE "Tool" ADD COLUMN "headline" TEXT NOT NULL DEFAULT 'No headline available';
ALTER TABLE "Tool" ADD COLUMN "features" TEXT[];

Comment on lines +30 to +38
{tool?.categories && tool.categories.slice(0, 3).map((category) => (
<Badge
key={category}
variant="secondary"
className="bg-[#141414] bg-opacity-80 text-xs text-gray-300 hover:bg-[#141414] hover:bg-opacity-100"
>
{category}
</Badge>
))}
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

Use optional chaining as suggested by static analysis

The current implementation could throw an error if categories is undefined.

- {tool?.categories && tool.categories.slice(0, 3).map((category) => (
+ {tool.categories?.slice(0, 3).map((category) => (
📝 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
{tool?.categories && tool.categories.slice(0, 3).map((category) => (
<Badge
key={category}
variant="secondary"
className="bg-[#141414] bg-opacity-80 text-xs text-gray-300 hover:bg-[#141414] hover:bg-opacity-100"
>
{category}
</Badge>
))}
{tool.categories?.slice(0, 3).map((category) => (
<Badge
key={category}
variant="secondary"
className="bg-[#141414] bg-opacity-80 text-xs text-gray-300 hover:bg-[#141414] hover:bg-opacity-100"
>
{category}
</Badge>
))}
🧰 Tools
🪛 Biome (1.9.4)

[error] 30-38: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

Comment on lines +44 to +48
<img
src={`/images/logo/${tool.name}.png`}
alt={`${tool.name} logo`}
className="w-10 h-10 object-cover border border-white border-opacity-10 rounded"
/>
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 error handling for tool logo images.

The current implementation doesn't handle cases where the logo image might be missing or have a different filename.

Apply this diff:

-<img
-  src={`/images/logo/${tool.name}.png`}
-  alt={`${tool.name} logo`}
-  className="w-10 h-10 object-cover border border-white border-opacity-10 rounded"
-/>
+<img
+  src={`/images/logo/${tool.name}.png`}
+  alt={`${tool.name} logo`}
+  className="w-10 h-10 object-cover border border-white border-opacity-10 rounded"
+  onError={(e) => {
+    e.currentTarget.src = '/images/default-logo.png';
+  }}
+/>
📝 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
<img
src={`/images/logo/${tool.name}.png`}
alt={`${tool.name} logo`}
className="w-10 h-10 object-cover border border-white border-opacity-10 rounded"
/>
<img
src={`/images/logo/${tool.name}.png`}
alt={`${tool.name} logo`}
className="w-10 h-10 object-cover border border-white border-opacity-10 rounded"
onError={(e) => {
e.currentTarget.src = '/images/default-logo.png';
}}
/>

Comment on lines +58 to +59
"stars": 4900,
"forks": "7300",
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 inconsistent data type for forks count.

The forks value is a string instead of a number, which could cause type mismatches when used in the application.

Apply this diff:

-    "forks": "7300",
+    "forks": 7300,
📝 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
"stars": 4900,
"forks": "7300",
"stars": 4900,
"forks": 7300,

Comment on lines +988 to +1008
const createdTools = await Promise.all(
tools.map(async (tool) => {
return prisma.tool.create({
data: {
name: tool.name,
description: tool.description,
headline: tool.headline,
features: tool.features,
logo: tool.name,
categories: tool.categories || [],
tags: tool.tags || [],
stars: tool.stars || 0,
forks: tool.forks || 0,
lastUpdated: new Date(),
websiteUrl: tool.websiteUrl,
githubUrl: tool?.githubUrl,
documentation: tool?.documentation
},
});
})
);
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 batch processing for database operations.

The current implementation processes each tool creation sequentially. Consider using Prisma's createMany for better performance.

Apply this diff:

-    const createdTools = await Promise.all(
-      tools.map(async (tool) => {
-        return prisma.tool.create({
-          data: {
+    const createdTools = await prisma.tool.createMany({
+      data: tools.map((tool) => ({
             name: tool.name,
             description: tool.description,
             headline: tool.headline,
             features: tool.features,
             logo: tool.name,
             categories: tool.categories || [],
             tags: tool.tags || [],
             stars: tool.stars || 0,
             forks: tool.forks || 0,
             lastUpdated: new Date(),
             websiteUrl: tool.websiteUrl,
             githubUrl: tool?.githubUrl,
             documentation: tool?.documentation
-          },
-        });
-      })
+      }))
     );
📝 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 createdTools = await Promise.all(
tools.map(async (tool) => {
return prisma.tool.create({
data: {
name: tool.name,
description: tool.description,
headline: tool.headline,
features: tool.features,
logo: tool.name,
categories: tool.categories || [],
tags: tool.tags || [],
stars: tool.stars || 0,
forks: tool.forks || 0,
lastUpdated: new Date(),
websiteUrl: tool.websiteUrl,
githubUrl: tool?.githubUrl,
documentation: tool?.documentation
},
});
})
);
const createdTools = await prisma.tool.createMany({
data: tools.map((tool) => ({
name: tool.name,
description: tool.description,
headline: tool.headline,
features: tool.features,
logo: tool.name,
categories: tool.categories || [],
tags: tool.tags || [],
stars: tool.stars || 0,
forks: tool.forks || 0,
lastUpdated: new Date(),
websiteUrl: tool.websiteUrl,
githubUrl: tool?.githubUrl,
documentation: tool?.documentation
}))
);

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

🧹 Outside diff range and nitpick comments (7)
src/lib/types.ts (3)

1-18: Consider refactoring interfaces to reduce duplication

The ToolCardInterface and ToolDetailsInterface share many common properties. Consider creating a base interface to reduce duplication and maintain consistency.

interface BaseToolInterface {
  name: string
  description: string
  categories: string[]
  stars: number
  forks: number
  headline: string
  features: string[]
  logo: string
  tags: string[]
}

export interface ToolCardInterface extends BaseToolInterface {
  id: string
  lastUpdated?: Date
  githubUrl?: string
  websiteUrl?: string
  documentation?: string
  createdAt?: Date
  updatedAt?: Date
}

export interface ToolDetailsInterface extends BaseToolInterface {
  id?: string
  lastUpdated: Date
  githubUrl: string
  websiteUrl?: string | null
  documentation?: string | null
  createdAt: Date
  updatedAt: Date
}

Also applies to: 21-38


34-35: Standardize nullability pattern

The websiteUrl and documentation properties use string | null in ToolDetailsInterface while other optional properties use undefined. Consider standardizing the nullability pattern across the interfaces.

- websiteUrl?: string | null
- documentation?: string | null
+ websiteUrl?: string
+ documentation?: string

2-4: Add validation constraints using JSDoc

Consider adding JSDoc comments with validation constraints for critical fields like URLs and descriptions.

/** @minLength 1 @maxLength 100 */
name: string
/** @minLength 10 @maxLength 500 */
description: string
/** @pattern ^https?:// */
githubUrl: string

Also applies to: 23-24

src/components/tools/ToolCard.tsx (3)

30-38: Simplify categories rendering with optional chaining

The current implementation has redundant null checking.

- {tool?.categories && tool.categories.slice(0, 3).map((category) => (
+ {tool.categories?.slice(0, 3).map((category) => (
🧰 Tools
🪛 Biome (1.9.4)

[error] 30-38: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


39-46: Extract categories limit to a constant

The magic number 3 is used multiple times for category limiting. Consider extracting it to a named constant.

+ const MAX_VISIBLE_CATEGORIES = 3;
...
- {tool.categories?.slice(0, 3).map((category) =>
+ {tool.categories?.slice(0, MAX_VISIBLE_CATEGORIES).map((category) =>
...
- {tool.categories && tool.categories.length > 3 && (
+ {tool.categories && tool.categories.length > MAX_VISIBLE_CATEGORIES && (
...
-   +{tool.categories.length - 3}
+   +{tool.categories.length - MAX_VISIBLE_CATEGORIES}

52-57: Add aria-labels for better accessibility

The star and fork counts should have proper aria-labels for screen readers.

- <div className="flex items-center">
+ <div className="flex items-center" aria-label={`${tool.stars.toLocaleString()} stars`}>
...
- <div className="flex items-center">
+ <div className="flex items-center" aria-label={`${tool.forks.toLocaleString()} forks`}>

Also applies to: 61-66

src/components/tools/AlgoliaSearch.tsx (1)

1-41: Consider enhancing the implementation to fully meet requirements.

Based on the PR objectives, this implementation seems incomplete:

  1. The search functionality is implemented but not integrated with the UI
  2. The component doesn't handle the filters mentioned in the PR objectives
  3. There's no error handling or loading states for search operations

Consider:

  • Implementing search input and results display
  • Adding filter integration
  • Including loading and error states
  • Adding proper client-side caching for search results

Would you like help implementing these additional features?

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 69c1bf3 and 3b84bd3.

📒 Files selected for processing (3)
  • src/components/tools/AlgoliaSearch.tsx (1 hunks)
  • src/components/tools/ToolCard.tsx (1 hunks)
  • src/lib/types.ts (1 hunks)
🧰 Additional context used
📓 Learnings (2)
src/components/tools/AlgoliaSearch.tsx (1)
Learnt from: Ankur1493
PR: tyaga001/devtoolsacademy#44
File: src/components/tools/AlgoliaSearch.tsx:42-49
Timestamp: 2024-11-30T20:14:25.360Z
Learning: The `AlgoliaSearch` component in `src/components/tools/AlgoliaSearch.tsx` currently does not require `searchParams` and does not need to accept them as props.
src/components/tools/ToolCard.tsx (1)
Learnt from: Ankur1493
PR: tyaga001/devtoolsacademy#44
File: src/components/tools/ToolCard.tsx:19-23
Timestamp: 2024-11-30T20:29:45.779Z
Learning: In the `src/components/tools/ToolCard.tsx` file, it's acceptable to use `tool.logo` directly in the `img` `src` attribute without additional validation because the image URLs are controlled by the backend, and overengineering is to be avoided before the MVP is complete.
🪛 Biome (1.9.4)
src/components/tools/ToolCard.tsx

[error] 30-38: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

🔇 Additional comments (6)
src/components/tools/ToolCard.tsx (2)

74-76: Handle invalid date formats

This is a duplicate of a previous review comment. The date handling needs improvement to handle invalid dates.


17-21: ⚠️ Potential issue

Avoid hardcoded image paths

The image path is hardcoded to /images/logo/${tool.name}.png. This assumes all logos are PNGs and follow a specific naming convention.

Consider:

  1. Using the logo property from ToolCardInterface which is currently unused
  2. Adding error handling for missing images
- src={`/images/logo/${tool.name}.png`}
+ src={tool.logo}
+ onError={(e) => {
+   e.currentTarget.src = '/images/default-logo.png'
+ }}
⛔ Skipped due to learnings
Learnt from: Ankur1493
PR: tyaga001/devtoolsacademy#44
File: src/components/tools/ToolCard.tsx:19-23
Timestamp: 2024-11-30T20:29:45.779Z
Learning: In the `src/components/tools/ToolCard.tsx` file, it's acceptable to use `tool.logo` directly in the `img` `src` attribute without additional validation because the image URLs are controlled by the backend, and overengineering is to be avoided before the MVP is complete.
src/components/tools/AlgoliaSearch.tsx (4)

1-10: LGTM! Clean imports and well-structured interface.

The imports are appropriate and the interface is well-defined with proper typing.


17-20: Ensure environment variables are properly handled

The environment variables are using non-null assertion operators (!). This should be handled more safely.


22-27: LGTM! Well-implemented search functionality.

The Algolia search implementation is clean with appropriate pagination (20 items per page) and proper typing.


30-37: ⚠️ Potential issue

Remove unnecessary async keyword and enhance component implementation.

The component has several issues:

  1. It's marked as async but contains no async operations
  2. The implementation seems incomplete as it's not utilizing the search functionality from getTools

Apply this diff to fix the immediate issues:

-const AlgoliaSearch: React.FC<AlgoliaSearchProps> = async () => {
+const AlgoliaSearch: React.FC<AlgoliaSearchProps> = () => {

Let's verify if the ToolsFilter component handles the search functionality:

@tyaga001 tyaga001 merged commit 9c2073c into tyaga001:main Dec 16, 2024
4 of 6 checks passed
@coderabbitai coderabbitai bot mentioned this pull request Apr 9, 2025
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
0