Skip to content

adim#65

Open
wabinyai wants to merge 5 commits into
stagingfrom
admin
Open

adim#65
wabinyai wants to merge 5 commits into
stagingfrom
admin

Conversation

@wabinyai
Copy link
Copy Markdown
Collaborator

@wabinyai wabinyai commented Mar 26, 2025

Summary by CodeRabbit

  • New Features

    • Launched several public pages showcasing AI-enabled tools such as air quality categorization, data analytics, health impact assessments, interactive mapping, machine learning forecasts, and optimized site locations.
    • Unveiled a revamped admin interface with enhanced dashboards, streamlined media management, and improved settings for easier system administration.
    • Introduced new components for user management, including editing and creating users within the admin interface.
    • Added a media upload component for easier image management in the admin panel.
    • Implemented a comprehensive authentication system with user roles and permissions.
    • Introduced a contact page for user inquiries and feedback.
    • Added a date range picker component for selecting date ranges in forms.
    • Introduced a new footer and header for improved site navigation and branding.
    • Added a loading spinner component for better user feedback during data fetching.
    • Added new components for alerts, badges, buttons, cards, dropdown menus, forms, navigation menus, popovers, sliders, skeletons, and various UI elements for enhanced user experience.
  • Improvements

    • Upgraded numerous dependency libraries and refined UI components for better performance, accessibility, and overall user experience.
    • Integrated real-time notification and feedback systems for smoother interactions.

@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented Mar 26, 2025

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

Name Status Preview Comments Updated (UTC)
air-track ✅ Ready (Inspect) Visit Preview 💬 Add feedback Mar 26, 2025 2:34pm
air-vista ✅ Ready (Inspect) Visit Preview 💬 Add feedback Mar 26, 2025 2:34pm
breeze-mind ✅ Ready (Inspect) Visit Preview 💬 Add feedback Mar 26, 2025 2:34pm
clean-aria ✅ Ready (Inspect) Visit Preview 💬 Add feedback Mar 26, 2025 2:34pm
clean-stats ✅ Ready (Inspect) Visit Preview 💬 Add feedback Mar 26, 2025 2:34pm
frontend ❌ Failed (Inspect) Mar 26, 2025 2:34pm
pure-sphere ✅ Ready (Inspect) Visit Preview 💬 Add feedback Mar 26, 2025 2:34pm

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 26, 2025

📝 Walkthrough

Walkthrough

This pull request introduces numerous frontend updates across configuration files, admin pages, API routes, UI components, and utility hooks. A new JSON configuration for components is added, and dependencies are updated in package.json. Several admin panel pages and API endpoints (authentication, media management) have been implemented alongside new feature pages for AI technologies and mapping. Additionally, many files received formatting improvements and consistent syntactic adjustments throughout the project.

Changes

File(s) Change Summary
frontend/components.json, frontend/next.config.ts, frontend/package.json, frontend/tailwind.config.ts Added a new JSON config for frontend components, updated Next.js export formatting, dependency revisions and additions, and minor stylistic adjustments in Tailwind config.
frontend/src/app/api/admin/**/route.ts (auth check, login, logout, media [id], media, media/upload) Introduced new API endpoints for admin authentication and media operations with enhanced error handling and JSON responses.
frontend/src/app/admin/** (login, layout, dashboard, AI technologies, features, media, settings) Added multiple new admin panel components for managing authentication, technologies, features, media library, and site settings with state management and control flow for API calls.
frontend/src/app/features/**, frontend/src/app/categorize/page.tsx, frontend/src/app/page.tsx, frontend/src/app/reports/page.tsx, frontend/src/app/locate/page.tsx, frontend/src/app/not-found.tsx Introduced several new feature pages (air quality categorization, data analytics, health impact, interactive mapping, machine learning, site location) and updated page layouts with improved formatting and responsive designs.
frontend/src/components/ui/**, frontend/src/components/Controls/** Added a comprehensive suite of reusable UI components (alert, badge, button, card, dropdown, input, label, loading spinner, select, switch, table, tabs, textarea, toast, toaster) alongside formatting improvements in control components.
frontend/src/components/map/**, frontend/src/components/navigation/navigation.tsx Enhanced mapping components with improved import paths, formatting tweaks, and accessibility enhancements for navigation elements.
frontend/src/hooks/**, frontend/src/lib/**, frontend/src/services/apiService.tsx, frontend/src/utils/** Added new custom hooks (useApi, useToast) and utility functions; restructured API error handling, environment validation, MongoDB connection, and consistent type/interface formatting.
frontend/src/styles/globals.css, frontend/src/types/leaflet-geosearch.d.ts Made minor CSS formatting adjustments and refined TypeScript declarations with stylistic changes.
Various individual files (e.g. multiple Loading.tsx components, GoogleAnalytics.tsx, ErrorBoundary, etc.) Applied stylistic changes such as removal of semicolons, adjusted line breaks, and minor reformatting without altering functional logic.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant AdminLogin
    participant AuthCheck as /api/admin/auth/check
    participant AuthLogin as /api/admin/auth/login
    participant Dashboard as AdminDashboard
    
    User->>AdminLogin: Access admin login page
    AdminLogin->>AuthCheck: Send authentication check request
    AuthCheck-->>AdminLogin: Respond { authenticated: false }
    AdminLogin->>User: Render login form
    User->>AdminLogin: Submit login credentials
    AdminLogin->>AuthLogin: POST credentials for login
    AuthLogin-->>AdminLogin: Respond with success (set cookie)
    AdminLogin->>User: Redirect to admin dashboard
Loading

Possibly related PRs

  • landing page #63: The changes in the main PR, particularly the introduction of the frontend/components.json configuration file and the modifications to the frontend/src/app/map/page.tsx component, are related to the changes in the retrieved PR, which also involves the frontend/src/app/map/page.tsx file, as both PRs focus on enhancing the map component's functionality and layout.

Poem

In lines of code both neat and bold,
New paths and features now unfold.
A config refined, a dashboard bright,
With APIs and components in sight.
Cheers to progress with each merge in flight!
🚀💻✨

Warning

Review ran into problems

🔥 Problems

Errors were encountered while retrieving linked issues.

Errors (1)
  • JIRA integration encountered authorization issues. Please disconnect and reconnect the integration in the CodeRabbit UI.
✨ Finishing Touches
  • 📝 Generate Docstrings

🪧 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 plan to trigger planning for file edits and PR creation.
  • @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 or @coderabbitai title anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

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

Documentation and Community

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

Copy link
Copy Markdown

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

🧹 Nitpick comments (78)
frontend/src/lib/env.ts (1)

1-15: Well-implemented environment variable validation function.

The validateEnv function follows a clean approach to validate critical environment variables. It properly checks for required variables and throws descriptive error messages when they're missing, which will help with debugging deployment issues.

One suggestion: consider how these non-null assertions (!) might affect type safety across the codebase. While they're fine here since you've already verified the variables exist, their use should be carefully considered throughout the project.

- return {
-   apiUrl: process.env.NEXT_PUBLIC_API_URL!,
-   apiToken: process.env.NEXT_PUBLIC_API_TOKEN!,
-   googleAnalyticsId: process.env.NEXT_PUBLIC_GOOGLE_ANALYTICS_ID || null,
- }
+ return {
+   apiUrl: process.env.NEXT_PUBLIC_API_URL,
+   apiToken: process.env.NEXT_PUBLIC_API_TOKEN,
+   googleAnalyticsId: process.env.NEXT_PUBLIC_GOOGLE_ANALYTICS_ID || null,
+ }
frontend/src/app/admin/media/loading.tsx (1)

5-5: Remove unnecessary empty line

There's an extra empty line at the end of the file that serves no purpose. Consider removing it to maintain cleaner code.

frontend/src/app/admin/features/loading.tsx (1)

5-5: Remove unnecessary empty line

There's an extra empty line at the end of the file that could be removed for cleaner code.

frontend/src/utils/index.ts (1)

5-5: Remove unnecessary empty line

Consider removing the extra empty line at the end of the file.

frontend/src/app/api/admin/auth/logout/route.ts (1)

16-16: Remove unnecessary empty line

Consider removing the extra empty line at the end of the file.

frontend/src/app/api/admin/media/upload/route.ts (1)

1-31: Good mock implementation for file upload API

The implementation provides a realistic mock response structure that frontend components can use during development. The error handling is appropriate, and the comments clearly indicate this is a temporary solution.

Consider adding a more specific TODO comment with acceptance criteria for the real implementation:

 // This is a mock implementation since actual file uploads would be handled by the console
 // In a real implementation, you might use a service like AWS S3, Cloudinary, etc.
+// TODO: Replace with actual file upload implementation that:
+// 1. Handles multipart form data
+// 2. Validates file types and sizes
+// 3. Stores metadata in database
+// 4. Returns actual file URLs
frontend/src/components/ui/toaster.tsx (1)

13-35: Well-implemented toast notification component

The Toaster component is well-structured, using the useToast hook to render toast notifications dynamically. It properly handles conditional rendering of titles and descriptions.

Consider adding JSDoc comments to document the purpose of this component:

+/**
+ * Toaster component that renders toast notifications
+ * Uses the useToast hook to access and display toast notifications
+ * Renders toast notifications with title, description, actions, and close button
+ */
 export function Toaster() {
   const { toasts } = useToast()
frontend/src/app/api/admin/auth/login/route.ts (1)

28-32: Error handling could be improved.

The error handling is good for catching exceptions, but the error logged to the console could include more details while maintaining a generic user-facing message.

  } catch (error) {
-    console.error("Login error:", error)
+    console.error("Login error:", error instanceof Error ? error.message : error)
    return NextResponse.json({ message: "An error occurred during login" }, { status: 500 })
  }
frontend/src/components/ui/loading-spinner.tsx (1)

17-22: Consider adding accessibility attributes.

The spinner component looks good, but could benefit from accessibility enhancements.

- <div className={cn("flex items-center justify-center", className)}>
+ <div className={cn("flex items-center justify-center", className)} role="status" aria-live="polite">
-   <Loader2 className={cn("animate-spin text-blue-500", sizeClasses[size])} />
+   <Loader2 className={cn("animate-spin text-blue-500", sizeClasses[size])} aria-hidden="true" />
-   {text && <span className="ml-2 text-gray-700">{text}</span>}
+   {text && <span className="ml-2 text-gray-700">{text}</span>}
+   <span className="sr-only">Loading{text ? `: ${text}` : ""}</span>
  </div>
frontend/src/components/ui/switch.tsx (1)

1-29: Well-implemented accessible Switch component.

The Switch component is well-structured, using Radix UI's primitives for accessibility support. The component correctly uses React.forwardRef and provides appropriate styling for different states.

Consider breaking up the long className string in lines 13-15 into more manageable variables for better readability and maintainability:

-    className={cn(
-      "peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input",
-      className
-    )}
+    className={cn(
+      [
+        "peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full",
+        "border-2 border-transparent shadow-sm transition-colors",
+        "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
+        "disabled:cursor-not-allowed disabled:opacity-50",
+        "data-[state=checked]:bg-primary data-[state=unchecked]:bg-input"
+      ].join(" "),
+      className
+    )}
frontend/src/lib/mongodb.ts (2)

17-17: Avoid using 'any' type.

Using any type defeats TypeScript's purpose of providing type safety.

-let cachedDb: any = null
+let cachedDb: ReturnType<MongoClient['db']> | null = null

19-39: Consider implementing connection closure mechanism.

The current implementation doesn't handle closing connections, which could lead to resource leaks.

Consider adding a function to properly close the MongoDB connection:

export async function disconnectFromDatabase() {
  if (cachedClient) {
    await cachedClient.close();
    cachedClient = null;
    cachedDb = null;
    console.log("MongoDB connection closed");
  }
}
frontend/src/components/ui/alert.tsx (1)

6-20: Well-defined alert variants using class-variance-authority.

The alert variants are properly defined with default and destructive styles, allowing for consistent styling across the application. The use of Tailwind CSS classes is appropriate here.

Consider adding more variants like "warning", "info", and "success" to provide a comprehensive set of alert styles that would enhance the UX by offering visual cues for different message types.

frontend/src/app/admin/layout.tsx (4)

25-51: Solid authentication flow with proper error handling.

The authentication check is well-implemented, skipping the check for the login page and handling API responses appropriately.

Consider adding TypeScript typing for the error in the catch block:

- } catch (error) {
+ } catch (error: unknown) {
  console.error("Auth check failed:", error)
  router.push("/admin/login")

Also, consider implementing a more robust error handling strategy that provides user feedback for authentication failures.


53-74: Remove unnecessary comment and add type for error handling.

The handleLogout function is well-structured, but contains an unnecessary comment and lacks error typing.

Remove the outdated comment and add type annotation for the error:

- // Update the handleLogout function to properly handle the logout process
const handleLogout = async () => {
  try {
    setIsLoading(true)
    const response = await fetch("/api/admin/auth/logout", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
    })

    if (response.ok) {
      router.push("/admin/login")
    } else {
      console.error("Logout failed: Server returned an error")
    }
-  } catch (error) {
+  } catch (error: unknown) {
    console.error("Logout failed:", error)
  } finally {
    setIsLoading(false)
  }
}

89-91: Redundant authentication guard that may cause UI flicker.

This guard is redundant since the authentication redirection is already handled in the useEffect hook.

Consider removing this condition to avoid potential UI flickering:

- if (!isAuthenticated && !isLoginPage) {
-   return null // Will redirect to login in useEffect
- }

The useEffect already handles redirecting if not authenticated, so this additional check isn't necessary.


104-108: Enhance mobile toggle button with accessibility attributes.

While the toggle button works, it lacks important accessibility attributes.

Add accessibility attributes to the mobile sidebar toggle similar to the navigation component:

<Button 
  variant="outline" 
  size="icon" 
  onClick={() => setIsSidebarOpen(!isSidebarOpen)} 
  className="bg-white"
+ aria-label={isSidebarOpen ? "Close sidebar" : "Open sidebar"}
+ aria-expanded={isSidebarOpen}
+ aria-controls="admin-sidebar"
>
  {isSidebarOpen ? <X className="h-5 w-5" /> : <Menu className="h-5 w-5" />}
</Button>

And add a corresponding id to the sidebar:

<div
+ id="admin-sidebar"
  className={`fixed inset-y-0 left-0 z-40 w-64 bg-white shadow-lg transform transition-transform duration-300 ease-in-out lg:translate-x-0 ${
    isSidebarOpen ? "translate-x-0" : "-translate-x-full"
  }`}
>
frontend/src/services/apiService.tsx (1)

90-93: Add type annotation for error in getMapNodes catch block.

Unlike the getSatelliteData function, this error parameter lacks a type annotation.

Add type annotation for consistency:

-  } catch (error) {
+  } catch (error: unknown) {
     console.error("Error fetching map nodes:", error)
     return null
   }
frontend/src/components/navigation/navigation.tsx (1)

18-21: Add type definition for component props.

The component accepts props without specifying their type, which could lead to type safety issues.

Add a TypeScript interface for the component props:

+ interface NavigationProps extends React.HTMLAttributes<HTMLElement> {}

- export default function Navigation({ ...props }) {
+ export default function Navigation({ ...props }: NavigationProps) {
  const pathname = usePathname()
  const [isOpen, setIsOpen] = useState(false)
frontend/src/components/Controls/FileUpload.tsx (1)

105-105: Consider adding error feedback for incorrect file types.

While the component accepts only CSV files through the input's accept attribute, you might want to add explicit error handling for cases where users try to upload non-CSV files that bypass the browser's file picker filter.

- <input type="file" ref={fileInputRef} onChange={handleFileChange} accept=".csv" className="hidden" />
+ <input 
+   type="file" 
+   ref={fileInputRef} 
+   onChange={(e) => {
+     const file = e.target.files?.[0];
+     if (file && !file.name.endsWith('.csv')) {
+       toast({
+         title: "Error",
+         description: "Please upload a CSV file.",
+         variant: "destructive",
+       });
+       e.target.value = '';
+       return;
+     }
+     handleFileChange(e);
+   }} 
+   accept=".csv" 
+   className="hidden" 
+ />
frontend/src/app/features/machine-learning/page.tsx (3)

47-49: Consider using a more robust image fallback approach.

The current fallback uses a placeholder URL which might not be reliable in all environments. Consider using an imported local SVG or a more robust fallback mechanism.

- onError={(e) => {
-   // Fallback if image doesn't exist
-   e.currentTarget.src = "/placeholder.svg?height=400&width=600"
- }}
+ onError={(e) => {
+   // Fallback if image doesn't exist
+   try {
+     e.currentTarget.src = "/placeholder.svg?height=400&width=600";
+     // Add a class to maintain aspect ratio
+     e.currentTarget.classList.add("placeholder-image");
+   } catch (err) {
+     // If the fallback itself fails, apply a background color
+     e.currentTarget.style.backgroundColor = "#f3f4f6";
+   }
+ }}

94-136: DRY opportunity: Key Benefits section has repeated structure.

The Key Benefits section repeats the same UI structure four times. Consider extracting this to a reusable component to improve maintainability.

You could create a BenefitItem component:

type BenefitProps = {
  title: string;
  description: string;
};

function BenefitItem({ title, description }: BenefitProps) {
  return (
    <div className="flex items-start">
      <div className="bg-blue-100 p-2 rounded-lg mr-4">
        <BrainCircuit className="h-6 w-6 text-blue-600" />
      </div>
      <div>
        <h3 className="text-xl font-semibold mb-2">{title}</h3>
        <p className="text-gray-600">{description}</p>
      </div>
    </div>
  );
}

Then use it in your Benefits section:

const benefits = [
  {
    title: "Accurate Forecasting",
    description: "Predict air quality conditions days in advance with high accuracy."
  },
  {
    title: "Pattern Recognition",
    description: "Identify complex patterns and relationships that might not be obvious through traditional analysis."
  },
  // Add other benefits here
];

// In your JSX:
<div className="grid md:grid-cols-2 gap-8">
  {benefits.map((benefit, index) => (
    <BenefitItem 
      key={index}
      title={benefit.title}
      description={benefit.description}
    />
  ))}
</div>

104-127: Consider using different icons for each benefit.

Currently, all benefits use the same BrainCircuit icon. Using different, more specific icons for each benefit would improve visual distinction and user understanding.

- <BrainCircuit className="h-6 w-6 text-blue-600" />
+ {/* Use appropriate icons for each benefit */}
+ {/* For Pattern Recognition */}
+ <Activity className="h-6 w-6 text-blue-600" />
+ {/* For Continuous Improvement */}
+ <TrendingUp className="h-6 w-6 text-blue-600" />
+ {/* For Factor Analysis */}
+ <PieChart className="h-6 w-6 text-blue-600" />
frontend/src/app/api/admin/media/[id]/route.ts (1)

28-45: Add validation for the DELETE endpoint

The DELETE endpoint follows the same pattern as GET, but consider adding validation to ensure the ID exists before attempting deletion. Also, you might want to add authentication checks to prevent unauthorized deletions.

export async function DELETE(request: Request, { params }: { params: { id: string } }) {
  try {
    const id = params.id
+
+    // Validate that the ID exists
+    // In a real implementation:
+    // const { db } = await connectToDatabase()
+    // const existingItem = await db.collection('media').findOne({ _id: id })
+    // if (!existingItem) {
+    //   return NextResponse.json({ message: "Media item not found" }, { status: 404 })
+    // }

    // In a real implementation, this would delete from MongoDB
    // const { db } = await connectToDatabase()
    // await db.collection('media').deleteOne({ _id: id })

    // For now, return a success response
    return NextResponse.json({
      success: true,
      message: "Media item deleted successfully",
    })
  } catch (error) {
    console.error("Error deleting media item:", error)
    return NextResponse.json({ message: "Failed to delete media item" }, { status: 500 })
  }
}
frontend/src/components/Controls/SearchBar.tsx (4)

30-42: Consider adding rate limiting for external API calls

You're making calls to the OpenStreetMap Nominatim API, which has usage policies. While you have debouncing in place, consider adding additional measures to respect their rate limits.

What are the usage policies for OpenStreetMap Nominatim API?

50-114: Consider breaking down the searchLocation function

The searchLocation function is quite long and handles multiple responsibilities (searching, fetching boundaries, updating UI). Consider breaking it down into smaller, focused functions.

You could refactor it into separate concerns:

- const searchLocation = async (selectedQuery?: string, selectedOsmId?: number) => {
+ // Main search orchestration function
+ const searchLocation = async (selectedQuery?: string, selectedOsmId?: number) => {
    const searchQuery = selectedQuery || query
    if (!searchQuery.trim()) return

    setIsLoading(true)
    setSuggestions([]) // Hide suggestions after selection

    try {
+     // Extract this to a separate function
+     const searchResults = await fetchSearchResults(searchQuery)
-     const searchResponse = await fetch(
-       `https://nominatim.openstreetmap.org/search?q=${encodeURIComponent(
-         searchQuery,
-       )}&format=json&polygon_geojson=1&limit=1`,
-     )
-     const searchResults = await searchResponse.json()

      if (searchResults.length === 0) {
        toast({
          title: "Location not found",
          description: "Try another search term.",
          variant: "destructive",
        })
        return
      }

      const osmId = selectedOsmId || searchResults[0].osm_id

+     // Extract this to a separate function
+     const boundaryData = await fetchBoundaryData(osmId)
-     // Fetch boundary data
-     const boundaryResponse = await fetch(
-       `https://nominatim.openstreetmap.org/lookup?osm_ids=R${osmId}&polygon_geojson=1&format=json`,
-     )
-     const boundaryData = await boundaryResponse.json()

+     // Extract this to a separate function
+     handleBoundaryResults(boundaryData, searchResults, searchQuery)
-     if (boundaryData[0]?.geojson?.coordinates?.[0]) {
-       const boundary = boundaryData[0].geojson.coordinates[0].map(([lng, lat]: number[]) => ({ lat, lng }))
-       onBoundaryFound(boundary)
-       onSearch(searchQuery)
-
-       // Center the map on the first coordinate of the boundary
-       if (window.map) {
-         const center = boundary[0]
-         window.map.setView([center.lat, center.lng], 12)
-       }
-
-       toast({
-         title: "Location found",
-         description: `Boundary drawn for ${searchResults[0].display_name}`,
-       })
-     } else {
-       toast({
-         title: "Boundary not found",
-         description: "No boundary data available",
-         variant: "destructive",
-       })
-     }
    } catch (error) {
      console.log("Error searching location:", error)
      toast({
        title: "Error",
        description: "Failed to search location",
        variant: "destructive",
      })
    } finally {
      setIsLoading(false)
    }
  }
+
+ // Helper functions
+ const fetchSearchResults = async (query: string) => {
+   const response = await fetch(
+     `https://nominatim.openstreetmap.org/search?q=${encodeURIComponent(
+       query,
+     )}&format=json&polygon_geojson=1&limit=1`,
+   )
+   return await response.json()
+ }
+
+ const fetchBoundaryData = async (osmId: number) => {
+   const response = await fetch(
+     `https://nominatim.openstreetmap.org/lookup?osm_ids=R${osmId}&polygon_geojson=1&format=json`,
+   )
+   return await response.json()
+ }
+
+ const handleBoundaryResults = (boundaryData: any, searchResults: any, searchQuery: string) => {
+   if (boundaryData[0]?.geojson?.coordinates?.[0]) {
+     const boundary = boundaryData[0].geojson.coordinates[0].map(([lng, lat]: number[]) => ({ lat, lng }))
+     onBoundaryFound(boundary)
+     onSearch(searchQuery)
+
+     // Center the map on the first coordinate of the boundary
+     if (window.map) {
+       const center = boundary[0]
+       window.map.setView([center.lat, center.lng], 12)
+     }
+
+     toast({
+       title: "Location found",
+       description: `Boundary drawn for ${searchResults[0].display_name}`,
+     })
+   } else {
+     toast({
+       title: "Boundary not found",
+       description: "No boundary data available",
+       variant: "destructive",
+     })
+   }
+ }

105-106: Add error type annotation for better error handling

Consider adding a type annotation for the error in the catch block for better type safety.

- console.log("Error searching location:", error)
+ console.log("Error searching location:", error as Error)

88-91: Add type check for window.map property

You're using window.map without type checking, which could lead to TypeScript errors. Consider adding a proper type check or interface.

+ // Declare global window interface
+ declare global {
+   interface Window {
+     map?: {
+       setView: (coords: [number, number], zoom: number) => void;
+     };
+   }
+ }

// Then in your component:
if (window.map) {
  const center = boundary[0]
  window.map.setView([center.lat, center.lng], 12)
}
frontend/src/app/features/air-quality-categorization/page.tsx (4)

47-58: Add image dimensions and improve error handling

The Image component should include width and height properties for better performance and to prevent layout shifts. Also, the error handling could be improved for better fallback behavior.

<div className="relative h-[400px] rounded-xl overflow-hidden shadow-xl">
  <Image
    src="/images/air-quality-categorization.jpg"
    alt="Air quality categorization visualization"
-   fill
+   width={600}
+   height={400}
-   className="object-cover"
+   className="object-cover w-full h-full"
    onError={(e) => {
      // Fallback if image doesn't exist
-     e.currentTarget.src = "/placeholder.svg?height=400&width=600"
+     e.currentTarget.src = "/placeholder.svg"
+     e.currentTarget.srcset = ""
+     console.error("Failed to load image:", e)
    }}
  />
</div>

9-234: Component architecture could benefit from modularization

This component is quite large (200+ lines) and handles multiple responsibilities (layout, content, navigation). Consider breaking it down into smaller, reusable components for better maintainability.

Consider extracting these sections into separate components:

  1. Feature Header (lines 15-59)
  2. AI Technology Section (lines 62-131)
  3. How It Works Section (lines 133-166)
  4. Key Benefits Section (lines 168-217)
  5. CTA Section (lines 219-226)
  6. Footer (lines 229-233)

For example:

// components/features/AirQualityHeader.tsx
export function AirQualityHeader() {
  return (
    <div className="grid md:grid-cols-2 gap-12 items-center mb-12">
      {/* Header content */}
    </div>
  );
}

// Then in your main component
import { AirQualityHeader } from "@/components/features/AirQualityHeader";

export default function AirQualityCategorizationFeature() {
  return (
    <div className="flex flex-col min-h-screen">
      <Navigation />
      <main className="flex-1 container mx-auto px-4 py-12">
        {/* Back button */}
        <Link href="/" className="inline-flex items-center text-blue-600 hover:text-blue-800 mb-8">
          <ChevronLeft className="h-4 w-4 mr-1" />
          Back to Home
        </Link>
        
        <AirQualityHeader />
        {/* Other components */}
      </main>
      <Footer />
    </div>
  );
}

171-216: DRY up repetitive benefit item structure

The "Key Benefits" section has four almost identical structures. Consider creating a reusable component for these items.

type BenefitProps = {
  title: string;
  description: string;
};

function BenefitItem({ title, description }: BenefitProps) {
  return (
    <div className="flex items-start">
      <div className="bg-blue-100 p-2 rounded-lg mr-4">
        <Wind className="h-6 w-6 text-blue-600" />
      </div>
      <div>
        <h3 className="text-xl font-semibold mb-2">{title}</h3>
        <p className="text-gray-600">{description}</p>
      </div>
    </div>
  );
}

// Then in your component:
<div className="mb-12">
  <h2 className="text-2xl font-bold mb-6">Key Benefits</h2>
  <div className="grid md:grid-cols-2 gap-8">
    <BenefitItem 
      title="Contextual Understanding" 
      description="Understand the context of your air quality readings based on the surrounding environment." 
    />
    <BenefitItem 
      title="Standardized Classification" 
      description="Use consistent categories across your monitoring network for better comparability." 
    />
    {/* other benefit items */}
  </div>
</div>

231-232: Extract dynamic year calculation to a utility function

The copyright year calculation should ideally be extracted to a utility function, especially if it's used across multiple components.

Consider creating a utility function:

// utils/date.ts
export function getCurrentYear(): number {
  return new Date().getFullYear();
}

// Then in your component:
import { getCurrentYear } from "@/utils/date";

// ...
<div className="container mx-auto px-4 text-center text-gray-600">
  &copy; {getCurrentYear()} AirQo. All rights reserved.
</div>
frontend/src/components/error-boundary.tsx (1)

4-4: Consider using consistent import paths.

The Button import uses a shorthand path @/ui/button which might differ from standard import paths like @/components/ui/button.

-import { Button } from "@/ui/button"
+import { Button } from "@/components/ui/button"
frontend/src/app/features/health-impact/page.tsx (3)

7-7: Consider using consistent import paths.

The Button import uses a shorthand path @/ui/button which might differ from standard import paths.

-import { Button } from "@/ui/button"
+import { Button } from "@/components/ui/button"

54-87: Consider extracting repeatable step components.

The "How It Works" section contains three similar step components that could be extracted into a reusable component to improve maintainability.

Create a Step component:

interface StepProps {
  number: number;
  title: string;
  description: string;
}

function Step({ number, title, description }: StepProps) {
  return (
    <div className="bg-white p-6 rounded-lg shadow-sm">
      <div className="w-12 h-12 rounded-full bg-blue-100 text-blue-600 flex items-center justify-center text-xl font-bold mb-4">
        {number}
      </div>
      <h3 className="text-xl font-semibold mb-2">{title}</h3>
      <p className="text-gray-600">{description}</p>
    </div>
  );
}

Then use it in the component:

<div className="grid md:grid-cols-3 gap-8">
  <Step 
    number={1} 
    title="Data Integration" 
    description="We combine air quality data with demographic information and health statistics." 
  />
  <Step 
    number={2} 
    title="Risk Modeling" 
    description="Our models calculate health risks based on exposure levels and vulnerability factors." 
  />
  <Step 
    number={3} 
    title="Impact Assessment" 
    description="Generate detailed reports on potential health impacts and recommended interventions." 
  />
</div>

89-138: Consider extracting repeatable benefit components.

The "Key Benefits" section contains four similar benefit items that could be extracted into a reusable component.

Create a Benefit component:

interface BenefitProps {
  title: string;
  description: string;
}

function Benefit({ title, description }: BenefitProps) {
  return (
    <div className="flex items-start">
      <div className="bg-blue-100 p-2 rounded-lg mr-4">
        <HeartPulse className="h-6 w-6 text-blue-600" />
      </div>
      <div>
        <h3 className="text-xl font-semibold mb-2">{title}</h3>
        <p className="text-gray-600">{description}</p>
      </div>
    </div>
  );
}

Then use it in the component:

<div className="grid md:grid-cols-2 gap-8">
  <Benefit 
    title="Vulnerability Mapping" 
    description="Identify areas with vulnerable populations that are at higher risk from air pollution." 
  />
  <Benefit 
    title="Health Burden Estimation" 
    description="Quantify the potential health burden associated with current air quality levels." 
  />
  {/* Add the other benefits */}
</div>
frontend/src/app/admin/media/upload/page.tsx (2)

39-46: Consider enhancing file type detection logic

The current file type detection logic is functional but could be more robust. The current approach uses string matching which might incorrectly categorize some file types.

- if (file.type.startsWith("image/")) {
-   type = "image"
-   preview = URL.createObjectURL(file)
- } else if (file.type.includes("pdf") || file.type.includes("document") || file.type.includes("text")) {
-   type = "document"
- } else if (file.type.startsWith("video/")) {
-   type = "video"
- }
+ const mimeCategory = file.type.split('/')[0];
+ switch (mimeCategory) {
+   case "image":
+     type = "image";
+     preview = URL.createObjectURL(file);
+     break;
+   case "video":
+     type = "video";
+     break;
+   default:
+     // For documents, check more specific mime types
+     if (file.type.includes("pdf") || 
+         file.type.includes("document") || 
+         file.type.includes("text") ||
+         /application\/(msword|vnd\.openxmlformats|vnd\.oasis\.opendocument)/.test(file.type)) {
+       type = "document";
+     }
+ }

163-170: Implement actual drag and drop functionality

The UI suggests drag and drop functionality with the message "Drag and drop files here", but the actual drag and drop event handling is missing.

<div
  className="border-2 border-dashed rounded-lg p-8 text-center cursor-pointer hover:bg-gray-50"
  onClick={() => fileInputRef.current?.click()}
+ onDragOver={(e) => {
+   e.preventDefault();
+   e.stopPropagation();
+ }}
+ onDrop={(e) => {
+   e.preventDefault();
+   e.stopPropagation();
+   if (e.dataTransfer.files && e.dataTransfer.files.length > 0) {
+     const fileList = e.dataTransfer.files;
+     const changeEvent = {
+       target: { files: fileList }
+     } as React.ChangeEvent<HTMLInputElement>;
+     handleFileChange(changeEvent);
+   }
+ }}
>
frontend/src/app/features/data-analytics/page.tsx (1)

89-130: Diversify icons for different benefits

All four benefits are using the same BarChart3 icon, which reduces visual differentiation between different benefit types.

Consider using different, more semantically appropriate icons for each benefit:

<div className="flex items-start">
  <div className="bg-blue-100 p-2 rounded-lg mr-4">
-   <BarChart3 className="h-6 w-6 text-blue-600" />
+   <TrendingUp className="h-6 w-6 text-blue-600" />
  </div>
  <div>
    <h3 className="text-xl font-semibold mb-2">Trend Identification</h3>
    <p className="text-gray-600">Identify long-term trends and seasonal patterns in air quality data.</p>
  </div>
</div>

<div className="flex items-start">
  <div className="bg-blue-100 p-2 rounded-lg mr-4">
-   <BarChart3 className="h-6 w-6 text-blue-600" />
+   <LineChart className="h-6 w-6 text-blue-600" />
  </div>
  <div>
    <h3 className="text-xl font-semibold mb-2">Predictive Forecasting</h3>
    <p className="text-gray-600">
      Get accurate predictions of future air quality based on historical data and environmental factors.
    </p>
  </div>
</div>

<div className="flex items-start">
  <div className="bg-blue-100 p-2 rounded-lg mr-4">
-   <BarChart3 className="h-6 w-6 text-blue-600" />
+   <BarChart className="h-6 w-6 text-blue-600" />
  </div>
  <div>
    <h3 className="text-xl font-semibold mb-2">Comparative Analysis</h3>
    <p className="text-gray-600">
      Compare air quality across different locations, time periods, and pollutant types.
    </p>
  </div>
</div>

<div className="flex items-start">
  <div className="bg-blue-100 p-2 rounded-lg mr-4">
-   <BarChart3 className="h-6 w-6 text-blue-600" />
+   <FileText className="h-6 w-6 text-blue-600" />
  </div>
  <div>
    <h3 className="text-xl font-semibold mb-2">Custom Reporting</h3>
    <p className="text-gray-600">Generate tailored reports for different stakeholders and purposes.</p>
  </div>
</div>

Remember to import these additional icons at the top of the file:

- import { ArrowRight, BarChart3, ChevronLeft } from "lucide-react"
+ import { ArrowRight, BarChart3, ChevronLeft, TrendingUp, LineChart, BarChart, FileText } from "lucide-react"
frontend/src/app/features/interactive-mapping/page.tsx (1)

93-136: Use more specific icons for different benefits

Similar to the previous file, all benefit sections use the same MapPin icon, which reduces visual differentiation between the different benefit categories.

Consider using more semantically appropriate icons for each benefit:

import { ArrowRight, MapPin, ChevronLeft, Globe, Search, Layers } from "lucide-react"

// Then in the benefits section:
<div className="flex items-start">
  <div className="bg-blue-100 p-2 rounded-lg mr-4">
-   <MapPin className="h-6 w-6 text-blue-600" />
+   <Globe className="h-6 w-6 text-blue-600" />
  </div>
  <div>
    <h3 className="text-xl font-semibold mb-2">Spatial Understanding</h3>
    <p className="text-gray-600">Visualize how air quality varies across different geographic areas.</p>
  </div>
</div>

// For the "Hotspot Identification" benefit, keep MapPin
// For the "Location Search" benefit:
<div className="flex items-start">
  <div className="bg-blue-100 p-2 rounded-lg mr-4">
-   <MapPin className="h-6 w-6 text-blue-600" />
+   <Search className="h-6 w-6 text-blue-600" />
  </div>
  <div>
    <h3 className="text-xl font-semibold mb-2">Location Search</h3>
    <p className="text-gray-600">
      Search for specific locations to check air quality in areas of interest.
    </p>
  </div>
</div>

// For the "Multiple Map Layers" benefit:
<div className="flex items-start">
  <div className="bg-blue-100 p-2 rounded-lg mr-4">
-   <MapPin className="h-6 w-6 text-blue-600" />
+   <Layers className="h-6 w-6 text-blue-600" />
  </div>
  <div>
    <h3 className="text-xl font-semibold mb-2">Multiple Map Layers</h3>
    <p className="text-gray-600">
      Switch between different map views to gain different perspectives on the data.
    </p>
  </div>
</div>
frontend/src/app/admin/media/page.tsx (5)

35-174: Extract mock data to a separate utility file

The component contains a large amount of mock data that would be better placed in a separate utility file to improve readability and maintainability.

Create a new file frontend/src/lib/mock-data/media-items.ts:

import { MediaItem } from "@/types/media";

export const mockMediaItems: MediaItem[] = [
  {
    _id: "1",
    filename: "site-location.jpg",
    url: "/images/site-location.jpg",
    type: "image",
    size: 245000,
    dimensions: { width: 1200, height: 800 },
    uploadedAt: "2023-05-15T10:30:00Z",
    tags: ["feature", "location"],
  },
  // ... all the other mock items
];

Then in your component:

-        // For demo purposes, use mock data if API fails
-        const mockMediaItems: MediaItem[] = [
-          {
-            _id: "1",
-            filename: "site-location.jpg",
-            url: "/images/site-location.jpg",
-            type: "image",
-            size: 245000,
-            dimensions: { width: 1200, height: 800 },
-            uploadedAt: "2023-05-15T10:30:00Z",
-            tags: ["feature", "location"],
-          },
-          // ... all the other items
-        ]
+        // Import mock data from utility file
+        import { mockMediaItems } from "@/lib/mock-data/media-items";
         setMediaItems(mockMediaItems)

184-214: Replace browser confirm with a modal dialog

The component uses the native browser confirm dialog for delete confirmation, which doesn't match the design system and provides a poor user experience.

Consider replacing with a custom modal dialog that matches your design system:

+ import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
+ import { useState } from "react";

export default function MediaLibraryPage() {
  const [mediaItems, setMediaItems] = useState<MediaItem[]>([])
  const [isLoading, setIsLoading] = useState(true)
  const [searchQuery, setSearchQuery] = useState("")
  const [viewMode, setViewMode] = useState<"grid" | "list">("grid")
  const [selectedType, setSelectedType] = useState<string>("all")
+ const [deleteDialogOpen, setDeleteDialogOpen] = useState(false)
+ const [itemToDelete, setItemToDelete] = useState<string | null>(null)
  const { toast } = useToast()

  // ... existing code

  const handleDelete = async (id: string) => {
-   if (!confirm("Are you sure you want to delete this media item?")) {
-     return
-   }
+   setItemToDelete(id);
+   setDeleteDialogOpen(true);
+  }
+  
+  const confirmDelete = async () => {
+   if (!itemToDelete) return;
+   
+   setDeleteDialogOpen(false);

    try {
      // In a real implementation, this would call an API
      // const response = await fetch(`/api/admin/media/${id}`, {
      //   method: 'DELETE',
      // })

      // if (!response.ok) {
      //   throw new Error('Failed to delete media item')
      // }

      // Simulate successful deletion
-     setMediaItems((prev) => prev.filter((item) => item._id !== id))
+     setMediaItems((prev) => prev.filter((item) => item._id !== itemToDelete))

      toast({
        title: "Media Deleted",
        description: "Media item has been deleted successfully",
      })
    } catch (error) {
      console.error("Error deleting media:", error)
      toast({
        title: "Error",
        description: "Failed to delete media item",
        variant: "destructive",
      })
    }
  }

  // ... rest of component
  
  return (
    <>
      {/* ... existing JSX */}
      
      <Dialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
        <DialogContent>
          <DialogHeader>
            <DialogTitle>Confirm Deletion</DialogTitle>
            <DialogDescription>
              Are you sure you want to delete this media item? This action cannot be undone.
            </DialogDescription>
          </DialogHeader>
          <DialogFooter>
            <Button variant="outline" onClick={() => setDeleteDialogOpen(false)}>Cancel</Button>
            <Button variant="destructive" onClick={confirmDelete}>Delete</Button>
          </DialogFooter>
        </DialogContent>
      </Dialog>
    </>
  );
}

216-224: Optimize filtering with memoization

The filtering logic runs on every render, which could cause performance issues with large datasets.

Use useMemo to optimize the filtering operation:

+ import { useState, useEffect, useMemo } from "react"

// ...existing code

- const filteredMediaItems = mediaItems.filter((item) => {
-   const matchesSearch =
-     item.filename.toLowerCase().includes(searchQuery.toLowerCase()) ||
-     item.tags.some((tag) => tag.toLowerCase().includes(searchQuery.toLowerCase()))
-
-   const matchesType = selectedType === "all" || item.type === selectedType
-
-   return matchesSearch && matchesType
- })

+ const filteredMediaItems = useMemo(() => {
+   return mediaItems.filter((item) => {
+     const matchesSearch =
+       item.filename.toLowerCase().includes(searchQuery.toLowerCase()) ||
+       item.tags.some((tag) => tag.toLowerCase().includes(searchQuery.toLowerCase()))
+
+     const matchesType = selectedType === "all" || item.type === selectedType
+
+     return matchesSearch && matchesType
+   });
+ }, [mediaItems, searchQuery, selectedType]);

307-584: Extract file type icons into reusable components

There's significant duplication of SVG code for different file type icons in both the grid and list views.

Create reusable icon components:

// components/icons/file-icons.tsx
export function DocumentIcon({ className = "w-6 h-6 text-gray-400" }: { className?: string }) {
  return (
    <svg className={className} fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
      <path
        strokeLinecap="round"
        strokeLinejoin="round"
        strokeWidth={2}
        d="M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z"
      />
    </svg>
  );
}

export function VideoIcon({ className = "w-6 h-6 text-gray-400" }: { className?: string }) {
  return (
    <svg className={className} fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
      <path
        strokeLinecap="round"
        strokeLinejoin="round"
        strokeWidth={2}
        d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z"
      />
      <path
        strokeLinecap="round"
        strokeLinejoin="round"
        strokeWidth={2}
        d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
      />
    </svg>
  );
}

export function GenericFileIcon({ className = "w-6 h-6 text-gray-400" }: { className?: string }) {
  return (
    <svg className={className} fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
      <path
        strokeLinecap="round"
        strokeLinejoin="round"
        strokeWidth={2}
        d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
      />
    </svg>
  );
}

export function FileTypeIcon({ type, className }: { type: string; className?: string }) {
  switch(type) {
    case "document":
      return <DocumentIcon className={className} />;
    case "video":
      return <VideoIcon className={className} />;
    default:
      return <GenericFileIcon className={className} />;
  }
}

Then use these components in your media library:

+ import { FileTypeIcon } from "@/components/icons/file-icons";

// Replace SVG blocks with:
- <svg
-   className="w-12 h-12 text-gray-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="M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z"
-   />
- </svg>
+ <FileTypeIcon type="document" className="w-12 h-12 text-gray-400" />

258-305: Add keyboard accessibility to filtering controls

The filtering and view controls could benefit from improved keyboard accessibility.

Enhance keyboard accessibility for the filtering controls:

<div className="flex flex-col md:flex-row gap-4 mb-6">
  <div className="relative flex-1">
    <Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400" size={18} />
    <Input
      placeholder="Search by filename or tag..."
      className="pl-10"
      value={searchQuery}
      onChange={(e) => setSearchQuery(e.target.value)}
+     aria-label="Search media items"
    />
  </div>

  <div className="flex gap-2">
    <DropdownMenu>
      <DropdownMenuTrigger asChild>
        <Button variant="outline" className="flex items-center gap-2">
          <Filter className="h-4 w-4" />
          {selectedType === "all" ? "All Types" : selectedType}
        </Button>
      </DropdownMenuTrigger>
      <DropdownMenuContent>
-       <DropdownMenuItem onClick={() => setSelectedType("all")}>All Types</DropdownMenuItem>
-       <DropdownMenuItem onClick={() => setSelectedType("image")}>Images</DropdownMenuItem>
-       <DropdownMenuItem onClick={() => setSelectedType("document")}>Documents</DropdownMenuItem>
-       <DropdownMenuItem onClick={() => setSelectedType("video")}>Videos</DropdownMenuItem>
-       <DropdownMenuItem onClick={() => setSelectedType("other")}>Other</DropdownMenuItem>
+       <DropdownMenuItem onClick={() => setSelectedType("all")} onKeyDown={(e) => e.key === "Enter" && setSelectedType("all")}>All Types</DropdownMenuItem>
+       <DropdownMenuItem onClick={() => setSelectedType("image")} onKeyDown={(e) => e.key === "Enter" && setSelectedType("image")}>Images</DropdownMenuItem>
+       <DropdownMenuItem onClick={() => setSelectedType("document")} onKeyDown={(e) => e.key === "Enter" && setSelectedType("document")}>Documents</DropdownMenuItem>
+       <DropdownMenuItem onClick={() => setSelectedType("video")} onKeyDown={(e) => e.key === "Enter" && setSelectedType("video")}>Videos</DropdownMenuItem>
+       <DropdownMenuItem onClick={() => setSelectedType("other")} onKeyDown={(e) => e.key === "Enter" && setSelectedType("other")}>Other</DropdownMenuItem>
      </DropdownMenuContent>
    </DropdownMenu>

    <div className="border rounded-md flex" role="radiogroup" aria-label="View mode">
      <Button
        variant={viewMode === "grid" ? "default" : "ghost"}
        size="icon"
        onClick={() => setViewMode("grid")}
        className="rounded-none rounded-l-md"
+       aria-label="Grid view"
+       aria-pressed={viewMode === "grid"}
      >
        <Grid className="h-4 w-4" />
      </Button>
      <Button
        variant={viewMode === "list" ? "default" : "ghost"}
        size="icon"
        onClick={() => setViewMode("list")}
        className="rounded-none rounded-r-md"
+       aria-label="List view"
+       aria-pressed={viewMode === "list"}
      >
        <List className="h-4 w-4" />
      </Button>
    </div>
  </div>
</div>
frontend/src/app/admin/features/new/page.tsx (2)

19-26: Consider making icons more dynamic or extensible.
This hardcoded list works, but you may eventually need more icons or support custom icons. You could retrieve these from a config file or an API for better maintainability.


303-320: Robust icon picker implementation.
It’s intuitive to let users select an icon from a curated list. Just remember to handle unexpected “value” errors gracefully if data changes down the road.

frontend/src/app/features/site-location/page.tsx (1)

152-203: Avoid repeated icon usage in benefit items.
To make the code more maintainable, consider extracting a small reusable component for display items instead of replicating the “MapPin” and text blocks multiple times.

frontend/src/hooks/use-toast.ts (2)

11-12: Toast removal delay seems unusually high.
A delay of 1,000,000ms (~16.7 minutes) may compromise user experience. Reducing it can help avoid cluttered screens.


133-141: Global memory state might cause SSR or multi-instance concerns.
Since it’s stored globally, multiple render instances could conflict. Consider a provider-based store or context for improved safety in SSR.

frontend/src/app/admin/ai-technologies/new/page.tsx (4)

19-26: Consider centralizing icon options.
Extracting ICON_OPTIONS into a shared constants file can encourage reuse across multiple components.

 // Example of moving ICON_OPTIONS to a separate file:
 // src/constants/icons.ts
-const ICON_OPTIONS = [
-  { value: "BrainCircuit", label: "Brain Circuit" },
-  ...
-]
+export const ICON_OPTIONS = [
+  { value: "BrainCircuit", label: "Brain Circuit" },
+  ...
+]

147-193: Unify console logging with production error handling.
Consider removing or gating console.log calls in a production scenario. The toast notifications already provide user-facing feedback.

 try {
   // In a real implementation, this would save to an API
-  console.log("Saving new AI technology:", technology)
   ...
 } catch (error) {

316-416: Content tab code repetition.
The repeated patterns for rendering lists (e.g., useCases, metrics) might be refactored into smaller components for clarity and maintainability.


470-505: SEO tab is well-structured.
Using separate states for title and description is clear. Consider offering optional fields for meta keywords or open-graph properties to enhance SEO flexibility.

frontend/src/app/admin/features/[action]/[id]/page.tsx (2)

52-59: Icon options array.
Similar to earlier feedback, centralizing shared icon definitions could reduce duplication and ease maintenance.


274-291: Saving feature.
The simulated API call is straightforward. Consider using a dedicated service or hook to keep business logic separate from presentation.

frontend/src/app/admin/page.tsx (1)

68-93: Consider extracting the card grid to a separate component.

The dashboard card grid section is well-structured, but as the dashboard grows, it might benefit from further componentization.

Consider extracting this grid to a separate component like DashboardCardGrid to improve readability and maintainability, especially if more cards are added in the future.

frontend/src/app/admin/settings/page.tsx (4)

42-71: Consider splitting the initial state into a separate constant.

The initial state is quite large and could be extracted to improve readability.

Extract the initial state to a separate constant to make the component more readable:

-  const [settings, setSettings] = useState<SiteSettings>({
-    general: {
-      siteName: "AirQo AI",
-      siteDescription: "AI-Powered Air Quality Monitoring",
-      contactEmail: "info@airqo.net",
-      contactPhone: "+256 786 142 396",
-      address:
-        "Makerere University, Software Systems Centre, Block B, Level 3, College of Computing and Information Sciences, Plot 56 University Pool Road, Kampala, Uganda",
-    },
-    seo: {
-      metaTitle: "AirQo AI | Air Quality Monitoring",
-      metaDescription:
-        "AirQo AI provides advanced tools for monitoring, analyzing, and optimizing air quality across African cities using artificial intelligence.",
-      googleAnalyticsId: "",
-      enableSitemap: true,
-    },
-    api: {
-      apiUrl: "",
-      apiToken: "",
-    },
-    advanced: {
-      maintenanceMode: false,
-      debugMode: false,
-      cacheTimeout: 3600,
-    },
-  })
+  const DEFAULT_SETTINGS: SiteSettings = {
+    general: {
+      siteName: "AirQo AI",
+      siteDescription: "AI-Powered Air Quality Monitoring",
+      contactEmail: "info@airqo.net",
+      contactPhone: "+256 786 142 396",
+      address:
+        "Makerere University, Software Systems Centre, Block B, Level 3, College of Computing and Information Sciences, Plot 56 University Pool Road, Kampala, Uganda",
+    },
+    seo: {
+      metaTitle: "AirQo AI | Air Quality Monitoring",
+      metaDescription:
+        "AirQo AI provides advanced tools for monitoring, analyzing, and optimizing air quality across African cities using artificial intelligence.",
+      googleAnalyticsId: "",
+      enableSitemap: true,
+    },
+    api: {
+      apiUrl: "",
+      apiToken: "",
+    },
+    advanced: {
+      maintenanceMode: false,
+      debugMode: false,
+      cacheTimeout: 3600,
+    },
+  };
+  const [settings, setSettings] = useState<SiteSettings>(DEFAULT_SETTINGS);

104-166: Consolidate repetitive event handlers using a more generic approach.

There's considerable repetition in the various change handlers.

Consider implementing a more generic change handler that can handle all input types and sections. For example:

+  const handleChange = (
+    section: keyof SiteSettings,
+    field: string,
+    value: string | boolean | number
+  ) => {
+    setSettings((prev) => ({
+      ...prev,
+      [section]: {
+        ...prev[section],
+        [field]: value,
+      },
+    }));
+  };

This would allow you to replace all the specific handlers with a more generic approach.


168-193: Consider using a form library for complex forms.

The form handling is manually implemented which can become difficult to maintain as the form grows.

For a complex form like this with multiple tabs and fields, consider using a form library like Formik or React Hook Form, which would provide better validation, error handling, and state management.


222-472: Consider splitting form sections into separate components.

The form is very large with multiple tabs and sections, making it difficult to read and maintain.

Break down this large form into smaller, more manageable components. Each tab's content could be a separate component, improving code organization and maintainability. For example:

<TabsContent value="general">
  <GeneralSettingsForm 
    settings={settings.general} 
    onChange={(field, value) => handleChange('general', field, value)} 
  />
</TabsContent>
frontend/src/app/admin/ai-technologies/page.tsx (2)

141-149: Debounce the search input to improve performance.

The search input directly updates state on every keystroke, which can cause performance issues with larger datasets.

Implement debouncing for the search input to prevent excessive re-renders:

+import { useDebounce } from '@/hooks/use-debounce';

export default function AITechnologiesPage() {
  // ...existing state
+  const [searchInputValue, setSearchInputValue] = useState("");
+  const debouncedSearch = useDebounce(searchInputValue, 300);
+
+  useEffect(() => {
+    setSearchQuery(debouncedSearch);
+  }, [debouncedSearch]);

  // ...existing code

  <Input
    placeholder="Search technologies..."
    className="pl-10"
-    value={searchQuery}
-    onChange={(e) => setSearchQuery(e.target.value)}
+    value={searchInputValue}
+    onChange={(e) => setSearchInputValue(e.target.value)}
  />

This would require creating a debounce hook if one doesn't exist yet.


188-190: Add aria-label to badge for accessibility.

The badge displaying the status lacks accessibility information.

Enhance accessibility by adding an aria-label to the Badge component:

-<Badge variant={tech.status === "published" ? "default" : "secondary"}>{tech.status}</Badge>
+<Badge 
+  variant={tech.status === "published" ? "default" : "secondary"}
+  aria-label={`Status: ${tech.status}`}
+>
+  {tech.status}
+</Badge>
frontend/src/app/admin/login/page.tsx (3)

15-20: Consider using React Hook Form for login form.

The current implementation uses multiple useState hooks for form fields.

For forms, even simple ones like login, consider using a form library like React Hook Form for better validation, error handling, and state management:

const { register, handleSubmit, formState: { errors, isSubmitting } } = useForm({
  defaultValues: {
    username: '',
    password: ''
  }
});

const onSubmit = async (data) => {
  // your login logic here
};

41-67: Store error message from API response more safely.

The current implementation assumes the API response will always have a 'message' property.

Implement a more defensive approach to handling API error responses:

  if (response.ok) {
    router.push("/admin")
  } else {
-    const data = await response.json()
-    setError(data.message || "Login failed")
+    try {
+      const data = await response.json()
+      setError(data.message || "Login failed")
+    } catch (parseError) {
+      // Handle case where response is not valid JSON
+      setError("Login failed. Please try again.")
+      console.error("Error parsing response:", parseError)
+    }
  }

116-117: Use consistent loading indicators.

The loading indicator in the login button uses an emoji spinner, which is inconsistent with other loading indicators in the application.

Replace the emoji spinner with a consistent loading indicator:

-<span className="animate-spin mr-2">⏳</span>
-Logging in...
+<div className="animate-spin rounded-full h-4 w-4 border-t-2 border-b-2 border-white mr-2"></div>
+Logging in...
frontend/src/components/map/MapComponent.tsx (2)

21-21: Consider replacing delete operator with undefined assignment.

The delete operator can impact performance as flagged by the static analysis tool.

-delete (L.Icon.Default.prototype as any)._getIconUrl
+(L.Icon.Default.prototype as any)._getIconUrl = undefined
🧰 Tools
🪛 Biome (1.9.4)

[error] 20-21: Avoid the delete operator which can impact performance.

Unsafe fix: Use an undefined assignment instead.

(lint/performance/noDelete)


119-124: Improve cleanup with undefined assignment rather than delete.

Using the delete operator can impact performance as flagged by the static analysis tool.

  return () => {
    // Clean up reference when component unmounts
    if (window.map === map) {
-     delete window.map
+     window.map = undefined
    }
  }
🧰 Tools
🪛 Biome (1.9.4)

[error] 122-122: Avoid the delete operator which can impact performance.

Unsafe fix: Use an undefined assignment instead.

(lint/performance/noDelete)

frontend/src/app/page.tsx (1)

55-61: Consider improving the double-click navigation implementation.

Using window.location.href for navigation on double-click isn't ideal in a Next.js application. Consider using the Next.js Router or Link component for better performance and routing.

<div
  className="relative h-[500px] w-full max-w-[900px] rounded-xl overflow-hidden shadow-2xl mx-auto"
- onDoubleClick={() => (window.location.href = "/map")}
+ onDoubleClick={() => router.push("/map")}
>
  <Image src="/images/homeMAP.png" alt="Air quality monitoring dashboard" fill className="object-cover" />
</div>

Make sure to import and use the router:

import { useRouter } from "next/navigation"

const Home: React.FC = () => {
  const router = useRouter()
  // ...
}
frontend/src/components/ui/select.tsx (2)

70-100: Consider extracting complex conditional className logic for better readability.

The SelectContent component has a complex className string with multiple conditional parts. This could be extracted to a separate variable for better readability.

(({ className, children, position = "popper", ...props }, ref) => (
  <SelectPrimitive.Portal>
+   const contentClassNames = cn(
+     "relative z-50 max-h-[--radix-select-content-available-height] min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border bg-popover text-popover-foreground shadow-md 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 origin-[--radix-select-content-transform-origin]",
+     position === "popper" &&
+       "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
+     className
+   )
    <SelectPrimitive.Content
      ref={ref}
-     className={cn(
-       "relative z-50 max-h-[--radix-select-content-available-height] min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border bg-popover text-popover-foreground shadow-md 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 origin-[--radix-select-content-transform-origin]",
-       position === "popper" &&
-         "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
-       className
-     )}
+     className={contentClassNames}
      position={position}
      {...props}
    >

1-159: Consider adding JSDoc comments for better documentation.

While the code is well-structured, adding JSDoc comments to describe the purpose and usage of each component would improve developer experience for consumers of this component.

+/**
+ * A select component that allows users to select from a list of options.
+ * Built on top of Radix UI's Select primitives.
+ */
const Select = SelectPrimitive.Root

+/**
+ * Used to group related items in the select.
+ */
const SelectGroup = SelectPrimitive.Group

+/**
+ * The part that displays the selected value.
+ */
const SelectValue = SelectPrimitive.Value
frontend/src/lib/api.ts (1)

123-131: Consider adding retry logic for critical API calls.

While the fetchGrids function has been improved for consistency, it might benefit from retry logic like the fetchWithRetry used in LeafletMap.tsx for better resilience against network issues.

export async function fetchGrids(): Promise<Grid[]> {
  try {
-   const data = await baseFetch<{ grids: Grid[] }>("devices/grids/summary")
-   return data.grids
+   // Implement retry logic for better resilience
+   let retries = 3;
+   let delay = 1000;
+   let error;
+   
+   for (let i = 0; i < retries; i++) {
+     try {
+       const data = await baseFetch<{ grids: Grid[] }>("devices/grids/summary")
+       return data.grids;
+     } catch (e) {
+       error = e;
+       if (i < retries - 1) {
+         await new Promise(resolve => setTimeout(resolve, delay));
+         delay *= 2; // Exponential backoff
+       }
+     }
+   }
+   
+   throw error;
  } catch (error) {
    console.error("Error fetching grids:", error)
    throw error
  }
}
frontend/src/components/map/LeafletMap.tsx (1)

672-689: Consider using a lookup object for more maintainable icon mapping.

The sequential if/else statements for mapping AQI categories to icons could be replaced with a lookup object for better maintainability and potentially better performance.

const getCustomIcon = (aqiCategory: string) => {
-  let imageSrc
-
-  // Normalize category name to handle case variations
-  const category = aqiCategory?.toLowerCase() || ""
-
-  if (category.includes("good")) {
-    imageSrc = GoodAir
-  } else if (category.includes("moderate")) {
-    imageSrc = Moderate
-  } else if (category.includes("unhealthy for sensitive") || category.includes("unhealthysg")) {
-    imageSrc = UnhealthySG
-  } else if (category.includes("unhealthy")) {
-    imageSrc = Unhealthy
-  } else if (category.includes("very unhealthy") || category.includes("veryunhealthy")) {
-    imageSrc = VeryUnhealthy
-  } else if (category.includes("hazardous")) {
-    imageSrc = Hazardous
-  } else {
-    imageSrc = Invalid
-  }
+  // Normalize category name to handle case variations
+  const category = aqiCategory?.toLowerCase() || ""
+  
+  // Define mapping of category keywords to images
+  const iconMapping = {
+    'good': GoodAir,
+    'moderate': Moderate,
+    'unhealthy for sensitive': UnhealthySG,
+    'unhealthysg': UnhealthySG,
+    'unhealthy': Unhealthy,
+    'very unhealthy': VeryUnhealthy,
+    'veryunhealthy': VeryUnhealthy,
+    'hazardous': Hazardous,
+  }
+  
+  // Find the matching icon or use Invalid as default
+  const imageSrc = Object.entries(iconMapping).find(
+    ([key]) => category.includes(key)
+  )?.[1] || Invalid

  return L.icon({
    iconUrl: imageSrc,
    iconSize: [40, 40],
    iconAnchor: [20, 20],
    popupAnchor: [0, -20],
  })
}
frontend/src/components/ui/dropdown-menu.tsx (1)

78-94: Accessible MenuItem implementation with proper state handling

The DropdownMenuItem component includes proper focus states and disabled state handling. The use of gap for spacing between icons and text provides consistent layout. Consider adding ARIA attributes for enhanced accessibility.

frontend/src/app/categorize/page.tsx (4)

21-30: Proper dynamic imports for Leaflet components

The dynamic imports prevent SSR-related errors that would occur with Leaflet. The formatting changes improve readability while maintaining functionality. The commented-out code could potentially be removed if it's no longer needed.

Consider removing the commented-out code (lines 23-26) if it's no longer needed, as it adds unnecessary visual noise.


81-110: Thorough file upload handler with proper state management

The handleFileUpload function correctly manages loading state, processes multiple locations, and provides appropriate feedback to the user. Consider adding batch processing for larger datasets to improve performance.

For better performance with large datasets, consider implementing batch processing:

- for (const location of locations) {
-   const response = await getSiteCategory(location.lat, location.lng)
-   newSites.push({
-     ...location,
-     category: response.site["site-category"].category,
-     area_name: response.site["site-category"].area_name,
-   })
- }

+ // Process in batches of 5 to avoid overwhelming the API
+ const batchSize = 5;
+ for (let i = 0; i < locations.length; i += batchSize) {
+   const batch = locations.slice(i, i + batchSize);
+   const batchPromises = batch.map(location => 
+     getSiteCategory(location.lat, location.lng)
+       .then(response => ({
+         ...location,
+         category: response.site["site-category"].category,
+         area_name: response.site["site-category"].area_name,
+       }))
+   );
+   const results = await Promise.all(batchPromises);
+   newSites.push(...results);
+ }

111-144: Robust manual input handler with validation

The handleManualSubmit function properly parses and validates user input, handling potential format errors. The duplicate check is a good optimization. Consider adding more detailed validation feedback for specific formatting issues.

Consider providing more specific error messages for different validation issues:

- throw new Error("Invalid coordinates format")
+ throw new Error(`Invalid coordinates format at line: "${line}". Expected format: latitude,longitude`)

168-194: Well-structured map component with proper marker implementation

The map component is correctly set up with appropriate center coordinates and zoom level. The markers with popups provide good user feedback. Consider adding a mechanism to fit the map view to include all markers when multiple locations are added.

Consider adding a function to adjust the map bounds when new sites are added:

// Add this import
import { useMap } from "react-leaflet"

// Add this component inside SiteCategoryContent
const FitBoundsToMarkers = ({ sites }) => {
  const map = useMap()
  
  useEffect(() => {
    if (sites.length > 0) {
      const bounds = sites.reduce(
        (bounds, site) => bounds.extend([site.lat, site.lng]),
        L.latLngBounds([sites[0].lat, sites[0].lng], [sites[0].lat, sites[0].lng])
      )
      map.fitBounds(bounds, { padding: [50, 50] })
    }
  }, [sites, map])
  
  return null
}

// Add this component to the MapContainer
<FitBoundsToMarkers sites={sites} />
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 3cee388 and d77eee3.

⛔ Files ignored due to path filters (1)
  • frontend/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (73)
  • frontend/components.json (1 hunks)
  • frontend/next.config.ts (1 hunks)
  • frontend/package.json (2 hunks)
  • frontend/src/app/Loading.tsx (1 hunks)
  • frontend/src/app/about/page.tsx (5 hunks)
  • frontend/src/app/admin/ai-technologies/loading.tsx (1 hunks)
  • frontend/src/app/admin/ai-technologies/new/page.tsx (1 hunks)
  • frontend/src/app/admin/ai-technologies/page.tsx (1 hunks)
  • frontend/src/app/admin/features/[action]/[id]/page.tsx (1 hunks)
  • frontend/src/app/admin/features/loading.tsx (1 hunks)
  • frontend/src/app/admin/features/new/page.tsx (1 hunks)
  • frontend/src/app/admin/features/page.tsx (1 hunks)
  • frontend/src/app/admin/layout.tsx (1 hunks)
  • frontend/src/app/admin/login/page.tsx (1 hunks)
  • frontend/src/app/admin/media/loading.tsx (1 hunks)
  • frontend/src/app/admin/media/page.tsx (1 hunks)
  • frontend/src/app/admin/media/upload/page.tsx (1 hunks)
  • frontend/src/app/admin/page.tsx (1 hunks)
  • frontend/src/app/admin/settings/page.tsx (1 hunks)
  • frontend/src/app/api/admin/auth/check/route.ts (1 hunks)
  • frontend/src/app/api/admin/auth/login/route.ts (1 hunks)
  • frontend/src/app/api/admin/auth/logout/route.ts (1 hunks)
  • frontend/src/app/api/admin/media/[id]/route.ts (1 hunks)
  • frontend/src/app/api/admin/media/route.ts (1 hunks)
  • frontend/src/app/api/admin/media/upload/route.ts (1 hunks)
  • frontend/src/app/categorize/page.tsx (3 hunks)
  • frontend/src/app/features/air-quality-categorization/page.tsx (1 hunks)
  • frontend/src/app/features/data-analytics/page.tsx (1 hunks)
  • frontend/src/app/features/health-impact/page.tsx (1 hunks)
  • frontend/src/app/features/interactive-mapping/page.tsx (1 hunks)
  • frontend/src/app/features/machine-learning/page.tsx (1 hunks)
  • frontend/src/app/features/site-location/page.tsx (1 hunks)
  • frontend/src/app/layout.tsx (1 hunks)
  • frontend/src/app/locate/page.tsx (3 hunks)
  • frontend/src/app/not-found.tsx (2 hunks)
  • frontend/src/app/page.tsx (4 hunks)
  • frontend/src/app/reports/page.tsx (4 hunks)
  • frontend/src/components/Controls/ControlPanel.tsx (7 hunks)
  • frontend/src/components/Controls/FileUpload.tsx (2 hunks)
  • frontend/src/components/Controls/SearchBar.tsx (4 hunks)
  • frontend/src/components/GoogleAnalytics.tsx (2 hunks)
  • frontend/src/components/error-boundary.tsx (1 hunks)
  • frontend/src/components/map/LeafletMap.tsx (13 hunks)
  • frontend/src/components/map/MapComponent.tsx (4 hunks)
  • frontend/src/components/map/NavigationControls.tsx (1 hunks)
  • frontend/src/components/navigation/navigation.tsx (3 hunks)
  • frontend/src/components/ui/alert.tsx (1 hunks)
  • frontend/src/components/ui/badge.tsx (1 hunks)
  • frontend/src/components/ui/button.tsx (1 hunks)
  • frontend/src/components/ui/card.tsx (1 hunks)
  • frontend/src/components/ui/dropdown-menu.tsx (1 hunks)
  • frontend/src/components/ui/input.tsx (1 hunks)
  • frontend/src/components/ui/label.tsx (1 hunks)
  • frontend/src/components/ui/loading-spinner.tsx (1 hunks)
  • frontend/src/components/ui/select.tsx (1 hunks)
  • frontend/src/components/ui/switch.tsx (1 hunks)
  • frontend/src/components/ui/table.tsx (1 hunks)
  • frontend/src/components/ui/tabs.tsx (1 hunks)
  • frontend/src/components/ui/textarea.tsx (1 hunks)
  • frontend/src/components/ui/toast.tsx (1 hunks)
  • frontend/src/components/ui/toaster.tsx (1 hunks)
  • frontend/src/hooks/use-api.tsx (1 hunks)
  • frontend/src/hooks/use-toast.ts (1 hunks)
  • frontend/src/lib/api.ts (1 hunks)
  • frontend/src/lib/env.ts (1 hunks)
  • frontend/src/lib/mongodb.ts (1 hunks)
  • frontend/src/lib/types.ts (1 hunks)
  • frontend/src/lib/utils.ts (1 hunks)
  • frontend/src/services/apiService.tsx (2 hunks)
  • frontend/src/styles/globals.css (5 hunks)
  • frontend/src/types/leaflet-geosearch.d.ts (1 hunks)
  • frontend/src/utils/index.ts (1 hunks)
  • frontend/tailwind.config.ts (2 hunks)
🧰 Additional context used
🧬 Code Definitions (36)
frontend/src/app/admin/features/loading.tsx (2)
frontend/src/app/admin/ai-technologies/loading.tsx (1)
  • Loading (1-3)
frontend/src/app/admin/media/loading.tsx (1)
  • Loading (1-3)
frontend/src/app/api/admin/auth/logout/route.ts (3)
frontend/src/app/api/admin/auth/login/route.ts (1)
  • POST (9-33)
frontend/src/app/api/admin/media/upload/route.ts (1)
  • POST (6-31)
frontend/src/app/api/admin/media/route.ts (1)
  • POST (17-38)
frontend/src/components/ui/textarea.tsx (1)
frontend/src/lib/utils.ts (1)
  • cn (4-6)
frontend/src/components/ui/alert.tsx (1)
frontend/src/lib/utils.ts (1)
  • cn (4-6)
frontend/src/app/api/admin/auth/check/route.ts (2)
frontend/src/app/api/admin/media/[id]/route.ts (1)
  • GET (3-26)
frontend/src/app/api/admin/media/route.ts (1)
  • GET (3-15)
frontend/src/app/admin/features/new/page.tsx (9)
frontend/src/hooks/use-toast.ts (2)
  • useToast (194-194)
  • toast (194-194)
frontend/src/components/ui/button.tsx (1)
  • Button (57-57)
frontend/src/components/ui/tabs.tsx (4)
  • Tabs (55-55)
  • TabsList (55-55)
  • TabsTrigger (55-55)
  • TabsContent (55-55)
frontend/src/components/ui/card.tsx (5)
  • Card (76-76)
  • CardHeader (76-76)
  • CardTitle (76-76)
  • CardDescription (76-76)
  • CardContent (76-76)
frontend/src/components/ui/label.tsx (1)
  • Label (26-26)
frontend/src/components/ui/input.tsx (1)
  • Input (22-22)
frontend/src/components/ui/textarea.tsx (1)
  • Textarea (22-22)
frontend/src/components/ui/select.tsx (5)
  • Select (149-149)
  • SelectTrigger (152-152)
  • SelectValue (151-151)
  • SelectContent (153-153)
  • SelectItem (155-155)
frontend/src/components/ui/switch.tsx (1)
  • Switch (29-29)
frontend/src/components/ui/label.tsx (1)
frontend/src/lib/utils.ts (1)
  • cn (4-6)
frontend/src/app/layout.tsx (2)
frontend/src/components/GoogleAnalytics.tsx (1)
  • GoogleAnalytics (13-52)
frontend/src/components/ui/toaster.tsx (1)
  • Toaster (13-35)
frontend/src/app/admin/features/page.tsx (5)
frontend/src/components/ui/button.tsx (1)
  • Button (57-57)
frontend/src/components/ui/card.tsx (6)
  • Card (76-76)
  • CardHeader (76-76)
  • CardTitle (76-76)
  • CardDescription (76-76)
  • CardContent (76-76)
  • CardFooter (76-76)
frontend/src/components/ui/input.tsx (1)
  • Input (22-22)
frontend/src/components/ui/table.tsx (6)
  • Table (112-112)
  • TableHeader (113-113)
  • TableRow (117-117)
  • TableHead (116-116)
  • TableBody (114-114)
  • TableCell (118-118)
frontend/src/components/ui/badge.tsx (1)
  • Badge (36-36)
frontend/src/components/ui/loading-spinner.tsx (1)
frontend/src/lib/utils.ts (1)
  • cn (4-6)
frontend/src/app/admin/media/upload/page.tsx (5)
frontend/src/hooks/use-toast.ts (2)
  • useToast (194-194)
  • toast (194-194)
frontend/src/components/ui/button.tsx (1)
  • Button (57-57)
frontend/src/components/ui/card.tsx (6)
  • Card (76-76)
  • CardHeader (76-76)
  • CardTitle (76-76)
  • CardDescription (76-76)
  • CardContent (76-76)
  • CardFooter (76-76)
frontend/src/components/ui/label.tsx (1)
  • Label (26-26)
frontend/src/components/ui/input.tsx (1)
  • Input (22-22)
frontend/src/components/ui/toaster.tsx (2)
frontend/src/hooks/use-toast.ts (1)
  • useToast (194-194)
frontend/src/components/ui/toast.tsx (6)
  • ToastProvider (122-122)
  • Toast (124-124)
  • ToastTitle (125-125)
  • ToastDescription (126-126)
  • ToastClose (127-127)
  • ToastViewport (123-123)
frontend/src/components/ui/badge.tsx (1)
frontend/src/lib/utils.ts (1)
  • cn (4-6)
frontend/src/components/error-boundary.tsx (1)
frontend/src/components/ui/button.tsx (1)
  • Button (57-57)
frontend/src/app/reports/page.tsx (1)
frontend/src/components/navigation/navigation.tsx (1)
  • Navigation (18-81)
frontend/src/app/admin/ai-technologies/page.tsx (5)
frontend/src/components/ui/button.tsx (1)
  • Button (57-57)
frontend/src/components/ui/card.tsx (6)
  • Card (76-76)
  • CardHeader (76-76)
  • CardTitle (76-76)
  • CardDescription (76-76)
  • CardContent (76-76)
  • CardFooter (76-76)
frontend/src/components/ui/input.tsx (1)
  • Input (22-22)
frontend/src/components/ui/table.tsx (6)
  • Table (112-112)
  • TableHeader (113-113)
  • TableRow (117-117)
  • TableHead (116-116)
  • TableBody (114-114)
  • TableCell (118-118)
frontend/src/components/ui/badge.tsx (1)
  • Badge (36-36)
frontend/src/app/api/admin/media/route.ts (5)
frontend/src/app/api/admin/auth/check/route.ts (1)
  • GET (4-17)
frontend/src/app/api/admin/media/[id]/route.ts (1)
  • GET (3-26)
frontend/src/app/api/admin/auth/login/route.ts (1)
  • POST (9-33)
frontend/src/app/api/admin/auth/logout/route.ts (1)
  • POST (4-14)
frontend/src/app/api/admin/media/upload/route.ts (1)
  • POST (6-31)
frontend/src/app/admin/settings/page.tsx (8)
frontend/src/hooks/use-toast.ts (2)
  • useToast (194-194)
  • toast (194-194)
frontend/src/components/ui/button.tsx (1)
  • Button (57-57)
frontend/src/components/ui/tabs.tsx (4)
  • Tabs (55-55)
  • TabsList (55-55)
  • TabsTrigger (55-55)
  • TabsContent (55-55)
frontend/src/components/ui/card.tsx (6)
  • Card (76-76)
  • CardHeader (76-76)
  • CardTitle (76-76)
  • CardDescription (76-76)
  • CardContent (76-76)
  • CardFooter (76-76)
frontend/src/components/ui/label.tsx (1)
  • Label (26-26)
frontend/src/components/ui/input.tsx (1)
  • Input (22-22)
frontend/src/components/ui/textarea.tsx (1)
  • Textarea (22-22)
frontend/src/components/ui/switch.tsx (1)
  • Switch (29-29)
frontend/src/components/ui/button.tsx (1)
frontend/src/lib/utils.ts (1)
  • cn (4-6)
frontend/src/app/admin/media/page.tsx (6)
frontend/src/hooks/use-toast.ts (2)
  • useToast (194-194)
  • toast (194-194)
frontend/src/components/ui/button.tsx (1)
  • Button (57-57)
frontend/src/components/ui/card.tsx (6)
  • Card (76-76)
  • CardHeader (76-76)
  • CardTitle (76-76)
  • CardDescription (76-76)
  • CardContent (76-76)
  • CardFooter (76-76)
frontend/src/components/ui/input.tsx (1)
  • Input (22-22)
frontend/src/components/ui/dropdown-menu.tsx (4)
  • DropdownMenu (186-186)
  • DropdownMenuTrigger (187-187)
  • DropdownMenuContent (188-188)
  • DropdownMenuItem (189-189)
frontend/src/components/ui/badge.tsx (1)
  • Badge (36-36)
frontend/src/components/ui/card.tsx (1)
frontend/src/lib/utils.ts (1)
  • cn (4-6)
frontend/src/components/ui/switch.tsx (1)
frontend/src/lib/utils.ts (1)
  • cn (4-6)
frontend/src/app/api/admin/media/[id]/route.ts (2)
frontend/src/app/api/admin/auth/check/route.ts (1)
  • GET (4-17)
frontend/src/app/api/admin/media/route.ts (1)
  • GET (3-15)
frontend/src/components/ui/input.tsx (1)
frontend/src/lib/utils.ts (1)
  • cn (4-6)
frontend/src/hooks/use-toast.ts (1)
frontend/src/components/ui/toast.tsx (3)
  • ToastProps (120-120)
  • ToastActionElement (121-121)
  • Toast (124-124)
frontend/src/app/features/interactive-mapping/page.tsx (2)
frontend/src/components/navigation/navigation.tsx (1)
  • Navigation (18-81)
frontend/src/components/ui/button.tsx (1)
  • Button (57-57)
frontend/src/app/admin/page.tsx (2)
frontend/src/components/ui/button.tsx (1)
  • Button (57-57)
frontend/src/components/ui/card.tsx (5)
  • Card (76-76)
  • CardHeader (76-76)
  • CardTitle (76-76)
  • CardDescription (76-76)
  • CardContent (76-76)
frontend/src/lib/api.ts (1)
frontend/src/lib/types.ts (6)
  • SiteLocatorPayload (9-16)
  • SiteLocatorResponse (35-38)
  • SiteCategoryResponse (47-62)
  • AirQualityReportPayload (69-73)
  • AirQualityReportResponse (103-110)
  • Grid (125-132)
frontend/src/components/ui/toast.tsx (1)
frontend/src/lib/utils.ts (1)
  • cn (4-6)
frontend/src/components/ui/table.tsx (1)
frontend/src/lib/utils.ts (1)
  • cn (4-6)
frontend/src/components/ui/dropdown-menu.tsx (1)
frontend/src/lib/utils.ts (1)
  • cn (4-6)
frontend/src/app/admin/login/page.tsx (5)
frontend/src/components/ui/card.tsx (6)
  • Card (76-76)
  • CardHeader (76-76)
  • CardTitle (76-76)
  • CardDescription (76-76)
  • CardContent (76-76)
  • CardFooter (76-76)
frontend/src/components/ui/alert.tsx (2)
  • Alert (59-59)
  • AlertDescription (59-59)
frontend/src/components/ui/label.tsx (1)
  • Label (26-26)
frontend/src/components/ui/input.tsx (1)
  • Input (22-22)
frontend/src/components/ui/button.tsx (1)
  • Button (57-57)
frontend/src/app/admin/features/[action]/[id]/page.tsx (8)
frontend/src/components/ui/button.tsx (1)
  • Button (57-57)
frontend/src/components/ui/tabs.tsx (4)
  • Tabs (55-55)
  • TabsList (55-55)
  • TabsTrigger (55-55)
  • TabsContent (55-55)
frontend/src/components/ui/card.tsx (5)
  • Card (76-76)
  • CardHeader (76-76)
  • CardTitle (76-76)
  • CardDescription (76-76)
  • CardContent (76-76)
frontend/src/components/ui/label.tsx (1)
  • Label (26-26)
frontend/src/components/ui/input.tsx (1)
  • Input (22-22)
frontend/src/components/ui/textarea.tsx (1)
  • Textarea (22-22)
frontend/src/components/ui/select.tsx (5)
  • Select (149-149)
  • SelectTrigger (152-152)
  • SelectValue (151-151)
  • SelectContent (153-153)
  • SelectItem (155-155)
frontend/src/components/ui/switch.tsx (1)
  • Switch (29-29)
frontend/src/components/ui/select.tsx (1)
frontend/src/lib/utils.ts (1)
  • cn (4-6)
frontend/src/app/admin/layout.tsx (1)
frontend/src/components/ui/button.tsx (1)
  • Button (57-57)
frontend/src/app/admin/ai-technologies/new/page.tsx (9)
frontend/src/hooks/use-toast.ts (2)
  • useToast (194-194)
  • toast (194-194)
frontend/src/components/ui/button.tsx (1)
  • Button (57-57)
frontend/src/components/ui/tabs.tsx (4)
  • Tabs (55-55)
  • TabsList (55-55)
  • TabsTrigger (55-55)
  • TabsContent (55-55)
frontend/src/components/ui/card.tsx (5)
  • Card (76-76)
  • CardHeader (76-76)
  • CardTitle (76-76)
  • CardDescription (76-76)
  • CardContent (76-76)
frontend/src/components/ui/label.tsx (1)
  • Label (26-26)
frontend/src/components/ui/input.tsx (1)
  • Input (22-22)
frontend/src/components/ui/textarea.tsx (1)
  • Textarea (22-22)
frontend/src/components/ui/select.tsx (5)
  • Select (149-149)
  • SelectTrigger (152-152)
  • SelectValue (151-151)
  • SelectContent (153-153)
  • SelectItem (155-155)
frontend/src/components/ui/switch.tsx (1)
  • Switch (29-29)
🪛 Biome (1.9.4)
frontend/src/components/map/MapComponent.tsx

[error] 20-21: Avoid the delete operator which can impact performance.

Unsafe fix: Use an undefined assignment instead.

(lint/performance/noDelete)


[error] 122-122: Avoid the delete operator which can impact performance.

Unsafe fix: Use an undefined assignment instead.

(lint/performance/noDelete)

🔇 Additional comments (206)
frontend/src/lib/env.ts (1)

17-20: Excellent use of documentation and export pattern.

The JSDoc comment clearly explains the purpose of the exported constant. Exporting the result of validateEnv() ensures that environment validation happens at application initialization, which is a good fail-fast approach.

frontend/src/app/reports/page.tsx (7)

1-4: Consistent import formatting.

The import statements follow the project's updated formatting style without semicolons, which contributes to codebase consistency.


12-13: Consistent return statement formatting.

The parenthesis has been moved to its own line, which follows the project's new formatting style.


23-24: Improved text formatting for readability.

The paragraph text has been reformatted to flow naturally on a single line per thought, which improves code readability while maintaining the same displayed content.


35-36: Improved text formatting for readability.

Similar to the previous text block, this paragraph has been reformatted for better code readability.


39-50: Consistent InfoBox component formatting.

The InfoBox components have been reformatted to follow the same style as the rest of the file, with consistent indentation and line wrapping for content.


59-62: Interface formatting aligned with project style.

The InfoBoxProps interface properties now follow the project's formatting convention of omitting semicolons.


73-75: Consistent closing bracket formatting and spacing.

The closing bracket has been moved to its own line, and an empty line has been added after the function, which contributes to the uniform styling across the codebase.

frontend/src/lib/types.ts (5)

1-4: Consistent interface property formatting.

The Location interface properties have been updated to remove semicolons, aligning with the project's new formatting style.


5-7: Consistent interface property formatting.

The FileUploadProps interface has been updated to follow the same formatting pattern, improving code style consistency.


9-16: Consistent formatting for complex interface properties.

The SiteLocatorPayload interface's properties, including nested objects and arrays, have been reformatted without semicolons while maintaining clear type definitions.


18-23: Consistent formatting for nested object types.

The SiteInformation interface properties have been updated to follow the project's formatting conventions, including the nested object type definition.


25-149: Comprehensive formatting update across all interfaces.

The remaining interfaces (SiteLocation, SiteLocatorResponse, ControlPanelProps, etc.) have all been consistently updated to follow the project's semicolon-free style. The changes are thorough and maintain good documentation through comments for each property.

This large-scale formatting update contributes significantly to codebase uniformity while preserving all type information and documentation.

frontend/src/app/admin/ai-technologies/loading.tsx (2)

1-3: Simple loading component implementation.

This minimal Loading component follows Next.js conventions for loading states. Returning null is appropriate when no visual loading indicator is needed at this point in the application.


4-4: Proper file formatting.

The trailing blank line follows the formatting convention seen in other files.

frontend/src/app/admin/media/loading.tsx (1)

1-4: Good use of Next.js loading pattern

The implementation of a Loading component that returns null is consistent with Next.js app router convention. This approach works as a placeholder that will be rendered during the loading state when navigating to the media admin section.

For more advanced user feedback, you might consider replacing null with a loading spinner or skeleton UI in the future, but this minimal implementation is perfectly valid for now.

frontend/src/app/admin/features/loading.tsx (1)

1-4: Consistent loading pattern across admin sections

This implementation matches the loading components in other admin sections (like media and ai-technologies), maintaining a consistent approach to loading states throughout the application.

As with the other loading components, you might consider enhancing this with a visual loading indicator in the future, but the current implementation is valid.

frontend/src/utils/index.ts (1)

1-3: Clean formatting change

The removal of semicolons aligns with what appears to be a project-wide formatting standardization. The function logic remains correct and handles trailing slashes properly.

frontend/src/app/api/admin/auth/logout/route.ts (1)

1-14: Well-implemented logout endpoint

The logout endpoint follows good practices:

  • Proper error handling with try/catch blocks
  • Clear error logging
  • Appropriate use of NextResponse for JSON responses
  • Consistent error status codes (500 for server errors)
  • Matches the pattern used in other admin API routes

This complements the login functionality seen in the login route and completes the authentication flow nicely.

frontend/src/lib/utils.ts (2)

1-2: Optimized Imports.
The consolidated import statement for clsx and ClassValue is now cleaner and easier to read, matching our style guidelines.


4-6: Refined cn Function Formatting.
The updated formatting of the cn function—particularly the streamlined return statement without extra indentation or a semicolon—improves clarity while maintaining identical functionality.

frontend/src/app/Loading.tsx (2)

1-21: Consistent Component Structure.
The Loading component is implemented clearly with proper JSX layout. The removal of the semicolon after the return statement aligns with our updated style guidelines without affecting the component logic.


23-24: Clean Export Statement.
The export of the Loading component is succinct and correctly formatted, enhancing overall code consistency.

frontend/next.config.ts (3)

1-2: Clear Type-Heavy Import.
The import statement using TypeScript’s import type syntax for NextConfig is precise and ensures proper type-checking.


3-7: Streamlined Next.js Configuration.
The nextConfig object retains its intended settings with minimal formatting clutter. The removal of semicolons and the addition of trailing newlines contribute to code clarity and consistency.


7-8: Proper Module Export.
The separate export statement for nextConfig is straightforward and adheres to our updated formatting conventions.

frontend/components.json (1)

1-21: Well-Structured New Configuration File.
The new JSON configuration is concise and clearly maps out component settings, Tailwind options, and alias definitions. This centralized configuration will aid in maintainability and can streamline our component imports across the project.

frontend/src/types/leaflet-geosearch.d.ts (1)

1-29: Consistent TypeScript Declarations.
The removal of semicolons in this .d.ts file brings uniformity with our project’s formatting standards while leaving the type declarations' functionality intact.

frontend/src/app/locate/page.tsx (1)

1-173: Coding style changes look good!

The changes in this file are purely stylistic, focusing on removing semicolons and adjusting line breaks for consistent formatting without altering any functionality. This maintains a clean, consistent code style across the project.

frontend/src/components/map/NavigationControls.tsx (1)

1-5: Clean simplification of the component

The return statement has been condensed to a single line, which is appropriate for this simple component. The removal of the semicolon from the "use client" directive maintains consistency with the project's coding style.

frontend/src/styles/globals.css (5)

29-29: Improved formatting for better readability.

The spacing between values in the rgba function has been standardized, which improves code readability while maintaining the same visual output.


150-151: Improved comment formatting.

The spacing before the comment has been fixed, making the code more consistent with standard CSS commenting conventions.


169-170: Standardized string quotes.

Changed single quotes to double quotes for the content property value, which provides better consistency with other string values in the codebase.


196-196: Consistent spacing in rgba values.

Added proper spacing in rgba color values, matching the formatting style used elsewhere in the file.


202-202: Standardized content property quotes.

Switched from single quotes to double quotes for consistency with other string values in the stylesheet.

frontend/src/components/ui/input.tsx (1)

1-23: Well-structured reusable Input component.

This new component follows React best practices with proper TypeScript typing, ref forwarding, and accessibility considerations. The component is flexible, allowing all standard input props while providing consistent styling through Tailwind classes.

The styling includes important accessibility features:

  • Focus states with focus-visible:ring-1
  • Disabled states with appropriate cursor and opacity changes
  • Responsive text sizing with the md:text-sm class
frontend/src/components/ui/textarea.tsx (1)

1-22: Well-implemented Textarea component with consistent patterns.

This component follows the same implementation pattern as the Input component, providing a consistent developer experience. The component includes proper TypeScript typing, ref forwarding, and accessibility features.

The consistent implementation between Input and Textarea components demonstrates good practice for maintaining a cohesive UI system.

frontend/src/app/api/admin/auth/login/route.ts (1)

17-23: Good cookie security settings.

The cookie configuration follows security best practices:

  • HTTP-only to prevent JavaScript access
  • Secure in production environments
  • Strict same-site policy to prevent CSRF attacks
  • Reasonable expiration time
  • Appropriate path setting
frontend/tailwind.config.ts (1)

1-1: Style consistency update looks good.

These changes align with modern TypeScript style practices by removing unnecessary semicolons. The modifications are purely stylistic and don't affect functionality while contributing to a more consistent codebase.

Also applies to: 18-19

frontend/src/components/GoogleAnalytics.tsx (8)

1-4: Import formatting looks good.

The cleaned-up import statements improve readability and maintain consistent styling.


8-9: Proper type declarations for global window object.

The correctly formatted type definitions ensure TypeScript properly recognizes the global window extensions needed for Google Analytics.


14-16: Hook usage looks good.

Clean variable declarations with consistent formatting.


19-20: Improved code readability.

Condensing the conditional checks and page path construction improves readability while maintaining the same functionality.

Also applies to: 24-24, 26-29


32-33: Proper error handling.

Good practice to provide a warning when the measurement ID is missing.


39-39: Script loading looks good.

The afterInteractive strategy ensures optimal loading of the Google Analytics script.


60-64: Clean type definitions.

Consistently formatted parameter types in the trackEvent function.


65-71: Well-formatted event tracking implementation.

The gtag event tracking implementation is clean and follows proper error checking.

frontend/src/components/ui/loading-spinner.tsx (2)

1-8: Well-structured component interface.

The component imports and props interface are cleanly defined with appropriate type definitions for the size variants.


10-16: Good implementation of size variants.

The size configuration object is well-structured and provides clear dimension options.

frontend/src/app/api/admin/auth/check/route.ts (3)

1-3: Good imports for API route.

The imports are appropriate for checking cookies in Next.js API routes.


12-12: Good use of appropriate status code.

Returning a 401 status code for unauthenticated requests follows HTTP standards.


13-16: Appropriate error handling.

Good practice to catch errors and return a 500 status code with a meaningful message.

frontend/src/components/ui/label.tsx (1)

1-26: Well-implemented Label component with proper accessibility support.

This is a well-structured React component that utilizes the Radix UI library to create an accessible label. The component uses React.forwardRef correctly to pass refs down to the primitive component, and the styling is handled cleanly with class-variance-authority. The use of the cn utility function for class name merging is a good practice for maintaining consistent styling across the application.

frontend/src/app/layout.tsx (3)

7-7: Good addition of Toaster component for notifications.

Adding the Toaster component will enable toast notifications throughout the application, improving user feedback.


37-37: Well-placed Toaster component in the layout hierarchy.

The Toaster component is properly placed at the root layout level, ensuring toast notifications are available throughout the application.


13-18: Consistent formatting of closing braces.

The formatting changes in the localFont calls improve code consistency.

frontend/src/components/ui/alert.tsx (3)

1-5: Appropriate imports and setup for the Alert component system.

The imports are well-structured, bringing in React, class-variance-authority for component variants, and the cn utility function for class name composition.


22-33: Well-structured Alert component with proper accessibility.

The component correctly utilizes forwardRef, includes TypeScript typing, and sets the appropriate role="alert" for accessibility. The use of displayName is good practice for debugging.


59-59: Clean export of alert components.

The export is concise and provides all three components for easy import elsewhere.

frontend/src/app/admin/layout.tsx (2)

1-10: Appropriate imports and client directive for the AdminLayout component.

The component correctly uses the "use client" directive and imports all necessary dependencies for layout, navigation, and UI elements.


11-24: Well-structured component setup with appropriate state management.

The component is properly typed and sets up essential state variables for authentication, loading status, and sidebar visibility.

frontend/src/components/navigation/navigation.tsx (3)

30-36: Great accessibility improvements for the mobile menu button.

Adding aria-label, aria-expanded, and aria-controls attributes significantly improves the accessibility of the mobile menu button. These attributes help screen reader users understand the button's purpose and current state.


60-60: Good addition of id for the mobile menu.

Adding an id attribute to the mobile menu element creates a proper association with the button that controls it via the aria-controls attribute. This is an important accessibility enhancement.


71-71: Excellent addition of aria-current attribute.

Adding the aria-current attribute to navigation links helps screen readers announce the current page, improving the navigation experience for users with disabilities.

frontend/src/components/ui/tabs.tsx (1)

1-56: Well-structured Tab components implementation.

This implementation of the tabs component using Radix UI primitives is clean and follows good React practices:

  • Proper use of React.forwardRef for all components
  • Consistent pattern of setting displayName for debugging
  • Good use of the cn utility function for handling class names
  • Comprehensive styling including accessibility features

The implementation provides a complete tab system with TabsList, TabsTrigger, and TabsContent components that can be easily composed together.

frontend/src/components/ui/card.tsx (1)

1-76: Excellent Card component system implementation.

This Card component system is well-designed and follows React best practices:

  • Good componentization with Card, CardHeader, CardTitle, CardDescription, CardContent, and CardFooter
  • Proper use of React.forwardRef for all components
  • Consistent use of displayName for debugging
  • Well-structured className handling with the cn utility function
  • Sensible default styling with the ability to override via className prop

This implementation provides a flexible and reusable card system that can be composed in various ways to build complex UI layouts.

frontend/src/components/Controls/FileUpload.tsx (4)

1-2: Good addition of "use client" directive.

Adding the "use client" directive correctly marks this component for client-side rendering in Next.js.


19-22: Improved formatting for better readability.

The formatting changes to the regex and the sets for latitude and longitude aliases enhance code consistency.


56-57: Better type safety with Number.parseFloat.

Using Number.parseFloat instead of global parseFloat is a good practice as it's more explicit and maintains better code organization.


60-61: Improved error handling logic.

The error message for no valid coordinates is clear and the filtering approach ensures only valid data points are processed.

frontend/src/app/features/machine-learning/page.tsx (1)

40-51: Good implementation of image error handling.

The error handling for the image component is well-implemented, providing a fallback when the image fails to load. This ensures the UI remains intact even if the image is unavailable.

frontend/src/components/ui/badge.tsx (5)

1-5: Imports look good

The imports are properly structured, bringing in React, the necessary class-variance-authority utilities, and the custom cn utility function for class name composition.


6-24: Well-structured variant system for the Badge component

The badgeVariants implementation uses class-variance-authority effectively to create a flexible styling system. The variants (default, secondary, destructive, and outline) provide good coverage for common use cases while maintaining a consistent design language.


26-28: Type definitions are properly structured

The BadgeProps interface correctly extends both the HTMLDivElement attributes and the variant props, enabling proper TypeScript type checking for the component.


30-34: Clean implementation of the Badge component

The component follows React best practices with proper props destructuring and spreading. The use of the cn utility ensures classes are combined correctly.


36-36: Export statement is correct

Both the Badge component and badgeVariants are exported, allowing for flexibility in how consumers use this component.

frontend/src/app/about/page.tsx (5)

3-3: Import statement looks good

The explicit Suspense import is properly formatted.


15-18: Text formatting improvement

The paragraph text formatting enhances readability.


29-29: Improved responsive layout

The grid layout has been updated to use a more nuanced responsive approach with 2 columns on medium screens and 4 columns on large screens. This is a good improvement for better layout on medium-sized devices.


66-66: Text formatting looks good

The formatting of the "Get in Touch" section text improves readability.


139-139: Component structure formatting is appropriate

The div element formatting follows standard JSX conventions.

frontend/src/app/api/admin/media/route.ts (1)

1-2: Import statement is correct

The NextResponse import is properly structured for creating API responses.

frontend/src/components/Controls/ControlPanel.tsx (13)

1-10: Import statements are properly formatted

The updated import statements follow a consistent style without semicolons.


14-15: Interface definition is properly formatted

The ExtendedControlPanelProps interface is properly defined with the onBoundaryFound method.


24-29: State variable declarations are properly formatted

The useState declarations follow a consistent format.


38-50: Validation logic formatting looks good

The validation function's formatting has been improved for better readability.


54-55: Simplified return statement improves readability

The simplified return statement is more concise and follows better coding practices.


59-64: Improved payload construction formatting

The formatting of the polygon coordinates mapping and must_have_locations assignment is now more readable.


67-70: minDistance handling is properly formatted

The logic for handling the optional minDistance parameter is formatted consistently.


72-89: Submit handler formatting is improved

The async submit handler has been formatted consistently, maintaining proper error handling.


93-112: handleAddLocation function formatting is improved

The location addition handler has been formatted for better readability while maintaining the same validation logic.


123-123: Label formatting is consistent

The label for the Must-Have Locations section is properly formatted.


146-146: Location counter text formatting is improved

The text showing the count of added locations is formatted consistently.


151-151: Input labels are properly formatted

The labels for the minimum distance and number of sensors inputs follow a consistent format.

Also applies to: 164-164


192-193: Return statement formatting is correct

The component's return statement follows proper formatting conventions.

frontend/src/hooks/use-api.tsx (5)

1-3: Good use of Next.js "use client" directive

The "use client" directive at the top is correctly used to indicate this is a client-side component, which is necessary for React hooks.


5-14: Well-defined TypeScript interfaces

The interfaces UseApiOptions<T> and ApiState<T> are well-defined with proper generic typing, making the hook strongly typed and flexible for different API response types.


16-21: Properly initialized state with generics

The state is correctly initialized with appropriate default values and proper generic typing.


23-40: Well-implemented execute function with proper error handling

The execute function:

  • Uses useCallback for optimization with appropriate dependencies
  • Updates loading state during execution
  • Has proper error handling with type checking
  • Returns data for chaining
  • Correctly calls optional callback functions

42-49: Clean API surface with reset functionality

The hook returns a clean API with state, execute function, and a reset function. The reset function is properly memoized with an empty dependency array.

frontend/src/components/Controls/SearchBar.tsx (3)

1-8: LGTM - Consistent import style and client directive

The "use client" directive is correctly placed at the top of the file, and the import statements follow a consistent style.


16-19: Clean state initialization

The component's state is well-organized with separate state variables for query, suggestions, and loading status.


45-47: Good use of debouncing for API calls

The debouncing implementation is well done, preventing excessive API calls as the user types.

frontend/src/app/features/air-quality-categorization/page.tsx (1)

1-8: Proper setup for client-side component

The "use client" directive is correctly placed at the top of the file, and necessary imports are included.

frontend/src/components/ui/button.tsx (1)

1-58: Well-structured, reusable Button component with comprehensive variants.

The implementation follows modern React patterns using forwardRef and Radix UI's Slot component. The buttonVariants function using class-variance-authority creates a flexible, type-safe styling system with good default options.

A few observations:

  • Good use of the Slot pattern for component composition (asChild prop)
  • Comprehensive set of variants and sizes
  • Proper TypeScript typing with interface extension
  • Clean integration with the cn utility function
frontend/src/app/not-found.tsx (6)

3-5: Consistent formatting improvement.

The removal of trailing semicolons aligns with modern JavaScript/TypeScript style preferences.


8-8: Clean variable declaration.

Consistent formatting by removing the trailing semicolon.


13-14: Improved JSX formatting.

Condensing these elements onto single lines improves readability while maintaining the same functionality.


16-17: Consistent JSX formatting.

The continuation line is properly indented and maintains readability.


27-28: Clean return statement.

Removing unnecessary line breaks and trailing semicolon improves code style consistency.


35-38: Improved component export pattern.

Moving the export to the end of the file and maintaining consistent formatting improves readability.

frontend/src/components/error-boundary.tsx (4)

1-6: Good error boundary implementation with proper imports.

The component correctly imports the necessary elements for error handling and UI components.


7-15: Well-defined interfaces for props and state.

The interfaces clearly define the component's contract, with good TypeScript typing.


17-33: Correct implementation of error boundary lifecycle methods.

The component properly implements getDerivedStateFromError and componentDidCatch with appropriate error handling and state management.


35-57: Well-designed fallback UI with recovery options.

The fallback UI provides clear error information and gives users multiple recovery options (try again or reload page).

frontend/src/app/features/health-impact/page.tsx (3)

1-8: Good organization of imports.

Imports are logically grouped (React/Next.js components, icons, custom components).


40-51: Good image error handling implementation.

The onError handler provides a fallback for missing images, enhancing user experience.


152-156: Dynamic year in copyright notice.

Good practice to automatically update the year in the copyright notice using new Date().getFullYear().

frontend/src/app/admin/media/upload/page.tsx (1)

7-7: LGTM! Button component import is correct

The import for the Button component uses the correct path.

frontend/src/app/admin/media/page.tsx (1)

240-250: LGTM! Clean header implementation with proper navigation

The header section with title and upload button is well implemented with clear navigation to the upload page.

frontend/src/app/admin/features/new/page.tsx (2)

70-76: Great input change handler implementation.
Straightforward approach for updating state. Consider also handling other input types (checkboxes, radios) by modifying the function if needed.


229-239: Clear and user-friendly saving state.
The spinner and “Saving...” text enhance the user experience by indicating progress.

frontend/src/app/features/site-location/page.tsx (2)

47-58: Fallback image approach is well-handled.
Swapping to a placeholder on error prevents broken images from affecting layout.


61-117: Clear explanation of AI technology and data sources.
The narrative is compelling and sufficiently detailed, highlighting the system’s complexity without overwhelming the user.

frontend/src/hooks/use-toast.ts (1)

61-75: Check for potential concurrency issues in addToRemoveQueue.
Multiple calls for the same toast ID can lead to confusion or stale timeouts. Review the logic to confirm correctness in edge cases.

Would you like a verification script to search for repeated triggers on the same toast ID?

frontend/src/app/admin/ai-technologies/new/page.tsx (2)

1-2: Good practice using the use client directive.
This ensures the component is rendered on the client side, which is appropriate for interactive UI.


128-133: Status toggle implementation looks correct.
Switching between published and draft states is straightforward and clearly handled.

frontend/src/app/admin/features/page.tsx (4)

1-2: use client usage.
This file includes interactive elements like searching and sorting, so client-side rendering is appropriate.


12-20: Interface definitions look clean.
The defined Feature interface helps maintain type consistency across the file.


29-100: Mock data fetching approach is understandable.
You could replace the static array with real API calls later. Logging errors is also a good start.


131-228: Table and sorting implementation.
The sorting logic is robust and easy to follow. Kudos for the search filter too.

frontend/src/app/admin/features/[action]/[id]/page.tsx (3)

72-107: Default feature state is well-structured.
Defining defaults in a single object clarifies the shape of your data.


109-194: Use of useEffect for edit vs. new route.
Nicely distinguishes between creating and editing. Error logging is present for debugging.


316-355: Preview feature for edit mode.
Providing a preview link is a handy user experience. This feature is well-implemented.

frontend/src/app/admin/page.tsx (9)

1-10: Clean imports and client directive.

Good job using the client directive at the top of the file and organizing your imports logically. The imports are well structured with React core imports first, followed by Next.js and UI components.


11-16: Well-structured interface definition.

The DashboardStats interface is cleanly defined with appropriate types for each property.


19-25: Good state initialization.

The initial state is properly typed with useState<DashboardStats> and initialized with appropriate default values.


48-54: Simple and effective loading state.

The loading state is well implemented with a clear visual indicator.


56-66: Well-structured dashboard header.

The header provides good navigation and context with a clean layout. The "View Site" button with the Eye icon is a nice touch for easy navigation.


95-153: Well-organized dashboard action cards.

The quick actions and system information cards are well structured with good visual hierarchy and clear call-to-actions.


158-164: Well-defined props interface.

The DashboardCardProps interface is cleanly defined with appropriate types and optional properties where needed.


166-184: Reusable card component with conditional rendering.

The DashboardCard component is well implemented with good conditional rendering based on whether the value is provided or not.


27-46:

Details

❓ Verification inconclusive

Replace simulated API call with real implementation.

The current implementation uses simulated data with a comment indicating it should be replaced with a real API call in the future.

This simulated data fetch should be replaced with an actual API call when the endpoint is available. Consider creating a custom hook for data fetching to separate concerns.


🏁 Script executed:

#!/bin/bash
# Check if there's a real API endpoint for dashboard stats
fd "api.*admin.*stats" --type f

Length of output: 33


Action Required: Replace simulated API call with a real one once the endpoint is available

After searching for files matching an admin stats API endpoint, no evidence of such an endpoint was found in the repository. The simulated data implementation in frontend/src/app/admin/page.tsx remains valid for now. Once the proper API endpoint is in place, please update this section to perform a real API call—ideally by extracting the logic into a custom hook for data fetching.

  • File: frontend/src/app/admin/page.tsx
  • Location: Lines 27-46
  • Suggestion: Replace the simulated data with a real API call and consider encapsulating the call in a custom hook.

Please manually verify this plan once the new endpoint is implemented.

frontend/src/app/admin/settings/page.tsx (3)

1-14: Clean imports with client directive.

Good use of the client directive and well-organized imports.


16-39: Well-structured interface definition.

The SiteSettings interface is well-defined with proper nesting for different categories of settings, making the structure clear and maintainable.


73-102:

Details

❓ Verification inconclusive

Replace simulated API call with real implementation.

Similar to the dashboard page, this uses a simulated API call.

Replace with a real API call when the endpoint is available. Consider creating a dedicated hook for fetching settings.


🏁 Script executed:

#!/bin/bash
# Check if there's a real API endpoint for settings
fd "api.*admin.*settings" --type f

Length of output: 36


Action Required: Verify API Endpoint Implementation

It appears that our automated search didn't locate any file matching the pattern for a real API endpoint for settings. Please manually verify whether a real API endpoint exists for fetching settings (or if it's implemented under a different pattern/path). Until confirmed, the simulated API call should remain in place. Once the proper endpoint is available, replace the simulated API call with a real implementation—ideally encapsulated in a dedicated hook, similar to what’s done on the dashboard page.

  • File: frontend/src/app/admin/settings/page.tsx (Lines 73-102)
  • Action:
    • Manually verify if a dedicated API endpoint exists for settings.
    • If it exists, update the call to fetch settings via a real API request.
    • Consider refactoring the API logic into a custom hook for better consistency and reuse.
frontend/src/app/admin/ai-technologies/page.tsx (4)

1-10: Clean imports with client directive.

Good organization of imports with client directive at the top.


12-20: Well-defined interface for AI Technology.

The interface is clear and properly typed with appropriate properties.


23-28: Good state initialization.

The component's state is properly initialized with appropriate types and default values.


93-112: Well-implemented sorting functionality.

The sorting and filtering logic is well implemented, providing a good user experience.

frontend/src/app/admin/login/page.tsx (1)

1-13: Clean imports with client directive.

Good organization of imports with client directive at the top.

frontend/src/components/map/MapComponent.tsx (5)

14-18: Good addition of global type declaration for window.map.

This TypeScript declaration properly types the global window.map property with optional chaining, which is important for type safety when accessing this property throughout the component.


75-76: Approval for map layer removal logic.

The layer removal logic is concise and handles the removal of existing tile layers appropriately.


114-125: Well-implemented global map reference management.

The useEffect hook correctly sets the global map reference and includes a proper cleanup function to prevent memory leaks when the component unmounts.

🧰 Tools
🪛 Biome (1.9.4)

[error] 122-122: Avoid the delete operator which can impact performance.

Unsafe fix: Use an undefined assignment instead.

(lint/performance/noDelete)


139-158: Good implementation of drawing functionality with proper cleanup.

The DrawControl component correctly handles event listeners with proper cleanup in the useEffect return function, preventing memory leaks.


208-208: Simplified NavigationControls implementation.

The change to a self-closing NavigationControls component improves code readability.

frontend/src/app/page.tsx (4)

5-17: Good organization of imports with appropriate icons.

The imports are well-structured and include all the necessary icons for the enhanced UI components. The grouping of imports makes the code more readable.


66-380: Excellent addition of the AI Technologies section.

The new section provides valuable information about the platform's AI capabilities with clear organization. The design is consistent and the content is informative with:

  • Well-structured cards for each AI technology
  • Consistent styling and layout
  • Proper descriptions and feature lists
  • Clear call-to-action buttons

This significantly enhances the user experience by showcasing the platform's technical capabilities.


394-437: Good enhancement of feature cards with navigation links.

Wrapping the FeatureCard components in Link components improves navigation and user experience by making the cards clickable and directing users to specific feature pages.


509-509: Appropriate cursor style for interactive elements.

Adding the cursor-pointer class to the FeatureCard provides a visual cue to users that the cards are clickable, improving usability.

frontend/src/components/ui/toast.tsx (5)

1-26: Well-structured toast components with proper TypeScript typing.

The implementation of the Toast components using Radix UI primitives is clean and follows best practices. The ToastViewport component is properly typed with React.forwardRef and includes appropriate class name handling using the cn utility function.


27-41: Good implementation of toast variants using class-variance-authority.

The toastVariants implementation provides a flexible way to style toast notifications with different variants (default and destructive). The use of cva for handling class variants is a modern and maintainable approach.


43-56: Well-implemented Toast component with proper variant handling.

The Toast component correctly uses the toastVariants function and forwards refs appropriately. The component is well-typed and follows best practices for React components.


58-113: Consistent implementation of toast subcomponents.

All the toast subcomponents (ToastAction, ToastClose, ToastTitle, ToastDescription) follow a consistent pattern with proper ref forwarding, class name handling, and display name assignment. This ensures a cohesive UI and maintainable codebase.


115-129: Good export of types and components.

The type definitions and component exports are clearly organized, making it easy to import and use the toast components throughout the application.

frontend/src/components/ui/table.tsx (3)

5-17: Well-implemented Table component with overflow handling.

The Table component is properly structured with ref forwarding and wraps the table in a div with overflow handling, which is a good practice for responsive tables. The use of the cn utility function for class name merging is appropriate.


19-109: Consistent and well-structured table subcomponents.

All table subcomponents (TableHeader, TableBody, TableFooter, TableRow, TableHead, TableCell, TableCaption) follow a consistent implementation pattern with:

  • Proper TypeScript typing
  • React.forwardRef usage
  • Class name handling with the cn utility
  • Appropriate display name assignment

This creates a cohesive set of components that are easy to use and maintain. The CSS classes applied to each component provide a good default styling while allowing for customization.


111-120: Clear and organized component exports.

The exports are well-organized, making it easy to import and use the table components throughout the application.

frontend/package.json (3)

12-18: Good choice with Radix UI components for enhanced UI capabilities.

The addition of Radix UI components (dropdown-menu, label, select, switch, tabs) is a solid choice for building accessible UI components. These primitives provide excellent accessibility features out of the box and integrate well with React's component model.


21-21: Dependency updates look good.

The updates to axios, leaflet-geosearch, tailwind-merge, and tailwindcss are all minor version changes that should maintain compatibility while providing bug fixes and improvements. The addition of tailwindcss-animate will enhance animation capabilities.

Also applies to: 28-28, 38-39, 51-51


30-30:

Details

❓ Verification inconclusive

Verify the need for MongoDB client in frontend code.

Including the MongoDB driver directly in frontend code is unusual and potentially concerning from an architecture perspective. MongoDB client libraries are typically used on the backend to interact with databases.


🏁 Script executed:

#!/bin/bash
# Search for MongoDB usage in frontend code
rg "mongodb" --type ts --type tsx --type js --type jsx -A 5 -B 5

Length of output: 92


Could you please run this updated script? It adds a custom file type definition for TSX files to ensure we capture any MongoDB-related usage in the frontend code. Once we have the output, it will help us finalize whether including the MongoDB client in the frontend is actually necessary or if it might be a remnant from past changes.


🏁 Script executed:

#!/bin/bash
# Re-run search command for "mongodb" usage with proper support for TSX files.
rg --type-add 'tsx:*.tsx' "mongodb" -A 5 -B 5 frontend/

Length of output: 6132


Action: Confirm Exclusive Server-Side Use of the MongoDB Driver

Our recent verification confirms that the MongoDB driver is imported in the file frontend/src/lib/mongodb.ts, where connection settings are also configured using environment variables. This suggests that the driver is likely intended for server-side functionality (such as for Next.js API routes or server-side data fetching). Still, please verify that:

  • Server-only usage: The mongodb.ts file is exclusively used in server-side contexts (e.g., within API routes or functions like getServerSideProps) and is not inadvertently imported into client-side components.
  • Frontend bundle impact: If the file is accidentally included in client bundles, it may lead to increased bundle size and expose potentially sensitive logic.

Once you confirm that the MongoDB client is strictly used in a backend context, you can safely keep the dependency in frontend/package.json.

frontend/src/components/ui/select.tsx (4)

1-8: Clean component setup with appropriate client directive.

The "use client" directive and imports are properly organized. The component correctly imports React and Radix UI primitives and includes the utility function for class name management.


9-13: Good pattern for component composition.

Using Radix UI primitives directly for root components creates a clean composition pattern. This approach makes the component API intuitive and follows best practices.


15-33: Well-structured trigger component with proper ref forwarding.

The SelectTrigger component properly uses React.forwardRef and correctly applies the displayName. The className handling with the cn utility provides good flexibility for styling.


148-159: Well-organized export statement.

The export statement is clean and properly exports all components. The order is logical, making it easy for consumers to understand the available components.

frontend/src/lib/api.ts (4)

1-8: Good use of type-only imports.

Converting to import type for type imports is a good practice that can help with tree-shaking and clearly indicates what is being used for type checking versus runtime.


10-19: Simplified error message and return statement improves readability.

The refactored code maintains the same functionality with cleaner syntax. The error message is now more concise while still providing all necessary information.


21-86: Enhanced error handling with detailed error extraction.

The revised baseFetch function now properly handles API errors by attempting to extract detailed error messages from JSON responses. The try-catch structure improves error handling flow and the error logging includes the endpoint for better context.


88-98: Consistent error handling pattern across API functions.

All API functions now follow a consistent pattern for error handling and logging. The simplified return statements improve code readability while maintaining the same functionality.

Also applies to: 100-109, 111-121

frontend/src/components/map/LeafletMap.tsx (8)

1-2: Correct usage of "use client" directive.

Adding the "use client" directive is necessary for this component as it uses browser-specific features like Leaflet. This ensures proper functioning in Next.js's hybrid rendering environment.


16-22: Good simplification of image imports for better maintainability.

Changing from alias imports to direct paths for images improves code clarity and maintainability. The comment properly explains the rationale for the change.


27-31: Improved handling of imported icon sources.

The updated code better handles the icon sources by checking if they're strings or imported modules, which prevents potential runtime errors.


55-100: Consistent return object structure in getAirQualityInfo.

The function now returns objects with a consistent structure for all conditions, which makes the code more predictable and easier to maintain.


108-136: Simplified PopupContent component with better null handling.

The PopupContent component now handles null/undefined values more gracefully with the nullish coalescing operator and provides default values for images.


490-516: Excellent use of useMemo for optimizing marker creation.

The memoization of marker creation is a significant performance improvement that prevents unnecessary re-renders when the component updates but node data hasn't changed.


525-572: Well-structured marker creation with proper error handling.

The marker creation logic is now encapsulated in a try-catch block which prevents a single marker error from breaking the entire map. The code is also more consistently formatted.


171-171:

Details

✅ Verification successful

Check for potential null reference with the Invalid image path.

The Invalid || "/placeholder.svg" expression assumes Invalid is always defined. Since it's a constant defined at the top of the file, this should be fine, but it's worth double-checking.


🏁 Script executed:

#!/bin/bash
# Check if Invalid constant is properly defined and imported
grep -n "Invalid" frontend/src/components/map/LeafletMap.tsx

Length of output: 478


Review Verified: Constant Handling Is Sound

After verifying the code in frontend/src/components/map/LeafletMap.tsx, we can confirm that the Invalid constant is properly defined (line 23) and always holds the string "/images/Invalid.png". Therefore, the use of Invalid || "/placeholder.svg" at line 171 doesn’t risk a null reference. The fallback is redundant but harmless.

frontend/src/components/ui/dropdown-menu.tsx (11)

1-8: Good implementation of client-side component with proper imports

The component correctly uses the "use client" directive at the top, essential for client-side interactivity. The import structure is clean with appropriate dependencies from Radix UI and Lucide for icons. The utility function import from @/lib/utils is also well placed.


9-20: Clean primitive component re-exports

Good approach for creating aliased components from Radix UI primitives. This pattern maintains a clear separation between the primitive components and your styled wrappers, making the code more maintainable.


21-42: Well-structured SubTrigger component with proper ref forwarding

The DropdownMenuSubTrigger component is correctly implemented with React's forwardRef for proper ref handling. The addition of the optional inset prop provides good flexibility for layout adjustments. The ChevronRight icon inclusion provides clear visual indication of submenu capability.


43-58: Comprehensive styling for SubContent with proper animations

The DropdownMenuSubContent component includes detailed styling with appropriate z-index, animations, and positioning. The data attributes for state transitions (open/closed) and positioning (side) are properly implemented for a smooth user experience.


59-77: Portal implementation ensures proper positioning

The DropdownMenuContent component correctly uses a Portal to render outside the DOM hierarchy of its parent, preventing clipping issues. The overflow handling with overflow-y-auto and max-height constraint is a thoughtful addition for menus with many items.


96-118: Well-implemented CheckboxItem with visual indicators

The DropdownMenuCheckboxItem component correctly handles checked state and provides appropriate visual feedback with the Check icon. The absolute positioning of the indicator ensures proper alignment regardless of content.


120-140: Consistent RadioItem implementation matching design patterns

The DropdownMenuRadioItem follows the same pattern as the CheckboxItem, maintaining consistency in the API and visual design. The Circle icon provides appropriate visual differentiation from checkboxes.


142-158: Clean Label implementation with inset option

The DropdownMenuLabel component provides appropriate styling for section headers within the dropdown menu. The optional inset prop maintains consistency with other components in the library.


160-170: Minimalist but effective Separator component

The DropdownMenuSeparator is appropriately styled with subtle coloring and proper spacing. This simple component effectively helps in visually organizing menu sections.


172-183: Custom Shortcut component enhances usability

The DropdownMenuShortcut component is a nice addition for displaying keyboard shortcuts. The styling choices (smaller text, wider tracking, reduced opacity) are appropriate for secondary information.


185-201: Complete exports for all components

All components are properly exported, making the full dropdown menu functionality available to the rest of the application. The export list is well-organized and comprehensive.

frontend/src/app/categorize/page.tsx (8)

1-20: Clean import organization with proper dependencies

The imports are well-organized, with React imports first, followed by UI components, third-party libraries, and internal imports. The client directive at the top is essential for this interactive component.


37-42: Well-structured state management

The component uses multiple state hooks to manage different aspects of the application state, including sites data, loading state, selected site, and manual input. This separation of concerns is a good practice.


44-65: Effective API call with proper error handling

The fetchSiteCategory function correctly handles the loading state, makes the API call, and properly formats the response. The error handling with toast notifications provides good user feedback.


67-80: Optimized map click handler with duplicate prevention

The handleMapClick function includes a check to prevent duplicate requests for the same location, which is a good optimization. The state updates are correctly handled for both the sites array and the selected site.


146-151: Clean MapEvents implementation

The MapEvents component correctly uses the useMapEvents hook to register the click handler without rendering any DOM elements. This is a clean approach to attaching events to the map.


153-160: Effective CSV export implementation

The downloadCSV function makes good use of the Papa library to generate the CSV and the Blob API to initiate the download. This provides a clean way for users to export their data.


254-260: Improved loading indicator with better visual feedback

The loading overlay provides clear visual feedback to users during API calls. The styling changes improve the appearance and visibility of the loading state.


265-271: Good use of Suspense for component loading

The SiteCategory component properly uses Suspense to handle loading states, providing a better user experience during the initial render.

Comment on lines +6 to +7
const ADMIN_USERNAME = "admin"
const ADMIN_PASSWORD = "password123"
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

Security risk: Hardcoded credentials in source code.

While this is noted as a simplified example, hardcoded credentials pose a significant security risk if accidentally deployed to production.

Consider storing credentials in environment variables:

-const ADMIN_USERNAME = "admin"
-const ADMIN_PASSWORD = "password123"
+const ADMIN_USERNAME = process.env.ADMIN_USERNAME || "admin"
+const ADMIN_PASSWORD = process.env.ADMIN_PASSWORD || "password123"

You should also add appropriate validation to ensure credentials are not empty when in production mode.

📝 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 ADMIN_USERNAME = "admin"
const ADMIN_PASSWORD = "password123"
const ADMIN_USERNAME = process.env.ADMIN_USERNAME || "admin"
const ADMIN_PASSWORD = process.env.ADMIN_PASSWORD || "password123"

Comment on lines +9 to +33
export async function POST(request: Request) {
try {
const body = await request.json()
const { username, password } = body

if (username === ADMIN_USERNAME && password === ADMIN_PASSWORD) {
// Set a cookie to indicate the user is logged in
// In a real app, you would use a proper JWT or session token
cookies().set("admin_session", "authenticated", {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
sameSite: "strict",
maxAge: 60 * 60 * 24, // 1 day
path: "/",
})

return NextResponse.json({ success: true })
}

return NextResponse.json({ message: "Invalid username or password" }, { status: 401 })
} catch (error) {
console.error("Login error:", error)
return NextResponse.json({ message: "An error occurred during login" }, { status: 500 })
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Login implementation lacks security measures.

The current implementation is missing several important security features:

  • Rate limiting to prevent brute force attacks
  • CSRF protection
  • Proper authentication token generation (as mentioned in comments)

For immediate improvement, consider adding rate limiting:

+import { RateLimiter } from "some-rate-limiter-package"
+
+// Create a rate limiter (e.g., max 10 attempts per IP in 15 minutes)
+const loginRateLimiter = new RateLimiter({
+  windowMs: 15 * 60 * 1000,
+  max: 10
+})

export async function POST(request: Request) {
  try {
+    // Get client IP address (implementation depends on your hosting)
+    const ip = request.headers.get("x-forwarded-for") || "unknown"
+    
+    // Check rate limit
+    const rateLimitResult = await loginRateLimiter.check(ip)
+    if (!rateLimitResult.success) {
+      return NextResponse.json(
+        { message: "Too many login attempts. Please try again later." },
+        { status: 429 }
+      )
+    }
+    
    const body = await request.json()

Additionally, in a production environment, consider using a proper authentication library like NextAuth.js or Auth.js.

📝 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 async function POST(request: Request) {
try {
const body = await request.json()
const { username, password } = body
if (username === ADMIN_USERNAME && password === ADMIN_PASSWORD) {
// Set a cookie to indicate the user is logged in
// In a real app, you would use a proper JWT or session token
cookies().set("admin_session", "authenticated", {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
sameSite: "strict",
maxAge: 60 * 60 * 24, // 1 day
path: "/",
})
return NextResponse.json({ success: true })
}
return NextResponse.json({ message: "Invalid username or password" }, { status: 401 })
} catch (error) {
console.error("Login error:", error)
return NextResponse.json({ message: "An error occurred during login" }, { status: 500 })
}
}
import { RateLimiter } from "some-rate-limiter-package"
// Create a rate limiter (e.g., max 10 attempts per IP in 15 minutes)
const loginRateLimiter = new RateLimiter({
windowMs: 15 * 60 * 1000,
max: 10
})
export async function POST(request: Request) {
try {
// Get client IP address (implementation depends on your hosting)
const ip = request.headers.get("x-forwarded-for") || "unknown"
// Check rate limit
const rateLimitResult = await loginRateLimiter.check(ip)
if (!rateLimitResult.success) {
return NextResponse.json(
{ message: "Too many login attempts. Please try again later." },
{ status: 429 }
)
}
const body = await request.json()
const { username, password } = body
if (username === ADMIN_USERNAME && password === ADMIN_PASSWORD) {
// Set a cookie to indicate the user is logged in
// In a real app, you would use a proper JWT or session token
cookies().set("admin_session", "authenticated", {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
sameSite: "strict",
maxAge: 60 * 60 * 24, // 1 day
path: "/",
})
return NextResponse.json({ success: true })
}
return NextResponse.json({ message: "Invalid username or password" }, { status: 401 })
} catch (error) {
console.error("Login error:", error)
return NextResponse.json({ message: "An error occurred during login" }, { status: 500 })
}
}

Comment on lines +4 to +17
export async function GET() {
try {
const session = cookies().get("admin_session")

if (session && session.value === "authenticated") {
return NextResponse.json({ authenticated: true })
}

return NextResponse.json({ authenticated: false, message: "Not authenticated" }, { status: 401 })
} catch (error) {
console.error("Auth check error:", error)
return NextResponse.json({ message: "An error occurred checking authentication" }, { status: 500 })
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider enhancing authentication security.

While the implementation works for basic authentication checks, the current approach has security limitations.

For a more robust authentication system:

  1. Use signed/encrypted cookies or JWTs instead of plain text values
  2. Add expiration times to sessions
  3. Implement CSRF protection
  4. Set secure cookie attributes:
- if (session && session.value === "authenticated") {
+ // Verify session token with proper validation
+ // Example with a hypothetical validateSession function:
+ if (session && await validateSession(session.value)) {

Also, when setting the cookie elsewhere, consider:

cookies().set({
  name: "admin_session",
  value: "generated_secure_token", // Not hardcoded
  httpOnly: true,
  secure: process.env.NODE_ENV === "production",
  sameSite: "strict",
  maxAge: 3600 // 1 hour in seconds
})

Comment thread frontend/src/lib/mongodb.ts Outdated
Comment thread frontend/src/lib/mongodb.ts Outdated
Comment on lines +29 to +91
useEffect(() => {
const fetchTechnologies = async () => {
try {
// In a real implementation, this would fetch from an API
// For now, we'll simulate with static data
const mockTechnologies: AITechnology[] = [
{
id: "1",
title: "Forecasting AI",
slug: "forecasting-ai",
description: "Predicts air quality conditions up to 7 days in advance with high accuracy.",
icon: "LineChart",
status: "published",
updatedAt: "2023-05-15T10:30:00Z",
},
{
id: "2",
title: "Calibration AI",
slug: "calibration-ai",
description: "Transforms data from low-cost sensors into reference-grade measurements.",
icon: "Sliders",
status: "published",
updatedAt: "2023-05-20T14:45:00Z",
},
{
id: "3",
title: "Location AI",
slug: "location-ai",
description: "Optimizes the placement of air quality monitors to maximize coverage.",
icon: "MapPin",
status: "published",
updatedAt: "2023-05-18T09:15:00Z",
},
{
id: "4",
title: "Satellite PM2.5 AI",
slug: "satellite-pm25-ai",
description: "Predicts PM2.5 concentrations in areas without ground-based monitors.",
icon: "Satellite",
status: "published",
updatedAt: "2023-05-22T11:20:00Z",
},
{
id: "5",
title: "Source Prediction AI",
slug: "source-prediction-ai",
description: "Identifies and characterizes stationary pollution sources.",
icon: "Factory",
status: "published",
updatedAt: "2023-05-17T16:10:00Z",
},
]

setTechnologies(mockTechnologies)
} catch (error) {
console.error("Failed to fetch AI technologies:", error)
} finally {
setIsLoading(false)
}
}

fetchTechnologies()
}, [])
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Replace mock data with real API integration.

The current implementation uses hard-coded mock data within the component.

Extract the mock data to a separate file and prepare for real API integration:

-        const mockTechnologies: AITechnology[] = [
-          {
-            id: "1",
-            title: "Forecasting AI",
-            slug: "forecasting-ai",
-            description: "Predicts air quality conditions up to 7 days in advance with high accuracy.",
-            icon: "LineChart",
-            status: "published",
-            updatedAt: "2023-05-15T10:30:00Z",
-          },
-          // ... other mock items
-        ]
-
-        setTechnologies(mockTechnologies)
+        // In development, use mock data
+        if (process.env.NODE_ENV === 'development') {
+          const { mockTechnologies } = await import('@/lib/mock-data/ai-technologies');
+          setTechnologies(mockTechnologies);
+          return;
+        }
+        
+        // In production, fetch from API
+        const response = await fetch('/api/admin/ai-technologies');
+        if (!response.ok) {
+          throw new Error('Failed to fetch technologies');
+        }
+        const data = await response.json();
+        setTechnologies(data);

Also check if a real API endpoint exists:

#!/bin/bash
# Check if there's a real API endpoint for AI technologies
fd "api.*admin.*technologies" --type f

Comment on lines +192 to +202
<div className="flex justify-end gap-2">
<Link href={`/admin/ai-technologies/edit/${tech.id}`}>
<Button variant="outline" size="icon">
<Edit className="h-4 w-4" />
</Button>
</Link>
<Button variant="outline" size="icon" className="text-red-500">
<Trash2 className="h-4 w-4" />
</Button>
</div>
</TableCell>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Implement confirmation dialog for delete action.

The delete button action is not implemented and lacks a confirmation step.

Implement a confirmation dialog before executing the delete action:

-<Button variant="outline" size="icon" className="text-red-500">
+<Button 
+  variant="outline" 
+  size="icon" 
+  className="text-red-500"
+  onClick={() => {
+    if (window.confirm(`Are you sure you want to delete "${tech.title}"?`)) {
+      // TODO: Implement delete functionality
+      console.log(`Deleting technology with ID: ${tech.id}`);
+    }
+  }}
+>
  <Trash2 className="h-4 w-4" />
</Button>

For a more sophisticated solution, consider using a modal dialog component instead of the native confirm.

📝 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
<div className="flex justify-end gap-2">
<Link href={`/admin/ai-technologies/edit/${tech.id}`}>
<Button variant="outline" size="icon">
<Edit className="h-4 w-4" />
</Button>
</Link>
<Button variant="outline" size="icon" className="text-red-500">
<Trash2 className="h-4 w-4" />
</Button>
</div>
</TableCell>
<div className="flex justify-end gap-2">
<Link href={`/admin/ai-technologies/edit/${tech.id}`}>
<Button variant="outline" size="icon">
<Edit className="h-4 w-4" />
</Button>
</Link>
<Button
variant="outline"
size="icon"
className="text-red-500"
onClick={() => {
if (window.confirm(`Are you sure you want to delete "${tech.title}"?`)) {
// TODO: Implement delete functionality
console.log(`Deleting technology with ID: ${tech.id}`);
}
}}
>
<Trash2 className="h-4 w-4" />
</Button>
</div>
</TableCell>

Comment thread frontend/src/app/admin/login/page.tsx Outdated
Comment on lines +129 to +134
<div className="w-full">
<p className="mb-2">Protected area. Unauthorized access is prohibited.</p>
<p className="text-xs text-blue-600">
For demo purposes, use: <strong>admin</strong> / <strong>password123</strong>
</p>
</div>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

Remove hardcoded demo credentials from production code.

Hardcoded credentials should not be included in production code, even as demo credentials.

Remove the hardcoded credentials from the UI and use environment variables or a secure configuration approach for demo credentials:

<div className="w-full">
  <p className="mb-2">Protected area. Unauthorized access is prohibited.</p>
-  <p className="text-xs text-blue-600">
-    For demo purposes, use: <strong>admin</strong> / <strong>password123</strong>
-  </p>
+  {process.env.NODE_ENV === 'development' && (
+    <p className="text-xs text-blue-600">
+      For demo purposes, use: <strong>admin</strong> / <strong>password123</strong>
+    </p>
+  )}
</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
<div className="w-full">
<p className="mb-2">Protected area. Unauthorized access is prohibited.</p>
<p className="text-xs text-blue-600">
For demo purposes, use: <strong>admin</strong> / <strong>password123</strong>
</p>
</div>
<div className="w-full">
<p className="mb-2">Protected area. Unauthorized access is prohibited.</p>
{process.env.NODE_ENV === 'development' && (
<p className="text-xs text-blue-600">
For demo purposes, use: <strong>admin</strong> / <strong>password123</strong>
</p>
)}
</div>

Comment thread frontend/src/components/map/MapComponent.tsx Outdated
Copy link
Copy Markdown

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

♻️ Duplicate comments (1)
frontend/src/app/admin/login/page.tsx (1)

130-139: ⚠️ Potential issue

Remove hardcoded demo credentials from production code.

Hardcoded credentials should not be included in production code, even as demo credentials.

Apply this diff to display credentials only in development mode:

<div className="w-full">
  <p className="mb-2">Protected area. Unauthorized access is prohibited.</p>
-  <p className="text-xs text-blue-600">
-    For demo purposes, use:
-    <br />
-    <strong>admin</strong> / <strong>password123</strong> (Admin)
-    <br />
-    <strong>editor</strong> / <strong>editor123</strong> (Editor)
-    <br />
-    <strong>viewer</strong> / <strong>viewer123</strong> (Viewer)
-  </p>
+  {process.env.NODE_ENV === 'development' && (
+    <p className="text-xs text-blue-600">
+      For demo purposes, use:
+      <br />
+      <strong>admin</strong> / <strong>password123</strong> (Admin)
+      <br />
+      <strong>editor</strong> / <strong>editor123</strong> (Editor)
+      <br />
+      <strong>viewer</strong> / <strong>viewer123</strong> (Viewer)
+    </p>
+  )}
</div>
🧹 Nitpick comments (37)
frontend/src/components/map/MapComponent.tsx (1)

21-21: Consider alternative to delete operator for performance

The delete operator can impact performance as noted by static analysis. While this is a standard fix for Leaflet's default markers, consider an alternative approach.

- delete (L.Icon.Default.prototype as any)._getIconUrl
+ (L.Icon.Default.prototype as any)._getIconUrl = undefined

This sets the property to undefined rather than removing it, which can be more performant in JavaScript engines.

🧰 Tools
🪛 Biome (1.9.4)

[error] 20-21: Avoid the delete operator which can impact performance.

Unsafe fix: Use an undefined assignment instead.

(lint/performance/noDelete)

frontend/src/app/admin/features/[action]/[id]/page.tsx (4)

67-121: Consider breaking down this large component into smaller subcomponents

The FeatureForm component is quite large (almost 700 lines) which can make it difficult to maintain. Consider extracting each tab content into its own component to improve readability and maintainability.

For example, you could create components like:

  • GeneralTab.tsx
  • ContentTab.tsx
  • MediaTab.tsx
  • SeoTab.tsx

This would make the main component more focused on orchestration rather than implementation details.


310-356: Enhance error handling in form submission

The current error handling in handleSubmit catches all errors but provides a generic message to the user. Consider implementing more specific error handling based on the error type.

 try {
   // In a real implementation, this would save to an API
   console.log("Saving feature:", feature)

   // Simulate API call
   await new Promise((resolve) => setTimeout(resolve, 1000))

   router.push("/admin/features")
 } catch (error) {
   console.error("Failed to save feature:", error)
-  setError("Failed to save feature. Please try again.")
+  if (error instanceof Response && error.status === 403) {
+    setError("You don't have permission to save this feature.")
+  } else if (error instanceof Response && error.status === 409) {
+    setError("A feature with this slug already exists. Please choose a different slug.")
+  } else {
+    setError("Failed to save feature. Please try again.")
+  }
 }

358-370: Improve slug generation for better internationalization support

The current slug generation only handles basic Latin characters. Consider enhancing it to properly handle international characters for a more inclusive user experience.

 const generateSlug = () => {
   if (!canEdit) return

-  const slug = feature.title
-    .toLowerCase()
-    .replace(/[^\w\s]/gi, "")
-    .replace(/\s+/g, "-")
+  // Use a more robust slugify approach
+  const slug = feature.title
+    .toLowerCase()
+    .normalize('NFD')                 // Normalize diacritical marks
+    .replace(/[\u0300-\u036f]/g, '')  // Remove diacritics
+    .replace(/[^\w\s-]/g, '')         // Remove non-word chars
+    .trim()                           // Remove whitespace from ends
+    .replace(/\s+/g, '-')             // Replace spaces with hyphens
+    .replace(/-+/g, '-')              // Remove consecutive hyphens

   setFeature((prev) => ({
     ...prev,
     slug,
   }))
 }

731-756: Add validation for SEO field lengths

You have helpful text suggesting optimal lengths for SEO fields, but there's no validation or visual feedback when users exceed these limits.

Add visual indicators when SEO fields exceed recommended lengths:

 <div className="space-y-2">
   <Label htmlFor="seoTitle">SEO Title</Label>
   <Input
     id="seoTitle"
     value={feature.seo.title}
     onChange={(e) => handleSeoChange("title", e.target.value)}
     placeholder="SEO title (appears in browser tab)"
     readOnly={!canEdit}
-    className={!canEdit ? "bg-gray-100" : ""}
+    className={`${!canEdit ? "bg-gray-100" : ""} ${
+      feature.seo.title.length > 60 ? "border-red-500" : ""
+    }`}
   />
-  <p className="text-xs text-gray-500">Recommended length: 50-60 characters</p>
+  <p className={`text-xs ${
+    feature.seo.title.length > 60 ? "text-red-500" : "text-gray-500"
+  }`}>
+    Recommended length: 50-60 characters | 
+    Current: ${feature.seo.title.length}/60
+  </p>
 </div>

Similar changes should be applied to the meta description field.

frontend/src/components/admin/media-upload.tsx (3)

26-71: Add file size validation to match documented limitations.

While the component validates file types correctly, it doesn't validate the file size despite mentioning a 5MB limit in the UI text. This could lead to unexpected behavior if users attempt to upload large files.

 // Validate file type
 if (!file.type.startsWith("image/")) {
   setUploadError("Please select an image file")
   setIsUploading(false)
   return
 }

+// Validate file size (5MB limit)
+const maxSize = 5 * 1024 * 1024; // 5MB in bytes
+if (file.size > maxSize) {
+  setUploadError("File size exceeds the 5MB limit")
+  setIsUploading(false)
+  return
+}

73-82: Replace simulation with actual implementation.

The current code uses a simulated upload function with setTimeout. While this works for demonstration purposes, it should be replaced with an actual implementation in production.

Would you like help implementing a real upload function that connects to your backend API or a service like Cloudinary?


98-147: Enhance image card accessibility.

The image preview card is visually well-structured but could benefit from improved accessibility.

<div
  className={`relative aspect-video flex items-center justify-center bg-gray-100 border-2 border-dashed rounded-md ${
    disabled ? "opacity-70" : "cursor-pointer"
  }`}
  onClick={handleBrowseClick}
+ role="button"
+ tabIndex={disabled ? -1 : 0}
+ aria-label="Select image"
+ onKeyDown={(e) => {
+   if (e.key === 'Enter' || e.key === ' ') {
+     e.preventDefault();
+     handleBrowseClick();
+   }
+ }}
>
frontend/src/app/api/admin/auth/login/route.ts (1)

47-54: Consider enhancing cookie security.

The current cookie implementation is good but could be further improved by adding SameSite protection and potentially signing the cookie contents.

cookieStore.set("admin_session", userSession, {
  httpOnly: true,
  secure: process.env.NODE_ENV === "production",
  sameSite: "strict",
  maxAge: 60 * 60 * 24, // 1 day
  path: "/",
+ // Consider adding these for enhanced security
+ // partitioned: true, // Adds Partitioned attribute when cross-site context
+ // priority: "high", // Minimizes cookie eviction risks
})
frontend/src/app/api/admin/auth/check/route.ts (2)

9-24: Optimize conditional checks with optional chaining.

The static analysis tool correctly identifies opportunities to use optional chaining for better code readability.

-if (sessionCookie && sessionCookie.value) {
+if (sessionCookie?.value) {
  try {
    // Parse the JSON session data
    const userData = JSON.parse(sessionCookie.value)

    // Check if the user is active
-    if (userData && userData.isActive) {
+    if (userData?.isActive) {
      return NextResponse.json({
        authenticated: true,
        user: userData,
      })
    }
  } catch (error) {
    console.error("Error parsing session cookie:", error)
  }
}
🧰 Tools
🪛 Biome (1.9.4)

[error] 9-9: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


[error] 15-15: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


21-24: Improve error handling for JSON parsing.

The current implementation logs JSON parsing errors but doesn't provide feedback to the user. Consider including error details in the response.

try {
  // Parse the JSON session data
  const userData = JSON.parse(sessionCookie.value)

  // Check if the user is active
  if (userData && userData.isActive) {
    return NextResponse.json({
      authenticated: true,
      user: userData,
    })
  }
} catch (error) {
  console.error("Error parsing session cookie:", error)
+ return NextResponse.json(
+   {
+     authenticated: false,
+     message: "Invalid session format",
+   },
+   { status: 401 }
+ )
}
frontend/src/app/admin/layout.tsx (1)

36-69: Add error handling feedback for authentication failures.

The authentication check handles errors by redirecting to the login page, but it doesn't inform the user about what went wrong. Consider adding toast notifications for better UX.

} catch (error) {
  console.error("Auth check failed:", error)
+ toast({
+   title: "Authentication Error",
+   description: "Failed to verify your login status. Please sign in again.",
+   variant: "destructive",
+ })
  router.push("/admin/login")
}
frontend/src/app/admin/users/edit/[id]/page.tsx (6)

17-23: Consider aligning User interface with the one in lib/auth.ts.

The User interface is defined locally, but there's already a similar interface in lib/auth.ts. Consider importing and reusing that interface to maintain consistency across the application.

-interface User {
-  id: string
-  username: string
-  email: string
-  role: "admin" | "editor" | "viewer"
-  isActive: boolean
-}
+import { User } from "@/lib/auth"

31-31: Fix typo in state variable name.

The variable name isResetingPassword has a typo - should be isResettingPassword (with double 't').

-const [isResetingPassword, setIsResetingPassword] = useState(false)
+const [isResettingPassword, setIsResettingPassword] = useState(false)

132-139: Enhance email validation.

The current email validation regex is simplistic. Consider using a more robust email validation regex or a validation library for better accuracy.

-if (!user.email.trim() || !/\S+@\S+\.\S+/.test(user.email)) {
+if (!user.email.trim() || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(user.email)) {

280-281: Update onClick handler name to match state variable.

If you update the state variable name from isResetingPassword to isResettingPassword, make sure to update the onClick handler as well.

-onClick={() => setIsResetingPassword(!isResetingPassword)}
+onClick={() => setIsResettingPassword(!isResettingPassword)}

288-288: Update conditional rendering to match renamed state variable.

If you update the state variable name, make sure to update this conditional rendering as well.

-{isResetingPassword && (
+{isResettingPassword && (

346-346: Replace hardcoded date with dynamic value or placeholder.

The "Last login" date is hardcoded. Consider retrieving this from the user data or API, or using a placeholder if the data isn't available yet.

-<p className="text-sm text-gray-500">Last login: {user.isActive ? "2023-07-15" : "Never"}</p>
+<p className="text-sm text-gray-500">Last login: {user.lastLoginDate ? new Date(user.lastLoginDate).toLocaleDateString() : (user.isActive ? "Unknown" : "Never")}</p>
frontend/src/app/admin/users/new/page.tsx (1)

35-37: Add type safety to the role value.

The handleRoleChange function doesn't restrict the role value to the expected types, unlike in the edit user component. Add type safety to ensure only valid roles are set.

-const handleRoleChange = (value: string) => {
-  setUser((prev) => ({ ...prev, role: value }))
+const handleRoleChange = (value: string) => {
+  setUser((prev) => ({ ...prev, role: value as "admin" | "editor" | "viewer" }))
frontend/src/lib/auth.ts (3)

11-40: Use exhaustive checking in the switch statement.

The switch statement in canAccessFeature doesn't handle all possible cases exhaustively. Consider adding a default case or using TypeScript's exhaustive checking pattern with a never type.

if (user.role === "editor") {
  switch (feature) {
    case "features":
    case "ai-technologies":
    case "media":
      return true
    case "users":
    case "settings":
      return false
+   default:
+     const exhaustiveCheck: never = feature;
+     return false;
  }
}

11-14: Extract feature types to a reusable type.

The feature parameter is defined inline with a union type. Extract this to a named type for better reusability and documentation.

+export type FeatureType = "users" | "features" | "ai-technologies" | "media" | "settings";

export function canAccessFeature(
  user: User | null,
-  feature: "users" | "features" | "ai-technologies" | "media" | "settings",
+  feature: FeatureType,
): boolean {

42-60: Reduce duplication in permission check functions.

All the permission check functions have the same initial check for user existence and active status. Consider extracting this to a helper function.

+function isActiveUser(user: User | null): boolean {
+  return !!user && user.isActive;
+}

export function canEditContent(user: User | null): boolean {
-  if (!user || !user.isActive) return false;
+  if (!isActiveUser(user)) return false;
  return user.role === "admin" || user.role === "editor";
}

export function canDeleteContent(user: User | null): boolean {
-  if (!user || !user.isActive) return false;
+  if (!isActiveUser(user)) return false;
  return user.role === "admin";
}

export function canManageUsers(user: User | null): boolean {
-  if (!user || !user.isActive) return false;
+  if (!isActiveUser(user)) return false;
  return user.role === "admin";
}

export function canManageSettings(user: User | null): boolean {
-  if (!user || !user.isActive) return false;
+  if (!isActiveUser(user)) return false;
  return user.role === "admin";
}
frontend/src/app/admin/login/page.tsx (2)

84-126: Consider adding "Forgot Password" functionality.

The login form doesn't include a "Forgot Password" option, which is a common feature for admin interfaces. Consider adding this functionality for better user experience.

<form onSubmit={handleSubmit} className="space-y-4">
  {/* ... existing code ... */}
  <Button type="submit" className="w-full" disabled={isLoading}>
    {/* ... existing code ... */}
  </Button>
+  <div className="flex justify-end">
+    <Button variant="link" className="p-0 h-auto text-sm" onClick={() => router.push('/admin/forgot-password')}>
+      Forgot password?
+    </Button>
+  </div>
</form>

91-112: Add client-side validation before form submission.

The form currently validates on the server side only. Add client-side validation for a better user experience by providing immediate feedback.

<div className="space-y-2">
  <Label htmlFor="username">Username</Label>
  <Input
    id="username"
    type="text"
    value={username}
    onChange={(e) => setUsername(e.target.value)}
    required
+   minLength={3}
    placeholder="Enter your username"
  />
+  {username.length > 0 && username.length < 3 && (
+    <p className="text-sm text-red-500 mt-1">Username must be at least 3 characters</p>
+  )}
</div>
<div className="space-y-2">
  <Label htmlFor="password">Password</Label>
  <Input
    id="password"
    type="password"
    value={password}
    onChange={(e) => setPassword(e.target.value)}
    required
+   minLength={8}
    placeholder="Enter your password"
  />
+  {password.length > 0 && password.length < 8 && (
+    <p className="text-sm text-red-500 mt-1">Password must be at least 8 characters</p>
+  )}
</div>
frontend/src/app/admin/media/page.tsx (6)

46-92: Mock data should be moved to a separate file or utility.

The mock data is currently embedded directly in the component, which could make maintenance more difficult as the component grows. Consider extracting this mock data to a separate file or utility function.

- // In a real implementation, this would fetch from an API
- // For now, we'll simulate with static data
- const mockMedia: MediaItem[] = [
-   {
-     id: "1",
-     name: "site-location.jpg",
-     url: "/placeholder.svg?height=400&width=800&text=Site+Location",
-     type: "image/jpeg",
-     size: 245000,
-     uploadedAt: "2023-07-15T10:30:00Z",
-     dimensions: {
-       width: 1200,
-       height: 800,
-     },
-   },
-   // ... other mock items
- ]
- 
- setMediaItems(mockMedia)
+ // Import mock data from a separate file
+ import { getMockMediaItems } from "@/lib/mock-data";
+ 
+ // In a real implementation, this would fetch from an API
+ // For now, we'll use mock data
+ const mockMedia = getMockMediaItems();
+ setMediaItems(mockMedia);

117-124: Improve document type detection logic.

The current document type detection relies on string inclusion checks which might lead to false positives. Consider using a more reliable approach with file extensions or MIME type matching.

if (activeTab === "documents")
  return (
    matchesSearch &&
-   (item.type.includes("pdf") ||
-     item.type.includes("doc") ||
-     item.type.includes("xls") ||
-     item.type.includes("ppt"))
+   (/^application\/(pdf|msword|vnd\.openxmlformats-officedocument|vnd\.ms-excel|vnd\.ms-powerpoint)/.test(item.type))
  )

139-141: Replace native confirm with a custom dialog component.

Using the native browser confirm() method provides a limited user experience and can be blocked by some browsers. Consider using a custom dialog component for a more consistent and user-friendly experience.

- if (!confirm("Are you sure you want to delete this media item?")) {
-   return
- }
+ // Import a dialog component
+ import { useDialog } from "@/hooks/use-dialog";
+ 
+ // At the top of your component
+ const { showConfirmDialog } = useDialog();
+ 
+ // Replace with:
+ const confirmed = await showConfirmDialog({
+   title: "Confirm Deletion",
+   message: "Are you sure you want to delete this media item?",
+   confirmText: "Delete",
+   cancelText: "Cancel"
+ });
+ 
+ if (!confirmed) {
+   return;
+ }

313-315: Use semicolon consistently with TypeScript's automatic semicolon insertion.

There's an unnecessary semicolon before a method call which breaks the coding style consistency. Although it doesn't affect functionality, it's better to maintain consistent styling.

onError={(e) => {
-  ;(e.target as HTMLImageElement).src =
+  (e.target as HTMLImageElement).src =
    "/placeholder.svg?height=200&width=300&text=Preview+Not+Available"
}}

484-485: Use semicolon consistently with TypeScript's automatic semicolon insertion.

Similar to the previous comment, there's an unnecessary semicolon before a method call in the error handler.

onError={(e) => {
-  ;(e.target as HTMLImageElement).src =
+  (e.target as HTMLImageElement).src =
    "/placeholder.svg?height=300&width=400&text=Preview+Not+Available"
}}

28-38: Consider implementing pagination for better performance with large datasets.

The current implementation loads all media items at once, which could lead to performance issues with large datasets. Consider implementing pagination or virtualization for better handling of large media libraries.

export default function MediaLibraryPage() {
  const [mediaItems, setMediaItems] = useState<MediaItem[]>([])
  const [isLoading, setIsLoading] = useState(true)
  const [searchQuery, setSearchQuery] = useState("")
  const [activeTab, setActiveTab] = useState("all")
  const [selectedItem, setSelectedItem] = useState<MediaItem | null>(null)
  const [isUploading, setIsUploading] = useState(false)
  const [newMediaUrl, setNewMediaUrl] = useState("")
+ const [page, setPage] = useState(1)
+ const [pageSize, setPageSize] = useState(12)
+ const [totalItems, setTotalItems] = useState(0)
  const { toast } = useToast()
  const { user } = useAuth()

Then modify your fetch logic to include pagination parameters and add pagination UI controls at the bottom of your component.

frontend/src/app/admin/users/page.tsx (4)

35-68: Move mock data to a separate utility file.

Similar to the media page, the mock user data should be extracted to a separate file for better maintainability.

- // In a real implementation, this would fetch from an API
- // For now, we'll simulate with static data
- const mockUsers: User[] = [
-   {
-     id: "1",
-     username: "admin",
-     email: "admin@example.com",
-     role: "admin",
-     status: "active",
-     lastLogin: "2023-07-15T10:30:00Z",
-   },
-   // ... other mock users
- ]
-
- setUsers(mockUsers)
+ // Import mock data from a separate file
+ import { getMockUsers } from "@/lib/mock-data";
+ 
+ // In a real implementation, this would fetch from an API
+ // For now, we'll use mock data
+ const mockUsers = getMockUsers();
+ setUsers(mockUsers);

95-98: Replace native confirm with a custom dialog component.

Similar to the media page, using the native browser confirm() method should be replaced with a custom dialog component.

- if (!confirm("Are you sure you want to delete this user?")) {
-   return
- }
+ // Import a dialog component
+ import { useDialog } from "@/hooks/use-dialog";
+ 
+ // At the top of your component
+ const { showConfirmDialog } = useDialog();
+ 
+ // Replace with:
+ const confirmed = await showConfirmDialog({
+   title: "Confirm User Deletion",
+   message: "Are you sure you want to delete this user?",
+   confirmText: "Delete",
+   cancelText: "Cancel"
+ });
+ 
+ if (!confirmed) {
+   return;
+ }

147-158: Consider using an enum or constants for role badge variants.

The role badge variant mapping is hardcoded with string literals. Consider using an enum or constants for better maintainability and type safety.

+ // Define constants for role badge variants
+ const ROLE_BADGE_VARIANTS = {
+   admin: "destructive",
+   editor: "default",
+   viewer: "secondary",
+   default: "outline"
+ } as const;
+ 
+ type RoleBadgeVariant = typeof ROLE_BADGE_VARIANTS[keyof typeof ROLE_BADGE_VARIANTS];

const getRoleBadgeVariant = (role: string) => {
-  switch (role) {
-    case "admin":
-      return "destructive"
-    case "editor":
-      return "default"
-    case "viewer":
-      return "secondary"
-    default:
-      return "outline"
-  }
+  return ROLE_BADGE_VARIANTS[role as keyof typeof ROLE_BADGE_VARIANTS] || ROLE_BADGE_VARIANTS.default;
}

270-274: Add pagination for scalability with large user datasets.

The current implementation loads all users at once without pagination. For better scalability and performance, consider implementing pagination.

      <CardFooter className="flex justify-between">
        <div className="text-sm text-gray-500">
          Showing {sortedUsers.length} of {users.length} users
        </div>
+       <div className="flex gap-2">
+         <Button 
+           variant="outline" 
+           size="sm" 
+           disabled={page === 1}
+           onClick={() => setPage(prev => Math.max(1, prev - 1))}
+         >
+           Previous
+         </Button>
+         <span className="px-3 py-1 border rounded text-sm flex items-center">
+           Page {page}
+         </span>
+         <Button 
+           variant="outline" 
+           size="sm" 
+           disabled={users.length <= page * pageSize}
+           onClick={() => setPage(prev => prev + 1)}
+         >
+           Next
+         </Button>
+       </div>
      </CardFooter>

You'll also need to add state variables for pagination:

export default function UsersPage() {
  const [users, setUsers] = useState<User[]>([])
  const [isLoading, setIsLoading] = useState(true)
  const [searchQuery, setSearchQuery] = useState("")
  const [sortField, setSortField] = useState<keyof User>("username")
  const [sortDirection, setSortDirection] = useState<"asc" | "desc">("asc")
+ const [page, setPage] = useState(1)
+ const [pageSize, setPageSize] = useState(10)

And modify how you paginate the displayed users:

- const sortedUsers = [...filteredUsers].sort((a, b) => {
+ // First sort the filtered users
+ const sortedUsers = [...filteredUsers].sort((a, b) => {
    // ... sorting logic remains the same
  })

+ // Then paginate the sorted users
+ const paginatedUsers = sortedUsers.slice((page - 1) * pageSize, page * pageSize)

Then use paginatedUsers instead of sortedUsers in your table mapping.

frontend/src/hooks/use-auth.tsx (3)

26-53: Consider implementing a token refresh mechanism.

The current authentication implementation doesn't handle token expiration or refreshing. Consider adding a token refresh mechanism to maintain user sessions.

const checkAuth = async (): Promise<boolean> => {
  try {
    const response = await fetch("/api/admin/auth/check")
    if (response.ok) {
      const data = await response.json()
      if (data.authenticated && data.user) {
        setUser(data.user)
        setIsAuthenticated(true)
+       // Set up token refresh if needed
+       if (data.expiresAt) {
+         setupTokenRefresh(data.expiresAt);
+       }
        return true
      } else {
        setUser(null)
        setIsAuthenticated(false)
        return false
      }
    } else {
+     // Handle specific error cases
+     if (response.status === 401) {
+       // Token expired, try to refresh
+       const refreshed = await refreshToken();
+       if (refreshed) {
+         return checkAuth();
+       }
+     }
      setUser(null)
      setIsAuthenticated(false)
      return false
    }
  } catch (error) {
    console.error("Auth check failed:", error)
    setUser(null)
    setIsAuthenticated(false)
    return false
  } finally {
    setIsLoading(false)
  }
}

+ // Add token refresh helper functions
+ const refreshToken = async (): Promise<boolean> => {
+   try {
+     const response = await fetch("/api/admin/auth/refresh", {
+       method: "POST",
+       credentials: "include",
+     });
+     
+     return response.ok;
+   } catch (error) {
+     console.error("Token refresh failed:", error);
+     return false;
+   }
+ }
+ 
+ const setupTokenRefresh = (expiresAt: string) => {
+   const expiryTime = new Date(expiresAt).getTime();
+   const currentTime = Date.now();
+   const timeUntilExpiry = expiryTime - currentTime;
+   
+   // Refresh 5 minutes before expiry
+   const refreshTime = Math.max(0, timeUntilExpiry - 5 * 60 * 1000);
+   
+   // Clear any existing refresh timers
+   if (window.refreshTimer) {
+     clearTimeout(window.refreshTimer);
+   }
+   
+   // Set up new refresh timer
+   window.refreshTimer = setTimeout(async () => {
+     await refreshToken();
+     checkAuth();
+   }, refreshTime);
+ }

You'll also need to add a global declaration for the refreshTimer variable:

declare global {
  interface Window {
    refreshTimer: ReturnType<typeof setTimeout> | undefined;
  }
}

3-6: Consider memoizing context value to prevent unnecessary re-renders.

The context value provided to AuthContext.Provider is created on each render. Consider memoizing this value to prevent unnecessary re-renders of consuming components.

- import { useEffect, useState, createContext, useContext, type ReactNode } from "react"
+ import { useEffect, useState, createContext, useContext, useMemo, type ReactNode } from "react"
import type { User } from "@/lib/auth"
import { useToast } from "@/hooks/use-toast"
import { useRouter } from "next/navigation"

// Later in the AuthProvider component:

+ // Memoize the context value to prevent unnecessary re-renders
+ const contextValue = useMemo(() => ({
+   user,
+   isLoading,
+   isAuthenticated,
+   login,
+   logout,
+   checkAuth
+ }), [user, isLoading, isAuthenticated]);

return (
-  <AuthContext.Provider value={{ user, isLoading, isAuthenticated, login, logout, checkAuth }}>
+  <AuthContext.Provider value={contextValue}>
    {children}
  </AuthContext.Provider>
)

55-57: Use dependency array for useEffect to prevent unnecessary checks.

The useEffect hook is missing dependencies, which could lead to exhaustive re-renders or stale data. Add the checkAuth function to the dependency array.

useEffect(() => {
  checkAuth()
- }, [])
+ }, [checkAuth])

// To prevent infinite loops, you should also memoize the checkAuth function:
+ const checkAuth = useCallback(async (): Promise<boolean> => {
  // ... existing implementation
- }, [])
+ }, [toast])

Don't forget to import useCallback:

- import { useEffect, useState, createContext, useContext, useMemo, type ReactNode } from "react"
+ import { useEffect, useState, createContext, useContext, useMemo, useCallback, type ReactNode } from "react"
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between d77eee3 and 86a5d61.

⛔ Files ignored due to path filters (1)
  • frontend/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (18)
  • frontend/package.json (2 hunks)
  • frontend/src/app/admin/features/[action]/[id]/page.tsx (1 hunks)
  • frontend/src/app/admin/layout.tsx (1 hunks)
  • frontend/src/app/admin/login/page.tsx (1 hunks)
  • frontend/src/app/admin/media/page.tsx (1 hunks)
  • frontend/src/app/admin/users/edit/[id]/page.tsx (1 hunks)
  • frontend/src/app/admin/users/loading.tsx (1 hunks)
  • frontend/src/app/admin/users/new/page.tsx (1 hunks)
  • frontend/src/app/admin/users/page.tsx (1 hunks)
  • frontend/src/app/api/admin/auth/check/route.ts (1 hunks)
  • frontend/src/app/api/admin/auth/login/route.ts (1 hunks)
  • frontend/src/app/api/admin/auth/logout/route.ts (1 hunks)
  • frontend/src/app/layout.tsx (1 hunks)
  • frontend/src/components/admin/media-upload.tsx (1 hunks)
  • frontend/src/components/map/MapComponent.tsx (4 hunks)
  • frontend/src/components/ui/radio-group.tsx (1 hunks)
  • frontend/src/hooks/use-auth.tsx (1 hunks)
  • frontend/src/lib/auth.ts (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • frontend/src/app/admin/users/loading.tsx
🚧 Files skipped from review as they are similar to previous changes (3)
  • frontend/src/app/api/admin/auth/logout/route.ts
  • frontend/src/app/layout.tsx
  • frontend/package.json
🧰 Additional context used
🧬 Code Definitions (6)
frontend/src/app/admin/layout.tsx (3)
frontend/src/lib/auth.ts (2)
  • User (3-9)
  • canAccessFeature (11-40)
frontend/src/hooks/use-toast.ts (2)
  • useToast (194-194)
  • toast (194-194)
frontend/src/components/ui/button.tsx (1)
  • Button (57-57)
frontend/src/app/api/admin/auth/login/route.ts (3)
frontend/src/app/api/admin/auth/logout/route.ts (1)
  • POST (4-14)
frontend/src/app/api/admin/media/route.ts (1)
  • POST (17-38)
frontend/src/app/api/admin/media/upload/route.ts (1)
  • POST (6-31)
frontend/src/app/admin/login/page.tsx (6)
frontend/src/hooks/use-auth.tsx (1)
  • useAuth (137-143)
frontend/src/components/ui/card.tsx (6)
  • Card (76-76)
  • CardHeader (76-76)
  • CardTitle (76-76)
  • CardDescription (76-76)
  • CardContent (76-76)
  • CardFooter (76-76)
frontend/src/components/ui/alert.tsx (2)
  • Alert (59-59)
  • AlertDescription (59-59)
frontend/src/components/ui/label.tsx (1)
  • Label (26-26)
frontend/src/components/ui/input.tsx (1)
  • Input (22-22)
frontend/src/components/ui/button.tsx (1)
  • Button (57-57)
frontend/src/app/admin/users/edit/[id]/page.tsx (2)
frontend/src/lib/auth.ts (1)
  • User (3-9)
frontend/src/components/ui/radio-group.tsx (2)
  • RadioGroup (44-44)
  • RadioGroupItem (44-44)
frontend/src/hooks/use-auth.tsx (1)
frontend/src/lib/auth.ts (1)
  • User (3-9)
frontend/src/app/admin/users/new/page.tsx (1)
frontend/src/components/ui/radio-group.tsx (2)
  • RadioGroup (44-44)
  • RadioGroupItem (44-44)
🪛 Biome (1.9.4)
frontend/src/app/api/admin/auth/check/route.ts

[error] 9-9: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


[error] 15-15: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

frontend/src/components/map/MapComponent.tsx

[error] 20-21: Avoid the delete operator which can impact performance.

Unsafe fix: Use an undefined assignment instead.

(lint/performance/noDelete)


[error] 117-117: Avoid the delete operator which can impact performance.

Unsafe fix: Use an undefined assignment instead.

(lint/performance/noDelete)

🔇 Additional comments (30)
frontend/src/components/ui/radio-group.tsx (4)

1-8: Well-structured imports and client directive.

The client directive at the top correctly indicates this component will run on the client-side. The imports are well-organized, bringing in the necessary dependencies from Radix UI and Lucide React. The use of the cn utility from your local utils library follows best practices for class name management.


9-21: Strong implementation of RadioGroup component.

Good use of forwardRef to properly pass refs to the underlying Radix UI component. The TypeScript typing is precise, using ElementRef and ComponentPropsWithoutRef to ensure type safety. The component correctly applies a grid layout with gap spacing while preserving any additional className properties passed by consumers.

Setting the displayName to match the primitive's displayName is a nice touch for debugging purposes.


23-42: Well-implemented RadioGroupItem with proper styling.

The RadioGroupItem component follows the same pattern as RadioGroup with proper ref forwarding and TypeScript typing. The styling provides a clean, accessible radio button with:

  • Appropriate dimensions and rounded shape
  • Primary border and text colors
  • Focus states for keyboard navigation
  • Disabled states for form control

The indicator implementation with the Circle icon from Lucide creates a visually pleasing selected state.


44-44: Clean component exports.

Both components are properly exported, making them available for use throughout the application.

frontend/src/components/map/MapComponent.tsx (7)

14-18: Global window.map declaration is now properly typed as optional

The change to make window.map optional with the ? modifier is a good improvement. This more accurately reflects that the map isn't always available on the window object, especially before initialization or after component unmount.


130-133: Refs for drawing properly defined

The DrawControl component now properly defines its refs with appropriate types. The drawingRef is for the polyline being drawn, and locationsRef holds the array of locations forming the polygon.


136-145: Click handler implementation is clean and efficient

The handler creates a new location object based on the clicked position, updates the locations array, initializes the polyline if needed, then updates both the drawn line and triggers the callback to the parent component.


147-153: Clean event binding and unbinding

The component correctly adds event listeners on mount and removes them on unmount, including cleaning up the polyline. This prevents memory leaks and unexpected behavior when the component is unmounted or remounted.


183-183: Better formatting for the Polygon component

The Polygon component rendering is now formatted in a more readable single-line style, maintaining consistency with the rest of the codebase.


49-56: Props interface updated with consistent formatting

The MapComponentProps interface has been updated to remove semicolons at the end of each property, which improves code consistency. The onPolygonChange function signature also matches the implementation in the DrawControl component.


109-120:

Details

❓ Verification inconclusive

Improved lifecycle management with proper cleanup

Good improvement adding a cleanup function in the useEffect hook that removes the global map reference when the component unmounts. This prevents memory leaks and avoids potential issues if the component is remounted.

However, there's a similar performance concern with using delete:

- delete window.map
+ window.map = undefined

Let's check if this impacts the component behavior:


🏁 Script executed:

#!/bin/bash
# Search for other code that might rely on checking if window.map property exists
rg "window\.map" --type=ts --type=tsx

Length of output: 67


Refine global map cleanup in MapComponent.tsx

Great improvement on lifecycle management with the new cleanup in the useEffect hook. One minor performance note: using window.map = undefined typically performs better than using delete window.map. My initial search for references to window.map returned an error due to TSX file type handling (unrecognized file type: tsx), so please manually verify that no part of the codebase depends on the existence of the window.map property when it's set to undefined.

  • File: frontend/src/components/map/MapComponent.tsx (lines 109–120)
  • Diff Suggestion:
    - delete window.map
    + window.map = undefined
🧰 Tools
🪛 Biome (1.9.4)

[error] 117-117: Avoid the delete operator which can impact performance.

Unsafe fix: Use an undefined assignment instead.

(lint/performance/noDelete)

frontend/src/app/admin/features/[action]/[id]/page.tsx (2)

211-219: LGTM! Good practice for handling input changes

The input change handler is properly implemented with appropriate permission checks.


1-764: LGTM! Overall well-structured component with good organization

The component demonstrates good practices for form handling, state management, and UI organization. The tabbed interface provides a good user experience for managing complex forms with multiple sections.

frontend/src/components/admin/media-upload.tsx (5)

1-11: Well-organized imports and client directive.

The component correctly uses the "use client" directive at the top, which is essential for client-side interaction functionality like file uploads. The imports are well-organized, bringing in necessary UI components and hooks.


13-17: Clean interface definition with appropriate props.

The MediaUploadProps interface is well-designed with clear prop types. Good use of the optional modifier for the disabled property.


19-25: Good initial state setup with clear type definitions.

The component properly initializes state variables for tracking upload status, preview URL, and error handling. The use of useRef for the file input is appropriate.


84-96: Solid implementation of utility functions.

The utility functions for handling file browsing and clearing images are well-implemented with appropriate disabled state handling.


152-195: Well-structured form controls with good error handling.

The right panel with URL input and file upload controls is well-designed with appropriate disabled states, error messaging, and informative text about supported formats.

frontend/src/app/api/admin/auth/login/route.ts (2)

6-25: Security risk: Hardcoded credentials in source code.

While this is noted as a simplified example, hardcoded credentials pose a significant security risk if accidentally deployed to production.

Consider storing credentials in environment variables:

-const USERS = {
-  admin: {
-    password: "password123",
-    role: "admin",
-    id: "1",
-    email: "admin@example.com",
-  },
-  // ...
-}
+const USERS = process.env.NODE_ENV === "production" 
+  ? {} // In production, this would connect to a database
+  : {
+    admin: {
+      password: process.env.ADMIN_PASSWORD || "password123",
+      role: "admin",
+      id: "1",
+      email: "admin@example.com",
+    },
+    // ...
+  }

27-64: Login implementation lacks security measures.

The current implementation is missing several important security features:

  • Rate limiting to prevent brute force attacks
  • CSRF protection
  • Proper authentication token generation (as mentioned in comments)

For immediate improvement, consider adding rate limiting:

+import { RateLimiter } from "some-rate-limiter-package"
+
+// Create a rate limiter (e.g., max 10 attempts per IP in 15 minutes)
+const loginRateLimiter = new RateLimiter({
+  windowMs: 15 * 60 * 1000,
+  max: 10
+})

export async function POST(request: Request) {
  try {
+    // Get client IP address (implementation depends on your hosting)
+    const ip = request.headers.get("x-forwarded-for") || "unknown"
+    
+    // Check rate limit
+    const rateLimitResult = await loginRateLimiter.check(ip)
+    if (!rateLimitResult.success) {
+      return NextResponse.json(
+        { message: "Too many login attempts. Please try again later." },
+        { status: 429 }
+      )
+    }
+    
    const body = await request.json()
frontend/src/app/api/admin/auth/check/route.ts (1)

4-42: Consider enhancing authentication security.

While the implementation works for basic authentication checks, the current approach could benefit from improved security.

#!/bin/bash
# Check for potential security enhancements

# Look for JWT libraries that could be used for secure token handling
echo "Checking for JWT libraries in package.json:"
cat package.json | grep -E "jsonwebtoken|jose|next-auth" || echo "No JWT libraries found"

# Check if there are any existing encryption utilities
echo -e "\nLooking for encryption utilities:"
fd -e ts -e js -t f | xargs grep -l "crypto" || echo "No crypto utilities found"
🧰 Tools
🪛 Biome (1.9.4)

[error] 9-9: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


[error] 15-15: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

frontend/src/app/admin/layout.tsx (4)

13-18: Well-defined NavItem interface with appropriate typing.

The NavItem interface provides a clear structure for navigation items, with proper typing for the feature property using specific string literals.


72-104: Well-implemented logout handler with proper state management.

The logout handler is comprehensive, including loading state management, error handling with user feedback via toast notifications, and proper state updates upon successful logout.


123-138: Robust navigation item definition with feature permissions.

The navigation items are well-structured with appropriate icons and feature mappings. The use of the canAccessFeature function to filter items based on user roles is a good security practice.


140-207: Well-designed responsive layout with accessibility considerations.

The admin layout is well-implemented with:

  • Responsive sidebar that works on both mobile and desktop
  • Visual indicators for active navigation items
  • Clean separation of navigation and content areas
  • Proper overlay for mobile interaction

The implementation follows good UI/UX practices with appropriate transitions and visual feedback.

frontend/src/app/admin/users/edit/[id]/page.tsx (1)

1-15: Good use of imports and client directive.

The component correctly uses the "use client" directive and imports necessary dependencies. The imports are well-organized and grouped logically.

frontend/src/app/admin/users/new/page.tsx (1)

1-15: Good organization of imports and client directive.

The imports are well-structured and the "use client" directive is correctly placed at the top of the file.

frontend/src/lib/auth.ts (1)

1-9: Good type definition and interface structure.

The UserRole type and User interface are well-defined with clear, descriptive types.

frontend/src/app/admin/login/page.tsx (2)

1-14: Good organization of imports and client directive.

The imports are well-structured and the "use client" directive is correctly placed at the top of the file.


15-61: Well-structured authentication logic.

The component implements authentication checks and login handling effectively. Nice work on separating the auth check and login functionality.

Comment on lines +567 to +573
value={feature.content.heroDescription}
onChange={(e) => handleContentChange("heroDescription", "", e.target.value)}
placeholder="Detailed description of the feature"
rows={4}
readOnly={!canEdit}
className={!canEdit ? "bg-gray-100" : ""}
/>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

Consistent issue with handleContentChange usage

Similar to the previous issue, there are several instances where handleContentChange is called with an empty string as the second parameter, which seems inconsistent with the function's intended usage.

These instances need to be fixed using the approach suggested in the previous comment.

Also applies to: 678-683, 690-695

Comment on lines +408 to +420
<Button className="flex items-center gap-2" onClick={handleSubmit} disabled={isSaving || !canEdit}>
{isSaving ? (
<>
<div className="animate-spin rounded-full h-4 w-4 border-t-2 border-b-2 border-white"></div>
Saving...
</>
) : (
<>
<Save className="h-4 w-4" />
Save Feature
</>
)}
</Button>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Fix form submission behavior

The form has an onSubmit handler on the form element (line 440), but the submission is actually triggered by a button click. This can lead to confusion and might bypass native form validation.

Change the button type to "submit" so that form submission properly triggers the form's onSubmit handler:

 <Button 
   className="flex items-center gap-2" 
-  onClick={handleSubmit} 
+  type="submit"
   disabled={isSaving || !canEdit}>
   {isSaving ? (
     <>
       <div className="animate-spin rounded-full h-4 w-4 border-t-2 border-b-2 border-white"></div>
       Saving...
     </>
   ) : (
     <>
       <Save className="h-4 w-4" />
       Save Feature
     </>
   )}
 </Button>
📝 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
<Button className="flex items-center gap-2" onClick={handleSubmit} disabled={isSaving || !canEdit}>
{isSaving ? (
<>
<div className="animate-spin rounded-full h-4 w-4 border-t-2 border-b-2 border-white"></div>
Saving...
</>
) : (
<>
<Save className="h-4 w-4" />
Save Feature
</>
)}
</Button>
<Button className="flex items-center gap-2" type="submit" disabled={isSaving || !canEdit}>
{isSaving ? (
<>
<div className="animate-spin rounded-full h-4 w-4 border-t-2 border-b-2 border-white"></div>
Saving...
</>
) : (
<>
<Save className="h-4 w-4" />
Save Feature
</>
)}
</Button>

Comment on lines +122 to +209
useEffect(() => {
const fetchFeature = async () => {
if (isNew) {
setIsLoading(false)
return
}

try {
// In a real implementation, this would fetch from an API
// For now, we'll simulate with static data
if (isEditing && id) {
// Mock data for editing
const mockFeature: Feature = {
id: id as string,
title: "Optimal Site Location",
slug: "site-location",
description:
"Use AI algorithms to determine the best locations for air quality monitors based on population density, pollution sources, and geographic factors.",
icon: "MapPin",
status: "published",
content: {
heroTitle: "Optimal Site Location",
heroDescription:
"Our AI-powered site location tool helps you determine the most effective places to position air quality monitors based on population density, pollution sources, and geographic factors.",
howItWorks: {
title: "How It Works",
steps: [
{
title: "Define Your Area",
description: "Draw a polygon on the map or search for a location to define your area of interest.",
},
{
title: "Set Parameters",
description: "Specify the number of sensors and minimum distance between them.",
},
{
title: "Get Optimal Locations",
description: "Our AI algorithm will suggest the best locations for your air quality monitors.",
},
],
},
benefits: {
title: "Key Benefits",
items: [
{
title: "Maximize Coverage",
description:
"Ensure your monitoring network covers the most critical areas with minimal redundancy.",
},
{
title: "Cost Efficiency",
description:
"Optimize your resources by placing sensors where they'll provide the most valuable data.",
},
{
title: "Data-Driven Decisions",
description: "Base your deployment strategy on scientific analysis rather than guesswork.",
},
{
title: "Customizable Constraints",
description:
"Add must-have locations and set minimum distances to meet your specific requirements.",
},
],
},
ctaTitle: "Ready to find the optimal locations for your air quality monitors?",
ctaButtonText: "Try Site Locator Now",
},
seo: {
title: "Optimal Site Location | AirQo AI",
description:
"Our AI-powered site location tool helps you determine the most effective places to position air quality monitors.",
},
heroImage: "/placeholder.svg?height=400&width=800&text=Site+Location+Image",
updatedAt: "2023-07-15T10:30:00Z",
}
setFeature(mockFeature)
}
} catch (error) {
console.error("Failed to fetch feature:", error)
setError("Failed to load feature data. Please try again.")
} finally {
setIsLoading(false)
}
}

fetchFeature()
}, [id, isEditing, isNew])
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Replace mock data with actual API implementation

The current implementation uses hardcoded mock data for development purposes. When moving to production, this should be replaced with actual API calls.

Consider implementing a service layer to handle API requests:

-        // In a real implementation, this would fetch from an API
-        // For now, we'll simulate with static data
-        if (isEditing && id) {
-          // Mock data for editing
-          const mockFeature: Feature = {
-            id: id as string,
-            title: "Optimal Site Location",
-            // ...rest of mock data
-          }
-          setFeature(mockFeature)
-        }
+        if (isEditing && id) {
+          const response = await fetch(`/api/features/${id}`);
+          if (!response.ok) throw new Error('Failed to fetch feature');
+          const featureData = await response.json();
+          setFeature(featureData);
+        }
📝 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
useEffect(() => {
const fetchFeature = async () => {
if (isNew) {
setIsLoading(false)
return
}
try {
// In a real implementation, this would fetch from an API
// For now, we'll simulate with static data
if (isEditing && id) {
// Mock data for editing
const mockFeature: Feature = {
id: id as string,
title: "Optimal Site Location",
slug: "site-location",
description:
"Use AI algorithms to determine the best locations for air quality monitors based on population density, pollution sources, and geographic factors.",
icon: "MapPin",
status: "published",
content: {
heroTitle: "Optimal Site Location",
heroDescription:
"Our AI-powered site location tool helps you determine the most effective places to position air quality monitors based on population density, pollution sources, and geographic factors.",
howItWorks: {
title: "How It Works",
steps: [
{
title: "Define Your Area",
description: "Draw a polygon on the map or search for a location to define your area of interest.",
},
{
title: "Set Parameters",
description: "Specify the number of sensors and minimum distance between them.",
},
{
title: "Get Optimal Locations",
description: "Our AI algorithm will suggest the best locations for your air quality monitors.",
},
],
},
benefits: {
title: "Key Benefits",
items: [
{
title: "Maximize Coverage",
description:
"Ensure your monitoring network covers the most critical areas with minimal redundancy.",
},
{
title: "Cost Efficiency",
description:
"Optimize your resources by placing sensors where they'll provide the most valuable data.",
},
{
title: "Data-Driven Decisions",
description: "Base your deployment strategy on scientific analysis rather than guesswork.",
},
{
title: "Customizable Constraints",
description:
"Add must-have locations and set minimum distances to meet your specific requirements.",
},
],
},
ctaTitle: "Ready to find the optimal locations for your air quality monitors?",
ctaButtonText: "Try Site Locator Now",
},
seo: {
title: "Optimal Site Location | AirQo AI",
description:
"Our AI-powered site location tool helps you determine the most effective places to position air quality monitors.",
},
heroImage: "/placeholder.svg?height=400&width=800&text=Site+Location+Image",
updatedAt: "2023-07-15T10:30:00Z",
}
setFeature(mockFeature)
}
} catch (error) {
console.error("Failed to fetch feature:", error)
setError("Failed to load feature data. Please try again.")
} finally {
setIsLoading(false)
}
}
fetchFeature()
}, [id, isEditing, isNew])
useEffect(() => {
const fetchFeature = async () => {
if (isNew) {
setIsLoading(false)
return
}
try {
if (isEditing && id) {
const response = await fetch(`/api/features/${id}`);
if (!response.ok) throw new Error('Failed to fetch feature');
const featureData = await response.json();
setFeature(featureData);
}
} catch (error) {
console.error("Failed to fetch feature:", error)
setError("Failed to load feature data. Please try again.")
} finally {
setIsLoading(false)
}
}
fetchFeature()
}, [id, isEditing, isNew])

Comment on lines +221 to +234
const handleContentChange = (section: string, field: string, value: string) => {
if (!canEdit) return

setFeature((prev) => ({
...prev,
content: {
...prev.content,
[section]: {
...prev.content[section as keyof typeof prev.content],
[field]: value,
},
},
}))
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve type safety in handleContentChange function

The current implementation uses type casting which could potentially lead to runtime errors if an invalid section name is provided.

Consider adding type checking or using a more type-safe approach:

 const handleContentChange = (section: string, field: string, value: string) => {
   if (!canEdit) return

+  // Make sure section is a valid key in content
+  if (!(section in feature.content)) {
+    console.error(`Invalid section: ${section}`);
+    return;
+  }
+
   setFeature((prev) => ({
     ...prev,
     content: {
       ...prev.content,
       [section]: {
         ...prev.content[section as keyof typeof prev.content],
         [field]: value,
       },
     },
   }))
 }
📝 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 handleContentChange = (section: string, field: string, value: string) => {
if (!canEdit) return
setFeature((prev) => ({
...prev,
content: {
...prev.content,
[section]: {
...prev.content[section as keyof typeof prev.content],
[field]: value,
},
},
}))
}
const handleContentChange = (section: string, field: string, value: string) => {
if (!canEdit) return
// Make sure section is a valid key in content
if (!(section in feature.content)) {
console.error(`Invalid section: ${section}`);
return;
}
setFeature((prev) => ({
...prev,
content: {
...prev.content,
[section]: {
...prev.content[section as keyof typeof prev.content],
[field]: value,
},
},
}))
}

Comment on lines +556 to +560
onChange={(e) => handleContentChange("heroTitle", "", e.target.value)}
placeholder="Main title for the feature page"
readOnly={!canEdit}
className={!canEdit ? "bg-gray-100" : ""}
/>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix handler usage in handleContentChange

The current implementation of handleContentChange expects three parameters, but you're passing an empty string as the second parameter. This seems inconsistent with other usages of this function.

Correct the function call to match the expected parameters:

 <Input
   id="heroTitle"
   value={feature.content.heroTitle}
-  onChange={(e) => handleContentChange("heroTitle", "", e.target.value)}
+  onChange={(e) => setFeature(prev => ({
+    ...prev,
+    content: {
+      ...prev.content,
+      heroTitle: e.target.value
+    }
+  }))}
   placeholder="Main title for the feature page"
   readOnly={!canEdit}
   className={!canEdit ? "bg-gray-100" : ""}
 />

Or alternatively, restructure handleContentChange to handle both direct properties and nested properties.

Comment on lines +228 to +230
<p className="text-sm text-gray-500">
Users will receive an email notification when they are added to the system.
</p>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Verify email notification functionality.

The footer mentions that users will receive an email notification, but there's no implementation for sending emails in the code. Either implement this functionality or update the message to reflect the actual behavior.


🏁 Script executed:

#!/bin/bash
# Check if email notification functionality is implemented
fd "email|notification" --type f --exec grep -l "send" {} \;

Length of output: 63



Action Required: Clarify Email Notification Implementation

Based on our repository-wide search (which returned no evidence of email sending code such as "nodemailer", "sendMail(", or "emailNotification"), it appears that the email notification functionality referenced in the UI isn’t implemented.

  • Frontend (File: frontend/src/app/admin/users/new/page.tsx, Lines: 228-230): The message presently notifies users that they will receive an email upon being added, yet no corresponding functionality exists in the codebase.
  • Next Steps:
    • If email notifications are intended: Implement the backend/email functionality to send notifications.
    • Otherwise: Update the UI text to accurately reflect the current behavior and avoid misleading users.

Please verify if the email sending is handled externally or in a separate module. Adjust the implementation or the messaging accordingly.


🏁 Script executed:

#!/bin/bash
# Further verify potential email sending functionality across the repository.
rg -i "nodemailer" && echo "nodemailer usage found" || echo "no nodemailer usage found"
rg -i "sendMail(" && echo "sendMail usage found" || echo "no sendMail usage found"
rg -i "emailNotification" && echo "emailNotification references found" || echo "no emailNotification references found"

Length of output: 336


Action Required: Address Missing Email Notification Implementation

After verifying the codebase, there is no evidence of any email sending functionality (e.g., via "nodemailer", "sendMail(", or "emailNotification"). The current UI message in frontend/src/app/admin/users/new/page.tsx (lines 228-230):

<p className="text-sm text-gray-500">
  Users will receive an email notification when they are added to the system.
</p>

appears misleading since the corresponding backend functionality for sending emails is absent.

  • Next Steps:
    • If email notifications are intended: Implement the email sending functionality in an appropriate module or update integration with an external service.
    • Otherwise: Revise the UI message to accurately reflect the current behavior, thereby preventing potential confusion.

Comment on lines +195 to +206
const newItem: MediaItem = {
id: `new-${Date.now()}`,
name: newMediaUrl.split("/").pop() || `media-${Date.now()}`,
url: newMediaUrl,
type: "image/jpeg", // Assuming it's an image
size: 100000, // Dummy size
uploadedAt: new Date().toISOString(),
dimensions: {
width: 800,
height: 600,
},
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve media type detection for new items.

The current implementation hardcodes the media type as "image/jpeg" when adding new media. Consider detecting the media type based on the file extension or URL.

const newItem: MediaItem = {
  id: `new-${Date.now()}`,
  name: newMediaUrl.split("/").pop() || `media-${Date.now()}`,
  url: newMediaUrl,
-  type: "image/jpeg", // Assuming it's an image
+  type: detectMediaType(newMediaUrl), // Helper function to detect type
  size: 100000, // Dummy size
  uploadedAt: new Date().toISOString(),
  dimensions: {
    width: 800,
    height: 600,
  },
}

// Add this helper function
function detectMediaType(url: string): string {
  const extension = url.split('.').pop()?.toLowerCase();
  const imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'svg', 'webp'];
  const docExtensions = ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx'];
  
  if (imageExtensions.includes(extension)) {
    return `image/${extension === 'jpg' ? 'jpeg' : extension}`;
  } else if (docExtensions.includes(extension)) {
    return 'application/octet-stream';
  }
  
  return 'application/octet-stream';
}
📝 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 newItem: MediaItem = {
id: `new-${Date.now()}`,
name: newMediaUrl.split("/").pop() || `media-${Date.now()}`,
url: newMediaUrl,
type: "image/jpeg", // Assuming it's an image
size: 100000, // Dummy size
uploadedAt: new Date().toISOString(),
dimensions: {
width: 800,
height: 600,
},
}
const newItem: MediaItem = {
id: `new-${Date.now()}`,
name: newMediaUrl.split("/").pop() || `media-${Date.now()}`,
url: newMediaUrl,
type: detectMediaType(newMediaUrl), // Helper function to detect type
size: 100000, // Dummy size
uploadedAt: new Date().toISOString(),
dimensions: {
width: 800,
height: 600,
},
}
// Add this helper function
function detectMediaType(url: string): string {
const extension = url.split('.').pop()?.toLowerCase();
const imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'svg', 'webp'];
const docExtensions = ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx'];
if (imageExtensions.includes(extension)) {
return `image/${extension === 'jpg' ? 'jpeg' : extension}`;
} else if (docExtensions.includes(extension)) {
return 'application/octet-stream';
}
return 'application/octet-stream';
}

Comment on lines +269 to +456
<TabsContent value="all" className="mt-0">
<div className="space-y-6">
{/* Upload Section */}
<Card>
<CardContent className="p-4">
<div className="space-y-4">
<h3 className="text-lg font-medium">Add New Media</h3>
<MediaUpload currentImageUrl={newMediaUrl} onImageSelected={setNewMediaUrl} disabled={!canEdit} />
<div className="flex justify-end">
<Button
onClick={handleAddMedia}
disabled={!newMediaUrl || !canEdit}
className="flex items-center gap-2"
>
<Plus className="h-4 w-4" />
Add to Library
</Button>
</div>
</div>
</CardContent>
</Card>

{/* Media Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
{filteredItems.length === 0 ? (
<div className="col-span-full text-center py-12 text-gray-500">
<FolderOpen className="h-12 w-12 mx-auto mb-4 text-gray-400" />
<p>No media items found</p>
</div>
) : (
filteredItems.map((item) => (
<Card
key={item.id}
className={`overflow-hidden cursor-pointer transition-shadow hover:shadow-md ${
selectedItem?.id === item.id ? "ring-2 ring-blue-500" : ""
}`}
onClick={() => setSelectedItem(item)}
>
<div className="aspect-video relative bg-gray-100">
<img
src={item.url || "/placeholder.svg"}
alt={item.name}
className="object-cover w-full h-full"
onError={(e) => {
;(e.target as HTMLImageElement).src =
"/placeholder.svg?height=200&width=300&text=Preview+Not+Available"
}}
/>
</div>
<CardContent className="p-3">
<div className="flex justify-between items-start">
<div className="truncate">
<p className="font-medium truncate" title={item.name}>
{item.name}
</p>
<p className="text-xs text-gray-500">
{formatFileSize(item.size)} • {formatDate(item.uploadedAt)}
</p>
</div>
<div className="flex gap-1">
<Button
variant="ghost"
size="icon"
className="h-7 w-7"
onClick={(e) => {
e.stopPropagation()
handleCopyUrl(item.url)
}}
>
<Copy className="h-4 w-4" />
</Button>
{canEdit && (
<Button
variant="ghost"
size="icon"
className="h-7 w-7 text-red-500"
onClick={(e) => {
e.stopPropagation()
handleDeleteMedia(item.id)
}}
>
<Trash2 className="h-4 w-4" />
</Button>
)}
</div>
</div>
</CardContent>
</Card>
))
)}
</div>
</div>
</TabsContent>

<TabsContent value="images" className="mt-0">
{/* Same structure as "all" tab but filtered for images */}
<div className="space-y-6">
{/* Upload Section (same as above) */}
<Card>
<CardContent className="p-4">
<div className="space-y-4">
<h3 className="text-lg font-medium">Add New Image</h3>
<MediaUpload currentImageUrl={newMediaUrl} onImageSelected={setNewMediaUrl} disabled={!canEdit} />
<div className="flex justify-end">
<Button
onClick={handleAddMedia}
disabled={!newMediaUrl || !canEdit}
className="flex items-center gap-2"
>
<Plus className="h-4 w-4" />
Add to Library
</Button>
</div>
</div>
</CardContent>
</Card>

{/* Media Grid (filtered for images) */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
{filteredItems.length === 0 ? (
<div className="col-span-full text-center py-12 text-gray-500">
<FolderOpen className="h-12 w-12 mx-auto mb-4 text-gray-400" />
<p>No images found</p>
</div>
) : (
filteredItems.map((item) => (
<Card
key={item.id}
className={`overflow-hidden cursor-pointer transition-shadow hover:shadow-md ${
selectedItem?.id === item.id ? "ring-2 ring-blue-500" : ""
}`}
onClick={() => setSelectedItem(item)}
>
<div className="aspect-video relative bg-gray-100">
<img
src={item.url || "/placeholder.svg"}
alt={item.name}
className="object-cover w-full h-full"
onError={(e) => {
;(e.target as HTMLImageElement).src =
"/placeholder.svg?height=200&width=300&text=Preview+Not+Available"
}}
/>
</div>
<CardContent className="p-3">
<div className="flex justify-between items-start">
<div className="truncate">
<p className="font-medium truncate" title={item.name}>
{item.name}
</p>
<p className="text-xs text-gray-500">
{formatFileSize(item.size)} • {formatDate(item.uploadedAt)}
</p>
</div>
<div className="flex gap-1">
<Button
variant="ghost"
size="icon"
className="h-7 w-7"
onClick={(e) => {
e.stopPropagation()
handleCopyUrl(item.url)
}}
>
<Copy className="h-4 w-4" />
</Button>
{canEdit && (
<Button
variant="ghost"
size="icon"
className="h-7 w-7 text-red-500"
onClick={(e) => {
e.stopPropagation()
handleDeleteMedia(item.id)
}}
>
<Trash2 className="h-4 w-4" />
</Button>
)}
</div>
</div>
</CardContent>
</Card>
))
)}
</div>
</div>
</TabsContent>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Reduce code duplication between tabs.

There's significant duplication between the "all" tab and "images" tab content. Consider refactoring to extract the common UI components into reusable elements.

+ // Create a reusable TabContent component at the top of your file or in a separate file
+ const MediaTabContent = ({ 
+   items, 
+   newMediaUrl, 
+   setNewMediaUrl, 
+   canEdit, 
+   handleAddMedia, 
+   handleCopyUrl, 
+   handleDeleteMedia, 
+   selectedItem, 
+   setSelectedItem, 
+   emptyMessage = "No media items found" 
+ }) => (
+   <div className="space-y-6">
+     {/* Upload Section */}
+     <Card>
+       <CardContent className="p-4">
+         <div className="space-y-4">
+           <h3 className="text-lg font-medium">Add New Media</h3>
+           <MediaUpload currentImageUrl={newMediaUrl} onImageSelected={setNewMediaUrl} disabled={!canEdit} />
+           <div className="flex justify-end">
+             <Button
+               onClick={handleAddMedia}
+               disabled={!newMediaUrl || !canEdit}
+               className="flex items-center gap-2"
+             >
+               <Plus className="h-4 w-4" />
+               Add to Library
+             </Button>
+           </div>
+         </div>
+       </CardContent>
+     </Card>
+
+     {/* Media Grid */}
+     <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
+       {items.length === 0 ? (
+         <div className="col-span-full text-center py-12 text-gray-500">
+           <FolderOpen className="h-12 w-12 mx-auto mb-4 text-gray-400" />
+           <p>{emptyMessage}</p>
+         </div>
+       ) : (
+         items.map((item) => (
+           <Card
+             key={item.id}
+             className={`overflow-hidden cursor-pointer transition-shadow hover:shadow-md ${
+               selectedItem?.id === item.id ? "ring-2 ring-blue-500" : ""
+             }`}
+             onClick={() => setSelectedItem(item)}
+           >
+             {/* Card content */}
+             {/* ... rest of your card code */}
+           </Card>
+         ))
+       )}
+     </div>
+   </div>
+ );
+
+ // Then use it in your tabs
+ <TabsContent value="all" className="mt-0">
+   <MediaTabContent 
+     items={filteredItems}
+     newMediaUrl={newMediaUrl}
+     setNewMediaUrl={setNewMediaUrl}
+     canEdit={canEdit}
+     handleAddMedia={handleAddMedia}
+     handleCopyUrl={handleCopyUrl}
+     handleDeleteMedia={handleDeleteMedia}
+     selectedItem={selectedItem}
+     setSelectedItem={setSelectedItem}
+     emptyMessage="No media items found"
+   />
+ </TabsContent>
+
+ <TabsContent value="images" className="mt-0">
+   <MediaTabContent 
+     items={filteredItems}
+     newMediaUrl={newMediaUrl}
+     setNewMediaUrl={setNewMediaUrl}
+     canEdit={canEdit}
+     handleAddMedia={handleAddMedia}
+     handleCopyUrl={handleCopyUrl}
+     handleDeleteMedia={handleDeleteMedia}
+     selectedItem={selectedItem}
+     setSelectedItem={setSelectedItem}
+     emptyMessage="No images found"
+   />
+ </TabsContent>
📝 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
<TabsContent value="all" className="mt-0">
<div className="space-y-6">
{/* Upload Section */}
<Card>
<CardContent className="p-4">
<div className="space-y-4">
<h3 className="text-lg font-medium">Add New Media</h3>
<MediaUpload currentImageUrl={newMediaUrl} onImageSelected={setNewMediaUrl} disabled={!canEdit} />
<div className="flex justify-end">
<Button
onClick={handleAddMedia}
disabled={!newMediaUrl || !canEdit}
className="flex items-center gap-2"
>
<Plus className="h-4 w-4" />
Add to Library
</Button>
</div>
</div>
</CardContent>
</Card>
{/* Media Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
{filteredItems.length === 0 ? (
<div className="col-span-full text-center py-12 text-gray-500">
<FolderOpen className="h-12 w-12 mx-auto mb-4 text-gray-400" />
<p>No media items found</p>
</div>
) : (
filteredItems.map((item) => (
<Card
key={item.id}
className={`overflow-hidden cursor-pointer transition-shadow hover:shadow-md ${
selectedItem?.id === item.id ? "ring-2 ring-blue-500" : ""
}`}
onClick={() => setSelectedItem(item)}
>
<div className="aspect-video relative bg-gray-100">
<img
src={item.url || "/placeholder.svg"}
alt={item.name}
className="object-cover w-full h-full"
onError={(e) => {
;(e.target as HTMLImageElement).src =
"/placeholder.svg?height=200&width=300&text=Preview+Not+Available"
}}
/>
</div>
<CardContent className="p-3">
<div className="flex justify-between items-start">
<div className="truncate">
<p className="font-medium truncate" title={item.name}>
{item.name}
</p>
<p className="text-xs text-gray-500">
{formatFileSize(item.size)} {formatDate(item.uploadedAt)}
</p>
</div>
<div className="flex gap-1">
<Button
variant="ghost"
size="icon"
className="h-7 w-7"
onClick={(e) => {
e.stopPropagation()
handleCopyUrl(item.url)
}}
>
<Copy className="h-4 w-4" />
</Button>
{canEdit && (
<Button
variant="ghost"
size="icon"
className="h-7 w-7 text-red-500"
onClick={(e) => {
e.stopPropagation()
handleDeleteMedia(item.id)
}}
>
<Trash2 className="h-4 w-4" />
</Button>
)}
</div>
</div>
</CardContent>
</Card>
))
)}
</div>
</div>
</TabsContent>
<TabsContent value="images" className="mt-0">
{/* Same structure as "all" tab but filtered for images */}
<div className="space-y-6">
{/* Upload Section (same as above) */}
<Card>
<CardContent className="p-4">
<div className="space-y-4">
<h3 className="text-lg font-medium">Add New Image</h3>
<MediaUpload currentImageUrl={newMediaUrl} onImageSelected={setNewMediaUrl} disabled={!canEdit} />
<div className="flex justify-end">
<Button
onClick={handleAddMedia}
disabled={!newMediaUrl || !canEdit}
className="flex items-center gap-2"
>
<Plus className="h-4 w-4" />
Add to Library
</Button>
</div>
</div>
</CardContent>
</Card>
{/* Media Grid (filtered for images) */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
{filteredItems.length === 0 ? (
<div className="col-span-full text-center py-12 text-gray-500">
<FolderOpen className="h-12 w-12 mx-auto mb-4 text-gray-400" />
<p>No images found</p>
</div>
) : (
filteredItems.map((item) => (
<Card
key={item.id}
className={`overflow-hidden cursor-pointer transition-shadow hover:shadow-md ${
selectedItem?.id === item.id ? "ring-2 ring-blue-500" : ""
}`}
onClick={() => setSelectedItem(item)}
>
<div className="aspect-video relative bg-gray-100">
<img
src={item.url || "/placeholder.svg"}
alt={item.name}
className="object-cover w-full h-full"
onError={(e) => {
;(e.target as HTMLImageElement).src =
"/placeholder.svg?height=200&width=300&text=Preview+Not+Available"
}}
/>
</div>
<CardContent className="p-3">
<div className="flex justify-between items-start">
<div className="truncate">
<p className="font-medium truncate" title={item.name}>
{item.name}
</p>
<p className="text-xs text-gray-500">
{formatFileSize(item.size)} {formatDate(item.uploadedAt)}
</p>
</div>
<div className="flex gap-1">
<Button
variant="ghost"
size="icon"
className="h-7 w-7"
onClick={(e) => {
e.stopPropagation()
handleCopyUrl(item.url)
}}
>
<Copy className="h-4 w-4" />
</Button>
{canEdit && (
<Button
variant="ghost"
size="icon"
className="h-7 w-7 text-red-500"
onClick={(e) => {
e.stopPropagation()
handleDeleteMedia(item.id)
}}
>
<Trash2 className="h-4 w-4" />
</Button>
)}
</div>
</div>
</CardContent>
</Card>
))
)}
</div>
</div>
</TabsContent>
// Add this reusable component at the top of your file or in a separate file
const MediaTabContent = ({
items,
newMediaUrl,
setNewMediaUrl,
canEdit,
handleAddMedia,
handleCopyUrl,
handleDeleteMedia,
selectedItem,
setSelectedItem,
heading = "Add New Media",
emptyMessage = "No media items found"
}) => (
<div className="space-y-6">
{/* Upload Section */}
<Card>
<CardContent className="p-4">
<div className="space-y-4">
<h3 className="text-lg font-medium">{heading}</h3>
<MediaUpload currentImageUrl={newMediaUrl} onImageSelected={setNewMediaUrl} disabled={!canEdit} />
<div className="flex justify-end">
<Button
onClick={handleAddMedia}
disabled={!newMediaUrl || !canEdit}
className="flex items-center gap-2"
>
<Plus className="h-4 w-4" />
Add to Library
</Button>
</div>
</div>
</CardContent>
</Card>
{/* Media Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
{items.length === 0 ? (
<div className="col-span-full text-center py-12 text-gray-500">
<FolderOpen className="h-12 w-12 mx-auto mb-4 text-gray-400" />
<p>{emptyMessage}</p>
</div>
) : (
items.map((item) => (
<Card
key={item.id}
className={`overflow-hidden cursor-pointer transition-shadow hover:shadow-md ${
selectedItem?.id === item.id ? "ring-2 ring-blue-500" : ""
}`}
onClick={() => setSelectedItem(item)}
>
<div className="aspect-video relative bg-gray-100">
<img
src={item.url || "/placeholder.svg"}
alt={item.name}
className="object-cover w-full h-full"
onError={(e) => {
(e.target as HTMLImageElement).src =
"/placeholder.svg?height=200&width=300&text=Preview+Not+Available";
}}
/>
</div>
<CardContent className="p-3">
<div className="flex justify-between items-start">
<div className="truncate">
<p className="font-medium truncate" title={item.name}>
{item.name}
</p>
<p className="text-xs text-gray-500">
{formatFileSize(item.size)} {formatDate(item.uploadedAt)}
</p>
</div>
<div className="flex gap-1">
<Button
variant="ghost"
size="icon"
className="h-7 w-7"
onClick={(e) => {
e.stopPropagation();
handleCopyUrl(item.url);
}}
>
<Copy className="h-4 w-4" />
</Button>
{canEdit && (
<Button
variant="ghost"
size="icon"
className="h-7 w-7 text-red-500"
onClick={(e) => {
e.stopPropagation();
handleDeleteMedia(item.id);
}}
>
<Trash2 className="h-4 w-4" />
</Button>
)}
</div>
</div>
</CardContent>
</Card>
))
)}
</div>
</div>
);
// Then update the tabs to use the new MediaTabContent component
<TabsContent value="all" className="mt-0">
<MediaTabContent
items={filteredItems}
newMediaUrl={newMediaUrl}
setNewMediaUrl={setNewMediaUrl}
canEdit={canEdit}
handleAddMedia={handleAddMedia}
handleCopyUrl={handleCopyUrl}
handleDeleteMedia={handleDeleteMedia}
selectedItem={selectedItem}
setSelectedItem={setSelectedItem}
heading="Add New Media"
emptyMessage="No media items found"
/>
</TabsContent>
<TabsContent value="images" className="mt-0">
<MediaTabContent
items={filteredItems}
newMediaUrl={newMediaUrl}
setNewMediaUrl={setNewMediaUrl}
canEdit={canEdit}
handleAddMedia={handleAddMedia}
handleCopyUrl={handleCopyUrl}
handleDeleteMedia={handleDeleteMedia}
selectedItem={selectedItem}
setSelectedItem={setSelectedItem}
heading="Add New Image"
emptyMessage="No images found"
/>
</TabsContent>

Comment on lines +22 to +28
export default function UsersPage() {
const [users, setUsers] = useState<User[]>([])
const [isLoading, setIsLoading] = useState(true)
const [searchQuery, setSearchQuery] = useState("")
const [sortField, setSortField] = useState<keyof User>("username")
const [sortDirection, setSortDirection] = useState<"asc" | "desc">("asc")
const { toast } = useToast()
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add authentication check for user management access.

This page doesn't verify if the current user has permission to manage users. Consider adding an authentication check similar to the media page.

import { useToast } from "@/hooks/use-toast"
+ import { useAuth } from "@/hooks/use-auth"
+ import { canManageUsers } from "@/lib/auth"

export default function UsersPage() {
  const [users, setUsers] = useState<User[]>([])
  const [isLoading, setIsLoading] = useState(true)
  const [searchQuery, setSearchQuery] = useState("")
  const [sortField, setSortField] = useState<keyof User>("username")
  const [sortDirection, setSortDirection] = useState<"asc" | "desc">("asc")
  const { toast } = useToast()
+ const { user } = useAuth()
+ 
+ // Check if user has permission to manage users
+ const canManage = canManageUsers(user)
+ 
+ // Redirect if user doesn't have permission
+ useEffect(() => {
+   if (!isLoading && !canManage) {
+     toast({
+       title: "Access Denied",
+       description: "You don't have permission to access user management",
+       variant: "destructive",
+     })
+     router.push("/admin")
+   }
+ }, [isLoading, canManage, toast, router])

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

Comment on lines +62 to +68
const response = await fetch("/api/admin/auth/login", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ username, password }),
})
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add CSRF protection to API requests.

The API requests don't include CSRF protection. Consider adding CSRF tokens to sensitive requests.

const login = async (username: string, password: string): Promise<boolean> => {
  setIsLoading(true)
  try {
    const response = await fetch("/api/admin/auth/login", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
+       "X-CSRF-Token": await getCsrfToken(),
      },
      body: JSON.stringify({ username, password }),
    })

+ // Add a function to fetch CSRF token
+ const getCsrfToken = async (): Promise<string> => {
+   try {
+     const response = await fetch("/api/csrf-token");
+     const data = await response.json();
+     return data.csrfToken;
+   } catch (error) {
+     console.error("Failed to fetch CSRF token:", error);
+     return "";
+   }
+ }

Apply similar changes to the logout method and any other sensitive API calls.

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

Copy link
Copy Markdown

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

🧹 Nitpick comments (52)
frontend/src/components/ui/skeleton.tsx (1)

8-11: Consider adding accessibility attributes for screen readers.

While the implementation is solid, consider enhancing accessibility by adding appropriate ARIA attributes to communicate the loading state to assistive technologies.

<div
  className={cn("animate-pulse rounded-md bg-primary/10", className)}
+ aria-hidden="true"
+ role="presentation"
  {...props}
/>

This helps screen readers understand that this is a visual placeholder and not meaningful content.

frontend/src/app/api/admin/features/route.ts (2)

7-34: Consider adding pagination or limits.

Without pagination or limiting the query, large data sets may degrade performance and increase response times. Incorporate skip/limit or pagination parameters to efficiently manage large collections of features.


36-88: Store the creator’s reference for auditing.

Although this logic is sufficient for creating features, it might be helpful to store a reference (e.g., user ID) indicating who created the feature for audit trails and future maintainability.

frontend/src/app/api/admin/users/route.ts (1)

7-61: Add pagination for large user lists.

Similar to the features listing, consider implementing pagination in the GET handler to avoid potential performance bottlenecks when returning large amounts of user data.

frontend/src/app/api/admin/features/[id]/route.ts (3)

8-33: Validate potential slug usage in GET.

Your GET handler already checks both an ObjectId and a slug. If a slug or ID is invalid, consider returning a more detailed error response (e.g., clarifying “Invalid feature ID or slug”) to help with debugging.


36-96: Allow partial updates if needed.

Currently, your PUT handler requires both title and slug. If partial updates are desired or if certain fields aren’t always provided, consider a PATCH-like approach or handle absent fields gracefully.


99-132: Consider a soft-delete or confirmation step.

For the DELETE handler, ensure admins intentionally remove a feature. A soft-delete or a confirmation workflow can help prevent accidental permanent deletion and provide an audit of previously removed features.

Do you want help modifying this to perform soft-deletes instead of permanent removal?

frontend/src/app/api/admin/users/[id]/route.ts (3)

8-44: Consider extracting shared authentication checks into a reusable helper or middleware.
The GET handler (lines 8-44), as well as the subsequent PUT and DELETE handlers, all repeat the same checks for session cookies and permissions. To improve maintainability and reduce duplication, factor these checks into a shared function or middleware.


79-86: Consider adding indexes on username and email to optimize lookups.
Currently, you perform a query to check if a user with the same username or email already exists. For larger datasets, a compound or individual index on these fields can significantly improve performance.


100-101: Avoid using the delete operator for performance and clarity.
Static analysis flagged the delete updateFields.password usage. Instead of deleting the property, skip adding it to the object altogether if it’s not provided, or use a separate update query without $set on the password field to maintain the existing password.

-  delete updateFields.password
+  // Instead of deleting, simply omit the password field from updateFields if not provided.
🧰 Tools
🪛 Biome (1.9.4)

[error] 101-101: Avoid the delete operator which can impact performance.

Unsafe fix: Use an undefined assignment instead.

(lint/performance/noDelete)

frontend/src/app/admin/login/page.tsx (1)

63-71: Optimize authentication redirection logic.

There appears to be redundant authentication check logic across multiple places in the component, potentially causing unnecessary re-renders or conflicting redirects.

Consider consolidating authentication checks:

-useEffect(() => {
-  if (isAuthenticated) {
-    router.push("/admin")
-  }
-}, [isAuthenticated, router])
-
-if (isAuthenticated) {
-  return null
-}
+// The authentication check and redirect is already handled in the 
+// first useEffect (lines 25-41), so this section can be removed
frontend/src/app/features/page.tsx (2)

23-23: Consider pagination for large feature sets.

If more features are added in the future, consider implementing pagination or a more scalable grid system to maintain performance.


36-42: Optimize image loading for better performance.

All feature images use the fill property without specifying priority or loading strategies.

Consider adding priority for above-the-fold images and lazy loading for others:

<Image
  src="/placeholder.svg?height=200&width=400&text=Site+Location"
  alt="Optimal Site Location"
  fill
+  priority={true} // for the first few above-the-fold images
+  loading="lazy" // for below-the-fold images
  className="object-cover rounded-md"
/>

Also applies to: 66-72, 96-102, 126-132, 156-162, 186-192

frontend/src/components/date-range-picker.tsx (2)

16-19: State management looks good, but consider exposing selected dates to parent components.

The component initializes state variables for start and end dates, but doesn't provide a way for parent components to react to date selection changes. Consider adding an onChange callback prop.

interface DatePickerWithRangeProps {
  className?: string
+ onChange?: (dates: { startDate: string; endDate: string }) => void
}

export function DatePickerWithRange({ className, onChange }: DatePickerWithRangeProps) {
  const [startDate, setStartDate] = React.useState<string>("")
  const [endDate, setEndDate] = React.useState<string>("")

+ // Update parent component when dates change
+ React.useEffect(() => {
+   if (onChange) {
+     onChange({ startDate, endDate })
+   }
+ }, [startDate, endDate, onChange])

20-55: Consider adding a way to clear selected dates.

The component allows selecting dates but doesn't provide an intuitive way to clear them once selected.

<PopoverContent className="w-auto p-4" align="start">
  <div className="grid gap-4">
    <div className="grid gap-2">
      <Label htmlFor="start-date">Start Date</Label>
      <Input id="start-date" type="date" value={startDate} onChange={(e) => setStartDate(e.target.value)} />
    </div>
    <div className="grid gap-2">
      <Label htmlFor="end-date">End Date</Label>
      <Input id="end-date" type="date" value={endDate} onChange={(e) => setEndDate(e.target.value)} />
    </div>
+   <Button 
+     variant="outline" 
+     className="mt-2" 
+     onClick={() => {
+       setStartDate("");
+       setEndDate("");
+     }}
+   >
+     Clear Dates
+   </Button>
  </div>
</PopoverContent>
frontend/src/lib/mongodb.ts (1)

26-34: Add connection options for better performance and security.

The MongoDB connection is created without any options, which might not be optimal for production use. Consider adding options for timeouts, connection pooling, and TLS.

- // Connect to the client
- await client.connect()
+ // Connect to the client with options
+ await client.connect({
+   connectTimeoutMS: 10000,
+   socketTimeoutMS: 45000,
+   // Add other options as needed for your use case
+ })
frontend/src/components/ui/popover.tsx (2)

14-30: Consider adding a controlled mode for the Popover component.

The popover component doesn't offer a controlled mode, which limits its flexibility in more complex use cases where you might need to programmatically control its open/closed state.

- const Popover = PopoverPrimitive.Root
+ // Add controlled version of Popover
+ interface PopoverProps extends React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Root> {
+   controlled?: boolean;
+   open?: boolean;
+   onOpenChange?: (open: boolean) => void;
+ }
+ 
+ const Popover = React.forwardRef<
+   React.ElementRef<typeof PopoverPrimitive.Root>,
+   PopoverProps
+ >(({ controlled, open, onOpenChange, ...props }, ref) => {
+   return controlled ? (
+     <PopoverPrimitive.Root
+       open={open}
+       onOpenChange={onOpenChange}
+       {...props}
+     />
+   ) : (
+     <PopoverPrimitive.Root {...props} />
+   );
+ });
+ Popover.displayName = "Popover";

23-26: Allow customization of popover width through props.

The popover has a fixed width of "w-72" (18rem) which might not be suitable for all use cases. Consider making this customizable through props.

const PopoverContent = React.forwardRef<
  React.ElementRef<typeof PopoverPrimitive.Content>,
  React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
->(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
+>(({ className, align = "center", sideOffset = 4, width, ...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 origin-[--radix-popover-content-transform-origin]",
+        "z-50 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 origin-[--radix-popover-content-transform-origin]",
+        width ? width : "w-72",
        className
      )}
      {...props}
    />
  </PopoverPrimitive.Portal>
))
frontend/src/app/api/admin/media/upload/route.ts (3)

28-32: Consider sanitizing the filename or restricting special characters.

Currently, you're taking the raw file.name, only stripping the extension in public_id. For additional security, you may wish to sanitize the user-submitted filename or strip out potentially problematic characters. This helps avoid complications or security exploits linked to unusual filenames.


34-37: Additional image validation for SVG files.

Currently, this check only ensures the file type starts with "image/". Some SVG files might contain embedded scripts or malicious payloads. If SVG uploads are a potential security concern, consider adding further checks or using a sanitizing library specifically for SVG.


69-73: Optional: Prevent or handle duplicate uploads.

If a user attempts to upload the same image multiple times, it will create duplicates in your media collection. Depending on your requirements, you might want to detect duplicate publicIds or identical content (by hashing) to prevent clutter.

frontend/src/app/ClientLayout.tsx (1)

30-32: Enhance user experience with a minimal suspense fallback.

Right now, your Suspense fallback is null. Consider a small loading indicator or placeholder to inform users that the Google Analytics component is being loaded. A simple spinner or progress bar could improve the user experience.

frontend/src/components/site-header.tsx (1)

27-60: Reduce duplication between desktop and mobile nav items.

The same set of nav items are repeated in both desktop and mobile sections. You could reduce repetition by storing these links in an array and iterating over them to render both desktop and mobile views. This improves maintainability and DRY principles.

 // Example approach:
 const NAV_LINKS = [
   { href: "/", label: "HOME" },
   { href: "/map", label: "MAP" },
   ...
 ];

 // Then in desktop navigation:
 {NAV_LINKS.map((link) => (
   <NavigationMenuItem key={link.href}>
     <Link href={link.href} legacyBehavior passHref>
       <NavigationMenuLink className={navigationMenuTriggerStyle()}>
         {link.label}
       </NavigationMenuLink>
     </Link>
   </NavigationMenuItem>
 ))}

 // And similarly in mobile:
 {NAV_LINKS.map((link) => (
   <Link href={link.href} key={link.href} className="text-lg font-medium" onClick={() => setIsMenuOpen(false)}>
     {link.label}
   </Link>
 ))}
frontend/src/app/admin/ai-technologies/new/page.tsx (2)

239-253: Fix Image component implementation and error handling.

The Image component has several issues:

  1. The fill prop without proper parent container styling can cause layout issues
  2. The error handling has an unnecessary semicolon
  3. The fallback logic could be improved
{previewUrl ? (
-  <div className="relative w-full h-full">
+  <div className="relative w-full h-[400px]">
     <Image
       src={previewUrl || "/placeholder.svg?height=400&width=800&text=Preview"}
       alt="Preview"
       fill
       className="object-cover"
       onError={(e) => {
-        ;(e.target as any).src = "/placeholder.svg?height=400&width=800&text=Image+Not+Found"
+        (e.target as HTMLImageElement).src = "/placeholder.svg?height=400&width=800&text=Image+Not+Found"
       }}
     />
   </div>

82-101: Improve error handling for API calls.

The preview API error handling could be more specific. Currently, it logs to console and shows a generic error message regardless of the error type.

Add more detailed error handling:

 if (response.ok) {
   const blob = await response.blob()
   const url = URL.createObjectURL(blob)
   setPreviewUrl(url)
 } else {
-  console.error("Failed to generate preview:", response.statusText)
+  // Extract more specific error information if available
+  let errorMessage = "There was an error generating the preview.";
+  try {
+    const errorData = await response.json();
+    errorMessage = errorData.message || errorMessage;
+  } catch (e) {
+    // If response is not JSON, use status text
+    errorMessage = `Error: ${response.statusText || "Unknown error"}`;
+  }
+  
+  console.error("Failed to generate preview:", errorMessage);
   toast({
     title: "Something went wrong!",
-    description: "There was an error generating the preview.",
+    description: errorMessage,
     variant: "destructive",
   })
 }
frontend/src/lib/cloudinary.ts (3)

3-8: Validate environment variables on startup
These lines configure Cloudinary using environment variables. For a production environment, it’s good practice to verify these variables are set and fail fast with a clear error message if they’re missing. This avoids runtime issues when credentials are not properly provided.


10-41: Ensure robust error handling in uploadImage
The uploadImage function correctly wraps the Cloudinary upload in a Promise. However, you might consider adding more explicit error-handling or logging to help debug cases where Cloudinary returns unexpected results. Unit tests covering success, failure, and edge cases (e.g., empty buffer) would also be beneficial.


49-51: Optional: Guard against missing resources in getImageDetails
While this code is correct, consider returning a more explicit error if the resource isn’t found or if Cloudinary returns any errors, so the caller can handle those scenarios gracefully.

frontend/src/app/admin/media/page.tsx (3)

42-109: Use real API calls or robust mocking
Currently, the fetchMedia function relies on mock data. This is acceptable for a placeholder, but ensure a smooth transition once a real API endpoint is in place. You might also provide fallback handling for partial/missing data to prevent potential runtime errors if the actual API returns unexpected shapes.


129-164: Consider a custom confirmation pattern
Using the native confirm dialog can be disruptive to user flow. A custom confirmation modal or toast-based approach can provide a more consistent user experience.


476-553: Potential extension: additional edit capabilities
The media details panel nicely displays file information. If future requirements allow for file renaming, updating metadata, or re-uploading an updated version, consider adding an edit flow here. A small “Edit” button or a separate form can improve usability.

frontend/src/app/api/admin/media/route.ts (1)

39-86: Verify Cloudinary and MongoDB deletion states
The DELETE handler properly checks permissions and removes the item. However, you might want to verify the result from deleteImage() to ensure Cloudinary successfully removed the resource (e.g., check for result.result === "ok") before finalizing the MongoDB deletion. This can maintain data consistency.

frontend/src/app/api/admin/media/[id]/route.ts (1)

29-70: Authorization logic and cleanup look sound.
The DELETE handler properly checks user permissions, removes the item from both Cloudinary and MongoDB, and returns a success message. As an enhancement, consider verifying whether the user is still active or has additional roles before deletion if that makes sense for your application’s security model.

frontend/src/app/map/page.tsx (4)

11-16: State management for map configuration.
Storing mapType, pollutantType, timeRange, and legend visibility is a solid approach. If you intend to allow URL bookmarking or shareable links, consider persisting these states in the query string.


18-124: Comprehensive map controls sidebar.
The multiple <Select> components and toggles provide a handy UI for filtering and customizing the map. If you notice common patterns, consider extracting a custom hook or component to reduce redundancy in the future.


125-170: Clear map placeholder block.
This placeholder approach is fine for development. If you wish to integrate a real map service (e.g., Leaflet, Mapbox, Google Maps), a dedicated component would keep this page more maintainable.

Would you like help scaffolding a Leaflet-based component?


173-285: Organized and informative data insights section.
Breaking these insights into separate functional components or modules could simplify future maintenance, particularly if you add more detail or interactivity. The current structure is very readable and user-friendly.

frontend/src/components/admin/media-upload.tsx (4)

44-45: Consider cleaning up the object URL to prevent memory leaks.
When setting a preview using URL.createObjectURL(file), it’s a good practice to revoke that object URL when it’s no longer needed or when the component unmounts. Repeatedly creating new object URLs without revoking old ones can accumulate in memory over time.


72-73: Avoid console statements in production code.
While console logs are useful for debugging, consider using a proper logging library or removing them in production builds to avoid leaking sensitive data.


98-109: Refine fallback logic in getImageUrl.
Your getImageUrl function checks for basic placeholder conditions. Consider making the fallback more dynamic, such as using a common default image for any invalid URL. This can enhance user experience if the retrieved URL is invalid or broken.


20-213: Add test coverage.
Given the complexity of file uploads, error handling, and previews, adding unit or integration tests can ensure the component remains reliable under various scenarios.

frontend/src/app/reports/page.tsx (2)

15-18: Assess suitability of local state versus global state.
If date, reportType, or location need to persist across routes or be shared by other components, consider a shared store (e.g., Redux or local context) for better maintainability.


129-231: Extract repeating UI blocks into reusable components.
Large sections of repetitive markup (e.g., summary, trends, pollutants, locations) can be moved into separate components for modularity. This approach will reduce duplication and improve maintainability.

frontend/src/app/page.tsx (2)

11-27: Hero section is well-structured and visually appealing.

The gradient background, bold headings, and clear call-to-action buttons nicely direct user attention to the key features of the platform.

Consider factoring out the repeated style classes into a shared utility or theme file for easier maintenance.


44-168: Main features section showcases new UI components effectively.

Your approach using the <Card> component for each feature is cohesive and visually consistent. The descriptive headers, icons, and calls-to-action for each card make the UX intuitive.

You could break out each feature into a small subcomponent for reuse in other sections or future pages, improving maintainability.

frontend/src/app/categorize/page.tsx (2)

14-26: Async simulation is straightforward but lacks error handling.

Your handleAnalyze function uses a timeout to simulate an API call, and sets an analyzing/loading state. In a real scenario, consider adding error handling and success/failure states to manage unexpected responses or network issues.

  const handleAnalyze = async () => {
    setIsAnalyzing(true)
    try {
-     setTimeout(() => {
-       setIsAnalyzing(false)
-       setResultsReady(true)
-     }, 2000)
+     // Example pseudo code:
+     const response = await fetch("/api/analyze", { ... })
+     if (!response.ok) throw new Error("Analysis failed")
+     // ...
+     setResultsReady(true)
    } catch (err) {
+     console.error(err)
      // Handle error
    } finally {
      setIsAnalyzing(false)
    }
  }

74-145: Input panel design is comprehensive.

Collecting pollutant data in one card is cohesive, and the immediate user feedback (enabled/disabled button) is a plus. Consider validating input ranges (e.g., no negative values) for more robust user input handling.

frontend/src/app/about/page.tsx (2)

19-53: ‘Our Story’ section is clear, but consider chunking paragraphs.

Long paragraphs can be harder to read. Small breakpoints or bullet points might improve readability for certain audiences.


162-230: Team section is visually appealing with the card-like structure.

Displaying team members with images and short bios fosters trust. Consider lazy-loading images if performance needs optimization.

frontend/src/components/ui/form.tsx (2)

15-18: Consider renaming for clarity.

You're re-exporting FormProvider as Form. This is convenient, but it could be confusing if other parts of the codebase or library references the term "Form" differently. Consider naming it FormProvider for clearer distinction.

-const Form = FormProvider
+const FormProviderWrapper = FormProvider

75-86: Be mindful of React SSR with React.useId().

React.useId() helps with unique ID generation, but can vary between server and client under certain SSR scenarios. If these IDs must persist across renders, consider allowing a prop-based ID override for advanced SSR setups.

frontend/src/components/ui/navigation-menu.tsx (1)

8-23: Watch out for potential stacking context issues.

z-10 and absolute positioning with a dynamic dropdown can sometimes compete with elements like modals or overlays. If you notice layering conflicts, consider controlling the stacking context more tightly, possibly via a portal or higher z-index.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 86a5d61 and b877caf.

⛔ Files ignored due to path filters (1)
  • frontend/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (39)
  • frontend/next.config.js (1 hunks)
  • frontend/package.json (2 hunks)
  • frontend/src/app/ClientLayout.tsx (1 hunks)
  • frontend/src/app/about/page.tsx (1 hunks)
  • frontend/src/app/admin/ai-technologies/new/page.tsx (1 hunks)
  • frontend/src/app/admin/features/new/page.tsx (1 hunks)
  • frontend/src/app/admin/login/page.tsx (1 hunks)
  • frontend/src/app/admin/media/page.tsx (1 hunks)
  • frontend/src/app/admin/media/upload/page.tsx (1 hunks)
  • frontend/src/app/api/admin/auth/login/route.ts (1 hunks)
  • frontend/src/app/api/admin/features/[id]/route.ts (1 hunks)
  • frontend/src/app/api/admin/features/route.ts (1 hunks)
  • frontend/src/app/api/admin/media/[id]/route.ts (1 hunks)
  • frontend/src/app/api/admin/media/route.ts (1 hunks)
  • frontend/src/app/api/admin/media/upload/route.ts (1 hunks)
  • frontend/src/app/api/admin/users/[id]/route.ts (1 hunks)
  • frontend/src/app/api/admin/users/route.ts (1 hunks)
  • frontend/src/app/categorize/page.tsx (1 hunks)
  • frontend/src/app/contact/page.tsx (1 hunks)
  • frontend/src/app/features/page.tsx (1 hunks)
  • frontend/src/app/features/site-location/page.tsx (1 hunks)
  • frontend/src/app/layout.tsx (1 hunks)
  • frontend/src/app/locate/page.tsx (1 hunks)
  • frontend/src/app/map/page.tsx (1 hunks)
  • frontend/src/app/metadata.ts (1 hunks)
  • frontend/src/app/page.tsx (1 hunks)
  • frontend/src/app/placeholder.svg/route.ts (1 hunks)
  • frontend/src/app/reports/page.tsx (1 hunks)
  • frontend/src/components/admin/media-upload.tsx (1 hunks)
  • frontend/src/components/date-range-picker.tsx (1 hunks)
  • frontend/src/components/site-footer.tsx (1 hunks)
  • frontend/src/components/site-header.tsx (1 hunks)
  • frontend/src/components/ui/form.tsx (1 hunks)
  • frontend/src/components/ui/navigation-menu.tsx (1 hunks)
  • frontend/src/components/ui/popover.tsx (1 hunks)
  • frontend/src/components/ui/skeleton.tsx (1 hunks)
  • frontend/src/components/ui/slider.tsx (1 hunks)
  • frontend/src/lib/cloudinary.ts (1 hunks)
  • frontend/src/lib/mongodb.ts (1 hunks)
✅ Files skipped from review due to trivial changes (2)
  • frontend/next.config.js
  • frontend/src/app/metadata.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • frontend/src/app/admin/features/new/page.tsx
  • frontend/src/app/api/admin/auth/login/route.ts
🧰 Additional context used
🧬 Code Definitions (18)
frontend/src/app/admin/media/upload/page.tsx (6)
frontend/src/components/ui/card.tsx (6)
  • Card (76-76)
  • CardHeader (76-76)
  • CardTitle (76-76)
  • CardDescription (76-76)
  • CardContent (76-76)
  • CardFooter (76-76)
frontend/src/components/ui/label.tsx (1)
  • Label (26-26)
frontend/src/components/ui/input.tsx (1)
  • Input (22-22)
frontend/src/components/ui/textarea.tsx (1)
  • Textarea (22-22)
frontend/src/components/ui/select.tsx (5)
  • Select (149-149)
  • SelectTrigger (152-152)
  • SelectValue (151-151)
  • SelectContent (153-153)
  • SelectItem (155-155)
frontend/src/components/ui/button.tsx (1)
  • Button (57-57)
frontend/src/components/date-range-picker.tsx (1)
frontend/src/components/ui/popover.tsx (3)
  • Popover (33-33)
  • PopoverTrigger (33-33)
  • PopoverContent (33-33)
frontend/src/app/api/admin/media/upload/route.ts (5)
frontend/src/app/api/admin/features/route.ts (1)
  • POST (37-88)
frontend/src/app/api/admin/users/route.ts (1)
  • POST (64-129)
frontend/src/app/api/admin/auth/logout/route.ts (1)
  • POST (4-14)
frontend/src/lib/cloudinary.ts (1)
  • uploadImage (11-41)
frontend/src/lib/mongodb.ts (1)
  • getCollection (40-43)
frontend/src/components/site-header.tsx (1)
frontend/src/components/ui/navigation-menu.tsx (5)
  • NavigationMenu (120-120)
  • NavigationMenuList (121-121)
  • NavigationMenuItem (122-122)
  • NavigationMenuLink (125-125)
  • navigationMenuTriggerStyle (119-119)
frontend/src/app/admin/ai-technologies/new/page.tsx (7)
frontend/src/hooks/use-toast.ts (2)
  • useToast (194-194)
  • toast (194-194)
frontend/src/components/ui/card.tsx (5)
  • Card (76-76)
  • CardHeader (76-76)
  • CardTitle (76-76)
  • CardDescription (76-76)
  • CardContent (76-76)
frontend/src/components/ui/form.tsx (6)
  • Form (171-171)
  • FormField (177-177)
  • FormItem (172-172)
  • FormLabel (173-173)
  • FormControl (174-174)
  • FormMessage (176-176)
frontend/src/components/ui/input.tsx (1)
  • Input (22-22)
frontend/src/components/ui/textarea.tsx (1)
  • Textarea (22-22)
frontend/src/components/ui/button.tsx (1)
  • Button (57-57)
frontend/src/components/ui/skeleton.tsx (1)
  • Skeleton (15-15)
frontend/src/app/api/admin/media/[id]/route.ts (4)
frontend/src/app/api/admin/media/route.ts (2)
  • GET (9-36)
  • DELETE (39-86)
frontend/src/app/api/admin/auth/check/route.ts (1)
  • GET (4-42)
frontend/src/lib/mongodb.ts (1)
  • getCollection (40-43)
frontend/src/lib/cloudinary.ts (1)
  • deleteImage (44-46)
frontend/src/app/page.tsx (2)
frontend/src/components/ui/button.tsx (1)
  • Button (57-57)
frontend/src/components/ui/card.tsx (6)
  • Card (76-76)
  • CardHeader (76-76)
  • CardTitle (76-76)
  • CardDescription (76-76)
  • CardContent (76-76)
  • CardFooter (76-76)
frontend/src/app/locate/page.tsx (7)
frontend/src/components/ui/card.tsx (6)
  • Card (76-76)
  • CardHeader (76-76)
  • CardTitle (76-76)
  • CardDescription (76-76)
  • CardContent (76-76)
  • CardFooter (76-76)
frontend/src/components/ui/select.tsx (5)
  • Select (149-149)
  • SelectTrigger (152-152)
  • SelectValue (151-151)
  • SelectContent (153-153)
  • SelectItem (155-155)
frontend/src/components/ui/slider.tsx (1)
  • Slider (28-28)
frontend/src/components/ui/input.tsx (1)
  • Input (22-22)
frontend/src/components/ui/switch.tsx (1)
  • Switch (29-29)
frontend/src/components/ui/button.tsx (1)
  • Button (57-57)
frontend/src/components/ui/tabs.tsx (4)
  • Tabs (55-55)
  • TabsList (55-55)
  • TabsTrigger (55-55)
  • TabsContent (55-55)
frontend/src/app/features/site-location/page.tsx (3)
frontend/src/components/ui/button.tsx (1)
  • Button (57-57)
frontend/src/lib/types.ts (2)
  • Site (134-148)
  • Location (1-4)
frontend/src/components/ui/card.tsx (2)
  • Card (76-76)
  • CardContent (76-76)
frontend/src/app/reports/page.tsx (8)
frontend/src/ui/card.tsx (5)
  • Card (79-79)
  • CardHeader (79-79)
  • CardTitle (79-79)
  • CardDescription (79-79)
  • CardContent (79-79)
frontend/src/components/ui/select.tsx (5)
  • Select (149-149)
  • SelectTrigger (152-152)
  • SelectValue (151-151)
  • SelectContent (153-153)
  • SelectItem (155-155)
frontend/src/components/date-range-picker.tsx (1)
  • DatePickerWithRange (16-57)
frontend/src/components/ui/popover.tsx (3)
  • Popover (33-33)
  • PopoverTrigger (33-33)
  • PopoverContent (33-33)
frontend/src/components/ui/button.tsx (1)
  • Button (57-57)
frontend/src/lib/utils.ts (1)
  • cn (4-6)
frontend/src/components/ui/input.tsx (1)
  • Input (22-22)
frontend/src/components/ui/tabs.tsx (4)
  • Tabs (55-55)
  • TabsList (55-55)
  • TabsTrigger (55-55)
  • TabsContent (55-55)
frontend/src/app/api/admin/users/[id]/route.ts (2)
frontend/src/app/api/admin/users/route.ts (1)
  • GET (7-61)
frontend/src/lib/mongodb.ts (1)
  • getCollection (40-43)
frontend/src/app/layout.tsx (5)
frontend/src/app/metadata.ts (1)
  • metadata (3-6)
frontend/src/hooks/use-auth.tsx (1)
  • AuthProvider (19-135)
frontend/src/components/site-header.tsx (1)
  • SiteHeader (15-118)
frontend/src/components/site-footer.tsx (1)
  • SiteFooter (6-134)
frontend/src/components/ui/toaster.tsx (1)
  • Toaster (13-35)
frontend/src/app/about/page.tsx (3)
frontend/src/components/ui/card.tsx (5)
  • Card (76-76)
  • CardHeader (76-76)
  • CardTitle (76-76)
  • CardDescription (76-76)
  • CardContent (76-76)
frontend/src/ui/button.tsx (1)
  • Button (56-56)
frontend/src/components/ui/button.tsx (1)
  • Button (57-57)
frontend/src/app/admin/media/page.tsx (8)
frontend/src/hooks/use-toast.ts (2)
  • useToast (194-194)
  • toast (194-194)
frontend/src/hooks/use-auth.tsx (1)
  • useAuth (137-143)
frontend/src/lib/auth.ts (1)
  • canEditContent (42-45)
frontend/src/components/ui/tabs.tsx (4)
  • Tabs (55-55)
  • TabsList (55-55)
  • TabsTrigger (55-55)
  • TabsContent (55-55)
frontend/src/components/ui/input.tsx (1)
  • Input (22-22)
frontend/src/components/admin/media-upload.tsx (1)
  • MediaUpload (20-213)
frontend/src/components/ui/button.tsx (1)
  • Button (57-57)
frontend/src/components/ui/label.tsx (1)
  • Label (26-26)
frontend/src/app/api/admin/features/route.ts (2)
frontend/src/app/api/admin/features/[id]/route.ts (1)
  • GET (8-33)
frontend/src/lib/mongodb.ts (1)
  • getCollection (40-43)
frontend/src/app/api/admin/media/route.ts (2)
frontend/src/lib/mongodb.ts (1)
  • getCollection (40-43)
frontend/src/lib/cloudinary.ts (1)
  • deleteImage (44-46)
frontend/src/app/admin/login/page.tsx (6)
frontend/src/hooks/use-auth.tsx (1)
  • useAuth (137-143)
frontend/src/components/ui/card.tsx (6)
  • Card (76-76)
  • CardHeader (76-76)
  • CardTitle (76-76)
  • CardDescription (76-76)
  • CardContent (76-76)
  • CardFooter (76-76)
frontend/src/components/ui/alert.tsx (2)
  • Alert (59-59)
  • AlertDescription (59-59)
frontend/src/components/ui/label.tsx (1)
  • Label (26-26)
frontend/src/components/ui/input.tsx (1)
  • Input (22-22)
frontend/src/components/ui/button.tsx (1)
  • Button (57-57)
frontend/src/app/categorize/page.tsx (8)
frontend/src/components/ui/card.tsx (6)
  • Card (76-76)
  • CardHeader (76-76)
  • CardTitle (76-76)
  • CardDescription (76-76)
  • CardContent (76-76)
  • CardFooter (76-76)
frontend/src/components/ui/label.tsx (1)
  • Label (26-26)
frontend/src/components/ui/select.tsx (5)
  • Select (149-149)
  • SelectTrigger (152-152)
  • SelectValue (151-151)
  • SelectContent (153-153)
  • SelectItem (155-155)
frontend/src/components/ui/input.tsx (1)
  • Input (22-22)
frontend/src/components/ui/button.tsx (1)
  • Button (57-57)
frontend/src/components/ui/tabs.tsx (4)
  • Tabs (55-55)
  • TabsList (55-55)
  • TabsTrigger (55-55)
  • TabsContent (55-55)
frontend/src/components/ui/badge.tsx (1)
  • Badge (36-36)
frontend/src/components/ui/table.tsx (6)
  • Table (112-112)
  • TableHeader (113-113)
  • TableRow (117-117)
  • TableHead (116-116)
  • TableBody (114-114)
  • TableCell (118-118)
🪛 Biome (1.9.4)
frontend/src/app/page.tsx

[error] 5-5: Do not shadow the global "Map" property.

Consider renaming this variable. It's easy to confuse the origin of variables when they're named after a known global.

(lint/suspicious/noShadowRestrictedNames)

frontend/src/app/api/admin/users/[id]/route.ts

[error] 101-101: Avoid the delete operator which can impact performance.

Unsafe fix: Use an undefined assignment instead.

(lint/performance/noDelete)

🔇 Additional comments (70)
frontend/src/components/ui/skeleton.tsx (2)

1-15: Well-structured Skeleton UI component implementation.

This is a clean implementation of a Skeleton loading component that follows React best practices. The component properly:

  • Uses TypeScript for type safety with React.HTMLAttributes
  • Leverages the cn utility for className composition
  • Implements a lightweight, flexible design pattern that can be reused throughout the application
  • Exports the component with a named export for clean imports

The animation and styling choices (animate-pulse, rounded-md, bg-primary/10) align with modern UI loading indicators, creating a subtle pulsing effect that indicates content is loading without being distracting.


3-6: Clean prop destructuring pattern.

The component follows the recommended pattern for handling props in functional components, specifically destructuring className while collecting remaining props with the spread operator.

frontend/src/app/api/admin/features/route.ts (1)

7-34: Restrict GET route if necessary.

Currently, the GET route is open and does not perform authentication checks, even though it resides in an admin endpoint. If this route should be protected, consider enforcing authentication or permission checks to prevent unauthorized data access.

If this is intended to be publicly accessible, please confirm it aligns with the security requirements for admin features.

frontend/src/app/api/admin/users/[id]/route.ts (1)

127-166: The DELETE handler logic is well-structured and correct.
The check preventing a user from deleting their own account is a nice safeguard, and overall the handler properly handles error cases. No concerns here.

frontend/src/app/admin/login/page.tsx (5)

135-144: Remove hardcoded demo credentials from production code.

Hardcoded credentials shouldn't be included in production code, even as demo credentials. This could pose a security risk and allows anyone to access the admin panel with these credentials.

Consider conditionally rendering these credentials only in development mode:

<div className="w-full">
  <p className="mb-2">Protected area. Unauthorized access is prohibited.</p>
-  <p className="text-xs text-blue-600">
-    For demo purposes, use:
-    <br />
-    <strong>admin</strong> / <strong>password123</strong> (Admin)
-    <br />
-    <strong>editor</strong> / <strong>editor123</strong> (Editor)
-    <br />
-    <strong>viewer</strong> / <strong>viewer123</strong> (Viewer)
-  </p>
+  {process.env.NODE_ENV === 'development' && (
+    <p className="text-xs text-blue-600">
+      For demo purposes, use:
+      <br />
+      <strong>admin</strong> / <strong>password123</strong> (Admin)
+      <br />
+      <strong>editor</strong> / <strong>editor123</strong> (Editor)
+      <br />
+      <strong>viewer</strong> / <strong>viewer123</strong> (Viewer)
+    </p>
+  )}
</div>

15-22: LGTM! Clean state management for the login form.

The component properly initializes state variables for form inputs, loading states, and error handling. The authentication context is correctly accessed via the useAuth hook.


43-61: LGTM! Well-structured form submission handler.

The form submission logic is well-implemented with proper error handling, loading state management, and user feedback.


73-79: LGTM! Good UX with loading indicator.

The loading indicator provides clear visual feedback during authentication checks, enhancing user experience.


89-95: LGTM! Proper error display for user feedback.

The error alert is well-implemented, providing clear feedback to users when login fails.

frontend/src/app/placeholder.svg/route.ts (3)

1-37: LGTM! Well-implemented dynamic SVG generation route.

This implementation creates a flexible SVG placeholder generator with proper parameter handling, sensible defaults, and security measures.


25-25: Good security practice with text sanitization.

The text sanitization prevents XML injection attacks by properly escaping special characters.


32-35: LGTM! Appropriate headers for SVG response.

The Content-Type is correctly set, and the caching strategy is well-configured for static resources.

frontend/src/components/ui/slider.tsx (3)

8-26: LGTM! Well-implemented Slider component with Radix UI.

The component is properly implemented with React.forwardRef, maintains accessibility attributes, and has appropriate styling for different states.


20-22: LGTM! Good implementation of track and range.

The slider track and range implementations follow best practices for creating accessible slider components.


23-23: LGTM! Comprehensive styling for the slider thumb.

The thumb element includes appropriate styling for different states (focus, disabled) and maintains accessibility.

frontend/src/app/features/page.tsx (2)

7-224: LGTM! Well-structured features page with consistent design.

The page has a clear structure with hero section, features grid, and call-to-action, all following a consistent design pattern.


37-42:

Details

❓ Verification inconclusive

Verify placeholder images will be replaced.

The component uses the placeholder.svg route for images. Ensure these will be replaced with actual feature images before production release.


🏁 Script executed:

#!/bin/bash
# Check if there are actual images in the public directory that could replace these placeholders
fd -t f -e jpg -e png -e svg -e webp . public | grep -v placeholder

Length of output: 163


Action: Confirm image replacement before production release

The image component in frontend/src/app/features/page.tsx (lines 37–42) still uses /placeholder.svg. The automated check attempted to locate non-placeholder images failed because it couldn’t find a public directory—this may mean either the correct directory isn’t being searched or the actual images aren’t yet in place. Please perform a manual verification to ensure that the placeholder graphic is replaced with the actual site image assets before the production release.

frontend/src/app/admin/media/upload/page.tsx (1)

19-25: Add validation for file size and type before preview.

The file handling lacks validation for file size and type restrictions, which could lead to errors or unnecessary resource usage when attempting to upload invalid files.

frontend/src/lib/mongodb.ts (1)

14-37: Add error handling to the database connection flow.

The function lacks error handling during the database connection process, which could lead to uncaught exceptions.

frontend/src/components/ui/popover.tsx (1)

1-33: LGTM: Well-structured component with proper accessibility support.

The Popover component is well implemented using Radix UI primitives, which provides good accessibility support out of the box. The component follows best practices for React component design, including proper use of forwardRef, displayName, and default props.

frontend/src/app/api/admin/media/upload/route.ts (2)

14-16: Ensure CSRF protection or relevant security measures.

While the authentication check for the session cookie looks solid, consider verifying that this POST request includes the necessary CSRF tokens or other anti-forgery methods to safeguard against potential CSRF attacks.

Do you confirm you have a site-wide CSRF protection strategy for admin routes? If not, I'd be happy to help you set one up.


1-81: Overall logic is well-structured.

The file is well laid out with authentication checks, file validation, Cloudinary integration, and database insertion. Good job handling errors with descriptive messages and returning appropriate status codes when issues occur.

frontend/src/app/ClientLayout.tsx (1)

1-53: Layout approach is correct.

Your approach to ensure client-side rendering is straightforward and neat. Marking this file as "use client" and using an isClient state to avoid hydration mismatches is an acceptable pattern.

frontend/src/components/site-header.tsx (3)

65-70: Mobile menu toggle looks good.

You use an accessible button with a clear aria-label, which is a good practice. The toggling logic with useState is also straightforward and easy to understand.


72-80: Kudos for neat CTA design.

The "Log In" and "Sign Up" buttons use distinct variant styles, making them visually distinguishable. This enhances the user experience on larger screens.


83-115: Mobile menu implementation is clear and user-friendly.

Nice job providing easy navigation for mobile users. The flexible approach with Tailwind classes for layout and spacing ensures a cohesive style.

frontend/src/app/contact/page.tsx (4)

1-6: Clean imports with appropriate component selection.

The imports are well-organized and include all necessary UI components from what appears to be a shadcn/UI library along with appropriate icons from lucide-react.


100-103: Replace placeholder emails with actual customer service emails.

The email addresses listed appear to be placeholders. Ensure they're replaced with actual customer support email addresses before deployment.


114-116: Replace placeholder phone numbers with actual business contact numbers.

The phone numbers used are clearly placeholders (+1 (555) 123-4567). Make sure to update them with the actual business phone numbers before deploying.


127-135: Replace placeholder address with actual business address.

The office address appears to be a placeholder. Update it with the actual physical address of the business before deploying.

frontend/src/components/site-footer.tsx (3)

18-33: Update social media links with actual profiles.

The social media links currently point to the main domains (facebook.com, twitter.com, etc.) rather than specific company profiles.

Replace these with actual company profile URLs:

-<Link href="https://facebook.com" className="text-gray-400 hover:text-white">
+<Link href="https://facebook.com/airquality" className="text-gray-400 hover:text-white">
   <Facebook size={20} />
   <span className="sr-only">Facebook</span>
 </Link>
-<Link href="https://twitter.com" className="text-gray-400 hover:text-white">
+<Link href="https://twitter.com/airquality" className="text-gray-400 hover:text-white">
   <Twitter size={20} />
   <span className="sr-only">Twitter</span>
 </Link>

73-97:

Details

❓ Verification inconclusive

Verify that Feature Links paths correspond to actual routes.

The Features section includes paths like "/features/site-location", "/features/forecasting", etc. Ensure these routes are properly implemented in the Next.js application.


🏁 Script executed:

#!/bin/bash
# Check if the referenced feature routes exist in the application
echo "Checking for feature route files in the pages/app directory..."
fd "^features/(site-location|forecasting|data-analysis|ai-technologies|health-impact)(/|.tsx|.jsx|.js)$" frontend/src/app frontend/src/pages

Length of output: 342


Feature Routes Verification Update
It appears that the verification command is including a directory (frontend/src/pages) that doesn't exist in our current project structure. Please ensure that the feature routes (i.e., /features/site-location, /features/forecasting, /features/data-analysis, /features/ai-technologies, and /features/health-impact) are correctly implemented under the frontend/src/app directory. Consider updating your verification command to search only within frontend/src/app. Here are a few points to address:

  • Directory Check: Remove or adjust the reference to frontend/src/pages since it’s not present.
  • Route Validation: Confirm that all the intended feature route files (or folders if using nested routing) exist in frontend/src/app and properly map to the paths used in the footer.

41-65:

Details

❓ Verification inconclusive

Verify that Quick Links paths correspond to actual routes.

The Quick Links section includes paths like "/features", "/about", "/contact", and "/blog". Ensure these routes are properly implemented in the Next.js application.


🏁 Script executed:

#!/bin/bash
# Check if the referenced routes exist in the application
echo "Checking for route files in the pages/app directory..."
fd "^(features|about|contact|blog)(/|.tsx|.jsx|.js)$" frontend/src/app frontend/src/pages

Length of output: 275


Attention: Verify Quick Links Route Files

The Quick Links in frontend/src/components/site-footer.tsx reference paths such as /features, /about, /contact, and /blog. It looks like the project now uses Next.js 13’s new app directory structure, as there is no frontend/src/pages directory. Please ensure that for each of these routes there’s a corresponding route file in the frontend/src/app directory (e.g., frontend/src/app/features/page.tsx, frontend/src/app/about/page.tsx, etc.).

  • Check that the /features, /about, /contact, and /blog routes are correctly implemented under frontend/src/app.
  • Update either the routing files or the Quick Links if any route is missing or misconfigured.
frontend/src/lib/cloudinary.ts (1)

43-46: deleteImage function looks clear
This function is straightforward and clearly indicates the intent to remove media from Cloudinary by public ID. No immediate concerns here.

frontend/src/app/admin/media/page.tsx (2)

195-206: Duplicate suggestion: detect media type dynamically
Currently, the code sets type: "image/jpeg" for new media items. This matches a previous reviewer comment suggesting a helper function to accurately detect file type (e.g., based on file extension).


269-364: Duplicate suggestion: reduce tab-related duplication
The layout and logic in the "All" and "Images" tabs share a large portion of code. Refer to past suggestions to extract common components and avoid duplicating UI blocks.

Also applies to: 366-462

frontend/src/app/api/admin/media/[id]/route.ts (2)

1-7: Smooth import and configuration setup.
The key imports for database interaction (getCollection), Cloudinary operations (deleteImage), and authentication are in place. This is a solid foundation for the subsequent GET and DELETE handlers.


9-26: Consider validating the id and sanitizing the returned data.
While the try/catch block catches errors, an invalid or malformed id might lead to a 500 response. You could handle this more gracefully by returning a 400 for an invalid ID before attempting the database query. Furthermore, if the mediaItem includes sensitive fields, you might want to selectively return only safe properties.

frontend/src/app/layout.tsx (3)

1-8: Good import structure and global styling.
The layout leverages Inter font, site header/footer, toaster notifications, and authentication provider. This is a tidy starting point for a cohesive user interface.


10-13: Inter font configuration is appropriate.
Setting up the font with subsets and the custom CSS variable is clear and maintainable.


27-35: Well-structured RootLayout component.
Wrapping the entire page in <AuthProvider> ensures consistent authentication context. The usage of <Toaster /> just below the main layout is convenient for global notifications. Nicely done.

frontend/src/app/map/page.tsx (1)

3-10: Logical imports and initial setup.
Using useState for local map settings and bringing in UI components from the shared library keeps this file clean and consistent.

frontend/src/components/admin/media-upload.tsx (1)

52-55: Verify correctness of the upload endpoint.
Ensure that "/api/admin/media/upload" is the intended and secure endpoint for handling media uploads. Consider validating file size/type on the server to mitigate security risks such as oversized or malicious file uploads.

frontend/src/app/reports/page.tsx (1)

97-101: Implement or clarify "Generate Report" functionality.
The “Generate Report” button currently takes no action, which may confuse users. Either implement the logic behind generating reports or remove the button until the feature is ready.

frontend/src/app/features/site-location/page.tsx (4)

14-23: Effective hero section.
The introductory heading, descriptive text, and call-to-action button form a strong opening. Visually, this is clear and encourages user engagement.


49-82: Great layout for the "How It Works" steps.
Presenting the process in three concise steps with individual cards is intuitive. This structure fosters a clear understanding of the workflow without clutter.


87-147: Key benefits section well-articulated.
This section effectively highlights the advantages, using distinct bullet points with icons for clarity. It succinctly portrays the tool’s strongest features.


165-233: Useful cross-linking to related features.
Providing direct links to complementary functionalities within the same ecosystem is a practical way to increase feature adoption. The card-based layout keeps the design cohesive.

frontend/src/app/page.tsx (3)

7-7: Export default function naming is consistent and clear.

Renaming the component to HomePage improves clarity for the top-level page component in Next.js. This is a good naming choice that aligns with Next.js conventions.


30-40: Effective use of a placeholder image with next/image.

The usage of fill and object-cover is consistent with Next.js best practices, ensuring responsive behavior. Keep in mind potential performance improvements by lazy-loading below-the-fold images if needed.


170-180: Centered call-to-action is user-friendly and straightforward.

Encouraging users to sign up or get started aligns with growth objectives, and the large button is prominent on the page. Looks good!

frontend/src/app/categorize/page.tsx (6)

3-11: Imports and initial state management look fine.

The chosen UI components provide a consistent, modern interface. Keep an eye on any potential duplication of similar states if you decide to expand functionality.


28-45: getBadgeVariant logic properly maps categories to badge variants.

Your categorization approach appears sufficient for the known categories. Ensure any extended categories or language localizations are handled in other parts of the codebase.


47-61: getBadgeIcon function is concise and effective.

Returning different icons for each category personalizes the UI. This approach is clean and straightforward.


63-72: Header structure is consistent with other pages in the project.

Using a minimal heading and subheading to orient users at the start improves the page’s usability.


146-459: Tabs and dynamic content are neatly organized.

Splitting results, details, and health implications into tabs helps keep the UI clean. Each tab’s content is well labeled. Great job employing TabsContent for structured data display.


463-564: Standards information section is very informative.

Providing context for WHO, US EPA, and EU standards improves transparency and user understanding. This is a solid approach to educating end users on different guidelines.

frontend/src/app/about/page.tsx (5)

1-4: Imports appear properly organized.

Good job referencing only the UI components in use, which keeps the file tidy and lowers potential bundle size.


6-17: Hero section styling is cohesive with the rest of the site.

It effectively communicates your mission. Ensure that the text is updated if your branding or mission statement changes over time.


55-160: ‘Our Technology’ layout is well-structured and consistent.

Using a grid to display your core technological pillars is effective. Ensure consistent usage of SVG icons if any external icon sets are introduced in the future.


232-288: Partner logos are showcased effectively.

Listing recognizable brands or names can build credibility. Also consider link redirects to partner sites if relevant.


290-307: Call to action nicely closes out the page.

Offering direct routes to contact or career pages helps with user engagement.

frontend/src/components/ui/form.tsx (2)

31-42: Context-based controller is well-structured.

Wrapping Controller with a provider here is a neat choice, as it cleanly shares form field context across child components. This approach is straightforward and aligns well with the React context pattern.


145-167: Returning null for empty messages is appropriate.

The FormMessage component checks body and returns null if empty. This cleanly avoids rendering empty markup. Functional approach and good for accessibility as well.

frontend/src/components/ui/navigation-menu.tsx (3)

43-45: CVA usage is clear and concise.

The navigationMenuTriggerStyle approach using class-variance-authority is well-structured. It keeps your styles modular and easy to extend. No concerns, looks great.


100-116: Indicator animations are well-handled.

Your approach to animate indicator visibility is straightforward and aligned with Radix UI guidelines. The fade-in/fade-out transitions appear consistent without cluttering the code.


82-99:

Details

❓ Verification inconclusive

Verify responsive behavior for the viewport container.

The absolute positioning and flex centering can sometimes behave unexpectedly on very narrow or wide screens. Recommend a quick check across multiple breakpoints to confirm the menu adjusts gracefully.


🏁 Script executed:

#!/bin/bash
# Grep for related media queries or styling usage in the code.
rg -A 3 "NavigationMenuViewport"

Length of output: 1222


Responsive Behavior Verification:
The NavigationMenuViewport component already leverages responsive styling (e.g., using md:w-[var(--radix-navigation-menu-viewport-width)]) to adjust its width at the medium breakpoint. However, given its absolute positioning and flex-based centering, please double-check its rendering on both very narrow and extremely wide screens to ensure it maintains the intended layout without unexpected misalignments.

  • Verify that the menu viewport aligns correctly across all breakpoints.
  • Confirm no adverse effects from the absolute positioning on extreme screen widths.
frontend/package.json (3)

59-59: Tailwind CSS upgraded to ^3.4.17.

Tailwind updates can introduce new configuration requirements or break changes. Double-check your tailwind.config.js for any deprecated or changed settings and confirm your existing styles remain intact.


12-22:

Details

✅ Verification successful

Confirm necessity of new Radix UI packages.

You've added several Radix UI components (@radix-ui/react-dropdown-menu, @radix-ui/react-label, etc.). Ensure they’re all required, as multiple overlapping dependencies may lead to code bloat if some packages aren’t in active use.


🏁 Script executed:

#!/bin/bash
# Inspect references to newly added Radix UI components
rg "@radix-ui/react-dropdown-menu"
rg "@radix-ui/react-label"
rg "@radix-ui/react-navigation-menu"
rg "@radix-ui/react-radio-group"
rg "@radix-ui/react-select"
rg "@radix-ui/react-slider"
rg "@radix-ui/react-switch"
rg "@radix-ui/react-tabs"

Length of output: 3867


Radix UI Dependencies Confirmed As Necessary

The grep output confirms that all the newly added Radix UI packages are actively used in the codebase. In summary:

  • @radix-ui/react-dropdown-menu: Used in frontend/src/components/ui/dropdown-menu.tsx
  • @radix-ui/react-label: Referenced in frontend/src/components/ui/form.tsx and frontend/src/components/ui/label.tsx
  • @radix-ui/react-navigation-menu: Used in frontend/src/components/ui/navigation-menu.tsx
  • @radix-ui/react-radio-group: Used in frontend/src/components/ui/radio-group.tsx
  • @radix-ui/react-select: Used in frontend/src/components/ui/select.tsx
  • @radix-ui/react-slider: Used in frontend/src/components/ui/slider.tsx
  • @radix-ui/react-switch: Used in frontend/src/components/ui/switch.tsx
  • @radix-ui/react-tabs: Used in frontend/src/components/ui/tabs.tsx

Given these references, the dependencies are justified and should remain to support the respective UI components without introducing unnecessary code bloat.


25-47:

Details

✅ Verification successful

Check for conflicting versions or security advisories.

New additions (e.g., cloudinary, convex) and updates to frequently used libraries like axios can introduce subtle incompatibilities or known vulnerabilities. A quick audit is recommended before production deployment.


🏁 Script executed:

#!/bin/bash
# Check for known security issues or potential version conflicts
npm install
npm audit --production

Length of output: 558


Dependency Audit Verified – No Vulnerabilities Detected

After re-running the audit in the correct directory (since the error indicated that package.json wasn’t found at the repository root), the security check for the dependencies in frontend/package.json confirms that there are no known vulnerabilities and no conflicting versions were detected.

Key points:

  • The initial error was due to running npm install in the wrong directory. Ensure that you run commands from the frontend folder (e.g., using cd frontend && npm install && npm audit --production).
  • The audit output (when executed in the proper context) showed 0 vulnerabilities.
  • Newly introduced packages like cloudinary and convex, along with updates to key libraries (e.g., axios), are currently free from reported security issues.

Comment on lines +64 to +129
export async function POST(request: Request) {
try {
// Check authentication and permissions
const cookieStore = await cookies()
const sessionCookie = cookieStore.get("admin_session")

if (!sessionCookie?.value) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 })
}

// Parse user data from session
const userData = JSON.parse(sessionCookie.value)

// Check if user has permission to manage users
if (!canManageUsers(userData)) {
return NextResponse.json({ error: "Permission denied" }, { status: 403 })
}

// Parse the request body
const newUserData = await request.json()

// Validate required fields
if (!newUserData.username || !newUserData.email || !newUserData.password) {
return NextResponse.json({ error: "Username, email, and password are required" }, { status: 400 })
}

// Get users collection
const usersCollection = await getCollection("users")

// Check if username or email already exists
const existingUser = await usersCollection.findOne({
$or: [{ username: newUserData.username }, { email: newUserData.email }],
})

if (existingUser) {
return NextResponse.json({ error: "Username or email already exists" }, { status: 400 })
}

// In a real implementation, you would hash the password here
// For now, we'll just store it as is (NOT SECURE - DEMO ONLY)

// Add timestamps and default values
const now = new Date().toISOString()
const userToInsert = {
...newUserData,
isActive: newUserData.isActive !== false, // Default to active if not specified
createdAt: now,
updatedAt: now,
lastLogin: null,
}

// Insert the new user
const result = await usersCollection.insertOne(userToInsert)

// Remove password from response
const { password, ...safeUser } = userToInsert

return NextResponse.json({
success: true,
user: { ...safeUser, id: result.insertedId.toString() },
})
} catch (error) {
console.error("Error creating user:", error)
return NextResponse.json({ error: "Failed to create user" }, { status: 500 })
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

Hash user passwords for security.

Storing plain-text passwords is a critical security vulnerability. Before inserting the user data, implement a reliable password-hashing mechanism (e.g., bcrypt) to protect user credentials.

Comment on lines +94 to +99
// If password is being updated and not empty
if (updateData.password) {
// In a real implementation, you would hash the password here
// For now, we'll just store it as is (NOT SECURE - DEMO ONLY)
updateFields.password = updateData.password
} else {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

Avoid storing plain text passwords to prevent security risks.
Using a plain text password is a major security risk. Even if this is a demo, it’s strongly recommended to hash passwords before storing them:

Apply a hashing approach, for example using bcrypt:

Import bcrypt and hash the password
+import bcrypt from "bcrypt"

...

 if (updateData.password) {
-  updateFields.password = updateData.password
+  const saltRounds = 10
+  const hashedPassword = await bcrypt.hash(updateData.password, saltRounds)
+  updateFields.password = hashedPassword
 }
📝 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
// If password is being updated and not empty
if (updateData.password) {
// In a real implementation, you would hash the password here
// For now, we'll just store it as is (NOT SECURE - DEMO ONLY)
updateFields.password = updateData.password
} else {
import bcrypt from "bcrypt"
// ... other imports and code
// If password is being updated and not empty
if (updateData.password) {
// In a real implementation, you would hash the password here
// For now, we'll just store it as is (NOT SECURE - DEMO ONLY)
const saltRounds = 10
const hashedPassword = await bcrypt.hash(updateData.password, saltRounds)
updateFields.password = hashedPassword
} else {
// ... rest of the code

Comment on lines +45 to +51
<Label htmlFor="start-date">Start Date</Label>
<Input id="start-date" type="date" value={startDate} onChange={(e) => setStartDate(e.target.value)} />
</div>
<div className="grid gap-2">
<Label htmlFor="end-date">End Date</Label>
<Input id="end-date" type="date" value={endDate} onChange={(e) => setEndDate(e.target.value)} />
</div>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add validation to ensure start date precedes end date.

Currently, there's no validation to ensure the start date comes before the end date, which could lead to invalid date ranges being selected.

<div className="grid gap-2">
  <Label htmlFor="start-date">Start Date</Label>
  <Input id="start-date" type="date" value={startDate} onChange={(e) => setStartDate(e.target.value)} />
</div>
<div className="grid gap-2">
  <Label htmlFor="end-date">End Date</Label>
-  <Input id="end-date" type="date" value={endDate} onChange={(e) => setEndDate(e.target.value)} />
+  <Input 
+    id="end-date" 
+    type="date" 
+    value={endDate} 
+    onChange={(e) => setEndDate(e.target.value)}
+    min={startDate} // Prevent selecting end date before start date
+  />
+  {endDate && startDate && new Date(endDate) < new Date(startDate) && (
+    <p className="text-sm text-red-500">End date cannot be before start date</p>
+  )}
</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
<Label htmlFor="start-date">Start Date</Label>
<Input id="start-date" type="date" value={startDate} onChange={(e) => setStartDate(e.target.value)} />
</div>
<div className="grid gap-2">
<Label htmlFor="end-date">End Date</Label>
<Input id="end-date" type="date" value={endDate} onChange={(e) => setEndDate(e.target.value)} />
</div>
<Label htmlFor="start-date">Start Date</Label>
<Input id="start-date" type="date" value={startDate} onChange={(e) => setStartDate(e.target.value)} />
</div>
<div className="grid gap-2">
<Label htmlFor="end-date">End Date</Label>
<Input
id="end-date"
type="date"
value={endDate}
onChange={(e) => setEndDate(e.target.value)}
min={startDate} // Prevent selecting end date before start date
/>
{endDate && startDate && new Date(endDate) < new Date(startDate) && (
<p className="text-sm text-red-500">End date cannot be before start date</p>
)}
</div>

</div>
</CardContent>
<CardFooter className="flex justify-end">
<Button>Upload</Button>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

Implement the upload functionality for the button.

The upload button doesn't have an onClick handler to actually trigger the upload process.

- <Button>Upload</Button>
+ <Button 
+   onClick={async () => {
+     if (!file) {
+       // Show error message
+       return;
+     }
+     
+     try {
+       // Set loading state
+       
+       // Create FormData
+       const formData = new FormData();
+       formData.append('file', file);
+       formData.append('name', document.getElementById('name').value);
+       formData.append('description', document.getElementById('description').value);
+       // Get selected category
+       
+       // Send to API
+       const response = await fetch('/api/admin/media', {
+         method: 'POST',
+         body: formData,
+       });
+       
+       if (!response.ok) throw new Error('Upload failed');
+       
+       // Show success message
+       // Redirect to media list
+     } catch (error) {
+       // Handle error
+       console.error('Upload error:', error);
+       // Show error message
+     } finally {
+       // Reset loading state
+     }
+   }}
+ >
+   Upload
+ </Button>
📝 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
<Button>Upload</Button>
<Button
onClick={async () => {
if (!file) {
// Show error message
return;
}
try {
// Set loading state
// Create FormData
const formData = new FormData();
formData.append('file', file);
formData.append('name', document.getElementById('name').value);
formData.append('description', document.getElementById('description').value);
// Get selected category
// Send to API
const response = await fetch('/api/admin/media', {
method: 'POST',
body: formData,
});
if (!response.ok) throw new Error('Upload failed');
// Show success message
// Redirect to media list
} catch (error) {
// Handle error
console.error('Upload error:', error);
// Show error message
} finally {
// Reset loading state
}
}}
>
Upload
</Button>

Comment thread frontend/src/app/admin/media/upload/page.tsx
Comment on lines +101 to +114
{/* Newsletter */}
<div className="md:col-span-1">
<h3 className="text-lg font-semibold mb-4">Stay Updated</h3>
<p className="text-gray-400 mb-4">
Subscribe to our newsletter for the latest updates on air quality monitoring and AI technologies.
</p>
<div className="flex flex-col space-y-2">
<Input type="email" placeholder="Your email address" className="bg-gray-800 border-gray-700 text-white" />
<Button className="w-full">
<Mail className="mr-2 h-4 w-4" />
Subscribe
</Button>
</div>
</div>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add newsletter form submission handler.

The newsletter subscription form lacks a submission handler and validation for the email input.

+import { useState } from "react"
+
 export function SiteFooter() {
+  const [email, setEmail] = useState("")
+  const [isSubmitting, setIsSubmitting] = useState(false)
+  const [subscribed, setSubscribed] = useState(false)
+
+  const handleSubscribe = async (e) => {
+    e.preventDefault()
+    if (!email || !/^\S+@\S+\.\S+$/.test(email)) {
+      alert("Please enter a valid email address")
+      return
+    }
+    
+    setIsSubmitting(true)
+    try {
+      // Replace with actual API call
+      await fetch('/api/subscribe', {
+        method: 'POST',
+        headers: {
+          'Content-Type': 'application/json',
+        },
+        body: JSON.stringify({ email }),
+      })
+      setSubscribed(true)
+      setEmail("")
+    } catch (error) {
+      console.error('Error subscribing:', error)
+      alert("Failed to subscribe. Please try again later.")
+    } finally {
+      setIsSubmitting(false)
+    }
+  }

  // ...

  {/* Newsletter */}
  <div className="md:col-span-1">
    <h3 className="text-lg font-semibold mb-4">Stay Updated</h3>
    <p className="text-gray-400 mb-4">
      Subscribe to our newsletter for the latest updates on air quality monitoring and AI technologies.
    </p>
-   <div className="flex flex-col space-y-2">
-     <Input type="email" placeholder="Your email address" className="bg-gray-800 border-gray-700 text-white" />
-     <Button className="w-full">
+   {subscribed ? (
+     <p className="text-green-400">Thank you for subscribing!</p>
+   ) : (
+     <form onSubmit={handleSubscribe} className="flex flex-col space-y-2">
+       <Input 
+         type="email" 
+         placeholder="Your email address" 
+         className="bg-gray-800 border-gray-700 text-white" 
+         value={email}
+         onChange={(e) => setEmail(e.target.value)}
+         required
+       />
+       <Button type="submit" className="w-full" disabled={isSubmitting}>
+         {isSubmitting ? "Subscribing..." : (
            <Mail className="mr-2 h-4 w-4" />
            Subscribe
-     </Button>
-   </div>
+         )}
+       </Button>
+     </form>
+   )}
  </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
{/* Newsletter */}
<div className="md:col-span-1">
<h3 className="text-lg font-semibold mb-4">Stay Updated</h3>
<p className="text-gray-400 mb-4">
Subscribe to our newsletter for the latest updates on air quality monitoring and AI technologies.
</p>
<div className="flex flex-col space-y-2">
<Input type="email" placeholder="Your email address" className="bg-gray-800 border-gray-700 text-white" />
<Button className="w-full">
<Mail className="mr-2 h-4 w-4" />
Subscribe
</Button>
</div>
</div>
import { useState } from "react"
// ...other imports
export function SiteFooter() {
const [email, setEmail] = useState("")
const [isSubmitting, setIsSubmitting] = useState(false)
const [subscribed, setSubscribed] = useState(false)
const handleSubscribe = async (e) => {
e.preventDefault()
if (!email || !/^\S+@\S+\.\S+$/.test(email)) {
alert("Please enter a valid email address")
return
}
setIsSubmitting(true)
try {
// Replace with actual API call
await fetch('/api/subscribe', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ email }),
})
setSubscribed(true)
setEmail("")
} catch (error) {
console.error('Error subscribing:', error)
alert("Failed to subscribe. Please try again later.")
} finally {
setIsSubmitting(false)
}
}
return (
<footer>
{/* ...other footer elements... */}
{/* Newsletter */}
<div className="md:col-span-1">
<h3 className="text-lg font-semibold mb-4">Stay Updated</h3>
<p className="text-gray-400 mb-4">
Subscribe to our newsletter for the latest updates on air quality monitoring and AI technologies.
</p>
{subscribed ? (
<p className="text-green-400">Thank you for subscribing!</p>
) : (
<form onSubmit={handleSubscribe} className="flex flex-col space-y-2">
<Input
type="email"
placeholder="Your email address"
className="bg-gray-800 border-gray-700 text-white"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
/>
<Button type="submit" className="w-full" disabled={isSubmitting}>
{isSubmitting ? "Subscribing..." : (
<>
<Mail className="mr-2 h-4 w-4" />
Subscribe
</>
)}
</Button>
</form>
)}
</div>
</footer>
);
}

Comment on lines +9 to +36
export async function GET(request: Request) {
try {
// Get query parameters for filtering
const url = new URL(request.url)
const type = url.searchParams.get("type")
const search = url.searchParams.get("search")

// Build query
const query: Record<string, any> = {}

if (type) {
query.type = { $regex: `^${type}`, $options: "i" }
}

if (search) {
query.$or = [{ name: { $regex: search, $options: "i" } }]
}

// Get media items from MongoDB
const mediaCollection = await getCollection("media")
const items = await mediaCollection.find(query).sort({ uploadedAt: -1 }).toArray()

return NextResponse.json({ items })
} catch (error) {
console.error("Error fetching media:", error)
return NextResponse.json({ error: "Failed to fetch media items" }, { status: 500 })
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Public GET endpoint considerations
Your GET handler allows unrestricted, anonymous access to media items. If that’s intentional, it’s all good. Otherwise, consider adding authentication checks, particularly if these payloads contain sensitive file references. You may also want to add pagination or rate limiting if many items are expected.

Comment on lines +104 to +127
<Tabs defaultValue="summary">
<div className="flex justify-between items-center mb-6">
<TabsList>
<TabsTrigger value="summary">Summary</TabsTrigger>
<TabsTrigger value="trends">Trends</TabsTrigger>
<TabsTrigger value="pollutants">Pollutants</TabsTrigger>
<TabsTrigger value="locations">Locations</TabsTrigger>
</TabsList>

<div className="flex space-x-2">
<Button variant="outline" size="sm">
<Download className="h-4 w-4 mr-2" />
Export PDF
</Button>
<Button variant="outline" size="sm">
<FileText className="h-4 w-4 mr-2" />
Export CSV
</Button>
<Button variant="outline" size="sm">
<Share2 className="h-4 w-4 mr-2" />
Share
</Button>
</div>
</div>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Ensure export and share actions are implemented or labeled as forthcoming.
The export and share buttons appear interactive yet lack functionality, potentially misleading users. Adding real export and share logic (e.g., generating PDFs, CSVs, share links) or clarifying that these features are still under development would improve the UX.

Comment thread frontend/src/app/page.tsx
Comment on lines +3 to +5
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"
import { MapPin, Map, BarChart2, FileText, Info } from "lucide-react"
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Rename the Map import to avoid overshadowing the global Map object.

Static analysis indicates that using Map from "lucide-react" can shadow the global JavaScript Map class, potentially causing confusion or conflicts in certain contexts. Consider a name like MapIcon to clarify usage.

- import { MapPin, Map, BarChart2, FileText, Info } from "lucide-react"
+ import { MapPin, Map as MapIcon, BarChart2, FileText, Info } from "lucide-react"
📝 Committable suggestion

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

Suggested change
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"
import { MapPin, Map, BarChart2, FileText, Info } from "lucide-react"
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"
import { MapPin, Map as MapIcon, BarChart2, FileText, Info } from "lucide-react"
🧰 Tools
🪛 Biome (1.9.4)

[error] 5-5: Do not shadow the global "Map" property.

Consider renaming this variable. It's easy to confuse the origin of variables when they're named after a known global.

(lint/suspicious/noShadowRestrictedNames)

Comment on lines +44 to +65
const useFormField = () => {
const fieldContext = React.useContext(FormFieldContext)
const itemContext = React.useContext(FormItemContext)
const { getFieldState, formState } = useFormContext()

const fieldState = getFieldState(fieldContext.name, formState)

if (!fieldContext) {
throw new Error("useFormField should be used within <FormField>")
}

const { id } = itemContext

return {
id,
name: fieldContext.name,
formItemId: `${id}-form-item`,
formDescriptionId: `${id}-form-item-description`,
formMessageId: `${id}-form-item-message`,
...fieldState,
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Validate the itemContext usage.

Within useFormField, you properly verify fieldContext before usage, but not itemContext. While FormItemContext is presumably always provided, consider a safety check or a fallback if itemContext is missing, similar to the pattern you use for fieldContext.


🏁 Script executed:

#!/bin/bash
# Confirm usage of `FormItemContext` across the codebase.
rg -A 5 "FormItemContext"

Length of output: 1600


Action Required: Add Guard for itemContext in useFormField Hook

It looks like while you’re correctly validating fieldContext, there isn’t a similar guard for itemContext. Although FormItemContext is created with a default value, relying on that default (i.e. using {} cast as FormItemContextValue) may hide issues if <FormItem> is ever omitted from the consumer’s tree. For consistency and safety, it would be beneficial to add a check for itemContext similar to the one for fieldContext. For example:

 const useFormField = () => {
   const fieldContext = React.useContext(FormFieldContext)
   const itemContext = React.useContext(FormItemContext)
   const { getFieldState, formState } = useFormContext()

   const fieldState = getFieldState(fieldContext.name, formState)

-  if (!fieldContext) {
-    throw new Error("useFormField should be used within <FormField>")
-  }
+  if (!fieldContext) {
+    throw new Error("useFormField should be used within <FormField>")
+  }
+  if (!itemContext) {
+    throw new Error("useFormField should be used within <FormItem>")
+  }

   const { id } = itemContext

   return {
     id,
     name: fieldContext.name,
     formItemId: `${id}-form-item`,
     formDescriptionId: `${id}-form-item-description`,
     formMessageId: `${id}-form-item-message`,
     ...fieldState,
   }
 }

This change ensures that misuse of the hook (for example, when used outside a <FormItem> provider) will surface promptly with a clear error message, matching the safeguard in place for fieldContext.

📝 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 useFormField = () => {
const fieldContext = React.useContext(FormFieldContext)
const itemContext = React.useContext(FormItemContext)
const { getFieldState, formState } = useFormContext()
const fieldState = getFieldState(fieldContext.name, formState)
if (!fieldContext) {
throw new Error("useFormField should be used within <FormField>")
}
const { id } = itemContext
return {
id,
name: fieldContext.name,
formItemId: `${id}-form-item`,
formDescriptionId: `${id}-form-item-description`,
formMessageId: `${id}-form-item-message`,
...fieldState,
}
}
const useFormField = () => {
const fieldContext = React.useContext(FormFieldContext)
const itemContext = React.useContext(FormItemContext)
const { getFieldState, formState } = useFormContext()
const fieldState = getFieldState(fieldContext.name, formState)
if (!fieldContext) {
throw new Error("useFormField should be used within <FormField>")
}
if (!itemContext) {
throw new Error("useFormField should be used within <FormItem>")
}
const { id } = itemContext
return {
id,
name: fieldContext.name,
formItemId: `${id}-form-item`,
formDescriptionId: `${id}-form-item-description`,
formMessageId: `${id}-form-item-message`,
...fieldState,
}
}

Copy link
Copy Markdown

@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

🧹 Nitpick comments (3)
frontend/src/components/icons.tsx (1)

1-69: Consider adding TypeScript interfaces for better type safety.

While the current implementation works well, adding TypeScript interfaces would improve type safety when using the Icons object throughout the application.

import {
  AlertTriangle,
  ArrowRight,
  // ... other imports
} from "lucide-react"
+ import { SVGProps } from "react"
+
+ export interface IconProps extends SVGProps<SVGSVGElement> {
+   size?: number | string
+   strokeWidth?: number | string
+ }
+
+ type IconsType = {
+   [key: string]: React.FC<IconProps>
+ }

- export const Icons = {
+ export const Icons: IconsType = {
  logo: Command,
  // ... other icons
}

This addition would provide better autocompletion and type checking when using the icons throughout your application.

frontend/src/app/admin/ai-technologies/new/page.tsx (2)

116-140: Form submission logic is duplicated.

The save button outside the form uses onClick={handleSubmit}, but there's also an onSubmit={handleSubmit} on the form element. This creates two potential submission paths that could diverge or cause unexpected behavior.

Consider using only the form's native submission:

-      <Button onClick={handleSubmit} disabled={isSubmitting} className="flex items-center gap-2">
+      <Button type="submit" form="aiTechForm" disabled={isSubmitting} className="flex items-center gap-2">

And update the form element:

-        <form onSubmit={handleSubmit} className="space-y-8">
+        <form id="aiTechForm" onSubmit={handleSubmit} className="space-y-8">

252-264: Image error fallback uses type 'any'.

The error handler for the Image component uses (e.target as any), which bypasses TypeScript's type checking. This could lead to runtime errors if the structure changes.

Improve type safety with a more specific type assertion:

-                    onError={(e) => {
-                      ;(e.target as any).src = "/placeholder.svg?height=400&width=800&text=Image+Not+Found"
-                    }}
+                    onError={(e) => {
+                      (e.target as HTMLImageElement).src = "/placeholder.svg?height=400&width=800&text=Image+Not+Found"
+                    }}
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between b877caf and 95addf6.

📒 Files selected for processing (2)
  • frontend/src/app/admin/ai-technologies/new/page.tsx (1 hunks)
  • frontend/src/components/icons.tsx (1 hunks)
🧰 Additional context used
🧬 Code Definitions (1)
frontend/src/app/admin/ai-technologies/new/page.tsx (6)
frontend/src/hooks/use-toast.ts (2)
  • useToast (194-194)
  • toast (194-194)
frontend/src/components/ui/button.tsx (1)
  • Button (57-57)
frontend/src/components/ui/card.tsx (5)
  • Card (76-76)
  • CardHeader (76-76)
  • CardTitle (76-76)
  • CardDescription (76-76)
  • CardContent (76-76)
frontend/src/components/ui/label.tsx (1)
  • Label (26-26)
frontend/src/components/ui/input.tsx (1)
  • Input (22-22)
frontend/src/components/ui/textarea.tsx (1)
  • Textarea (22-22)
🔇 Additional comments (9)
frontend/src/components/icons.tsx (4)

1-25: Well-structured import organization.

The imports are cleanly organized with one component per line and arranged alphabetically, making the code more readable and maintainable. This approach makes it easy to locate specific icons and identify what's being imported from the lucide-react library.


27-43: Good abstraction for icon components.

Creating a centralized Icons object is an excellent approach for maintaining consistency across the application. This pattern:

  • Provides a single source of truth for icon components
  • Makes it easy to swap icon implementations if needed
  • Creates intuitive aliases that match your application's terminology

The naming convention is consistent and intuitive (camelCase), following React best practices.


44-60: Custom GitHub icon implementation is well-crafted.

The custom SVG implementation for the GitHub icon includes proper accessibility attributes (aria-hidden, focusable, role) and correctly passes through additional props using the spread operator. This ensures the icon will behave consistently with the other icons from lucide-react.


61-68: Complete icon collection follows consistent pattern.

The remaining icon mappings follow the same pattern established earlier, maintaining consistency throughout the object. The collection appears comprehensive for typical UI needs across the application.

frontend/src/app/admin/ai-technologies/new/page.tsx (5)

15-15: Import looks good, aligns with project structure.

I see you're importing the useToast hook from @/hooks/use-toast, which is the correct path based on your project structure. This properly follows the project conventions.


27-41: Component setup is structured well.

The state management is well-organized with appropriate types. The initial state for the form data has all required fields initialized, making it clear what data structure is expected.


48-79: Memory leak potential in preview image handling.

The handlePreview function doesn't clean up previous preview URLs, which can cause memory leaks over time. Additionally, there's no cleanup when the component unmounts.

Add a cleanup effect to revoke object URLs:

 const [previewUrl, setPreviewUrl] = useState<string | null>(null)
+
+// Add cleanup effect for blob URLs
+useEffect(() => {
+  return () => {
+    if (previewUrl) {
+      URL.revokeObjectURL(previewUrl);
+    }
+  };
+}, [previewUrl]);

 const handlePreview = async () => {
   if (!formData.prompt) {
     toast({
       title: "Prompt required",
       description: "Please enter a prompt to generate a preview",
       variant: "destructive",
     })
     return
   }

   try {
+    // Clean up previous preview URL if it exists
+    if (previewUrl) {
+      URL.revokeObjectURL(previewUrl);
+    }
     setIsGenerating(true)

81-114: Form validation is properly implemented.

The form submission handler correctly validates required fields before proceeding and provides appropriate user feedback with toast notifications. The loading state is properly managed throughout the submission process.


1-276: Overall this is a well-structured component.

The page implements a comprehensive form for creating AI technologies with appropriate validation, loading states, and user feedback. The UI is clean and responsive. After addressing the few points mentioned above, this will be a solid implementation.

Copy link
Copy Markdown

@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

♻️ Duplicate comments (2)
frontend/src/app/api/admin/users/route.ts (1)

103-105: ⚠️ Potential issue

Hash user passwords for security.

Storing plain-text passwords is a critical security vulnerability. Before inserting the user data, implement a reliable password-hashing mechanism (e.g., bcrypt) to protect user credentials.

- // In a real implementation, you would hash the password here
- // For now, we'll just store it as is (NOT SECURE - DEMO ONLY)
+ // Hash the password before storing
+ import bcrypt from "bcrypt"
+ 
+ // Add at the beginning of the POST function
+ const saltRounds = 10
+ const hashedPassword = await bcrypt.hash(newUserData.password, saltRounds)
+ 
+ // Then modify the userToInsert object
+ const userToInsert = {
+   ...newUserData,
+   password: hashedPassword,
+   isActive: newUserData.isActive !== false,
+   createdAt: now,
+   updatedAt: now,
+   lastLogin: null,
+ }
frontend/src/app/api/admin/users/[id]/route.ts (1)

95-100: ⚠️ Potential issue

Avoid storing plain text passwords to prevent security risks.

Using a plain text password is a major security risk. Even if this is a demo, it's strongly recommended to hash passwords before storing them.

+import bcrypt from "bcrypt"

...

 // If password is being updated and not empty
 if (updateData.password) {
-  // In a real implementation, you would hash the password here
-  // For now, we'll just store it as is (NOT SECURE - DEMO ONLY)
-  updateFields.password = updateData.password
+  const saltRounds = 10
+  const hashedPassword = await bcrypt.hash(updateData.password, saltRounds)
+  updateFields.password = hashedPassword
 } else {
   // Don't update password if not provided
   delete updateFields.password
 }
🧹 Nitpick comments (13)
frontend/src/app/api/admin/users/route.ts (2)

108-114: Consider adding role validation.

The user creation doesn't validate if the assigned role is valid. Consider adding a check against a predefined list of allowed roles to prevent assigning invalid roles.

+ // Validate user role if provided
+ const validRoles = ["admin", "editor", "user"] // Define your valid roles
+ if (newUserData.role && !validRoles.includes(newUserData.role)) {
+   return NextResponse.json({ error: "Invalid role specified" }, { status: 400 })
+ }

 // Add timestamps and default values
 const now = new Date().toISOString()
 const userToInsert = {
   ...newUserData,
+  role: newUserData.role || "user", // Default to regular user if not specified
   isActive: newUserData.isActive !== false, // Default to active if not specified
   createdAt: now,
   updatedAt: now,
   lastLogin: null,
 }

116-118: Add error handling for MongoDB insert failures.

When inserting the user document, consider enhancing the error handling to deal with specific MongoDB errors like duplicate key violations.

 // Insert the new user
-const result = await usersCollection.insertOne(userToInsert)
+let result;
+try {
+  result = await usersCollection.insertOne(userToInsert)
+} catch (insertError) {
+  // Handle specific MongoDB errors
+  if (insertError.code === 11000) { // Duplicate key error
+    return NextResponse.json({ error: "Username or email already exists" }, { status: 400 })
+  }
+  throw insertError; // Re-throw for the outer catch block
+}
frontend/src/app/api/admin/users/[id]/route.ts (3)

100-103: Consider using property assignment over the delete operator.

The delete operator can potentially impact performance. Consider assigning undefined instead.

 } else {
   // Don't update password if not provided
-  delete updateFields.password
+  updateFields.password = undefined
 }
🧰 Tools
🪛 Biome (1.9.4)

[error] 102-102: Avoid the delete operator which can impact performance.

Unsafe fix: Use an undefined assignment instead.

(lint/performance/noDelete)


150-153: Add ID format validation across all handlers.

In all three handlers, consider validating that the ID parameter is a valid MongoDB ObjectId format before attempting to use it. This would prevent potential errors when an invalid ID is provided.

 const id = params.id
 
+// Validate ObjectId format
+if (!ObjectId.isValid(id)) {
+  return NextResponse.json({ error: "Invalid user ID format" }, { status: 400 })
+}

 // Prevent deleting your own account
 if (userData.id === id) {
   return NextResponse.json({ error: "You cannot delete your own account" }, { status: 400 })
 }

1-5: Consider implementing request rate limiting.

For security-sensitive endpoints like user management, consider implementing rate limiting to prevent brute force attacks and potential DoS situations.

You could use a package like next-rate-limit or implement a simple rate limiter using Redis or a similar caching mechanism. This would help protect your authentication endpoints from abuse.

frontend/src/app/admin/media/upload/page.tsx (2)

15-17: State management is incomplete for the form

The component only manages the preview URL state but doesn't track other form field values (name, description, category). This will make it difficult to gather all required data on submission.

export default function MediaUploadPage() {
  const [previewUrl, setPreviewUrl] = useState<string | null>(null)
+  const [file, setFile] = useState<File | null>(null)
+  const [name, setName] = useState("")
+  const [description, setDescription] = useState("")
+  const [category, setCategory] = useState("")

42-42: Add fixed height to preview container

The preview container has h-full which may not provide enough vertical space for the preview to be visible.

- <div className="relative w-full h-full">
+ <div className="relative w-full h-64">
frontend/src/app/features/site-location/page.tsx (3)

26-32: Replace placeholder image with actual site location imagery.

The hero section uses a placeholder image that should be replaced with actual imagery relevant to site location optimization for better user engagement and visual context.

-                <Image
-                  src="/placeholder.svg?height=400&width=600&text=Site+Location"
-                  alt="Optimal Site Location"
-                  fill
-                  className="object-cover rounded-lg shadow-lg"
-                />
+                <Image
+                  src="/images/site-location-hero.jpg"
+                  alt="Optimal Site Location - Map with optimal sensor placements"
+                  fill
+                  className="object-cover rounded-lg shadow-lg"
+                />

177-184: Replace placeholder images in related features cards.

All images in the related features section are placeholders. Replace them with relevant imagery to enhance visual appeal and better communicate the feature's purpose.

For all the related feature cards, consider replacing the placeholder images with actual feature-specific imagery like:

-                <Image
-                  src="/placeholder.svg?height=200&width=400&text=Data+Analysis"
-                  alt="Data Analysis"
-                  fill
-                  className="object-cover rounded-t-lg"
-                />
+                <Image
+                  src="/images/features/data-analysis.jpg"
+                  alt="Data Analysis Dashboard"
+                  fill
+                  className="object-cover rounded-t-lg"
+                />

49-83: Consider adding descriptive icons to the workflow steps.

The "How It Works" section uses numbered steps, but adding relevant icons would enhance visual communication and user understanding of each step.

Add descriptive icons to each step card. For example:

  • Step 1: A map/location icon
  • Step 2: A settings/parameters icon
  • Step 3: A pin/marker icon
frontend/src/app/categorize/page.tsx (1)

254-255: Implement export functionality for the "Export Results" button.

The "Export Results" button is currently disabled based on the resultsReady state but doesn't have any implementation for exporting data.

Add an export function to generate PDF or CSV of the results:

+ const handleExportResults = () => {
+   // Generate CSV or PDF based on results
+   // Example for CSV:
+   const csvContent = [
+     ["Pollutant", "Value", "Category", "Index"],
+     ["PM2.5", "15 μg/m³", "Moderate", "75"],
+     ["PM10", "30 μg/m³", "Good", "45"],
+     ["NO₂", "25 ppb", "Good", "30"],
+     ["O₃", "40 ppb", "Moderate", "60"]
+   ].map(row => row.join(",")).join("\n")
+   
+   const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' })
+   const url = URL.createObjectURL(blob)
+   const link = document.createElement("a")
+   link.setAttribute("href", url)
+   link.setAttribute("download", "air_quality_results.csv")
+   document.body.appendChild(link)
+   link.click()
+   document.body.removeChild(link)
+ }

// Then update the button:
- <Button variant="outline" disabled={!resultsReady}>
-   Export Results
- </Button>
+ <Button 
+   variant="outline" 
+   disabled={!resultsReady} 
+   onClick={handleExportResults}
+ >
+   Export Results
+ </Button>
frontend/src/app/about/page.tsx (2)

41-50: Replace placeholder image with actual company imagery.

The "Our Story" section uses a placeholder image that should be replaced with an actual image of the team or company to enhance credibility and user engagement.

-                <Image
-                  src="/placeholder.svg?height=500&width=800&text=Our+Story"
-                  alt="AirQo Team"
-                  fill
-                  className="object-cover"
-                />
+                <Image
+                  src="/images/about/team-photo.jpg"
+                  alt="AirQo Team working on air quality monitoring solutions"
+                  fill
+                  className="object-cover"
+                />

175-182: Replace placeholder team member images.

All team member images are using placeholders. Replace these with actual headshots of team members for authenticity and professional appearance.

For all team member images:

-                <Image
-                  src="/placeholder.svg?height=200&width=200&text=Team+Member"
-                  alt="Team Member"
-                  fill
-                  className="object-cover"
-                />
+                <Image
+                  src="/images/team/jane-smith.jpg"
+                  alt="Dr. Jane Smith - Founder & CEO"
+                  fill
+                  className="object-cover"
+                />
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 95addf6 and e101b64.

📒 Files selected for processing (12)
  • frontend/src/app/about/page.tsx (1 hunks)
  • frontend/src/app/admin/media/upload/page.tsx (1 hunks)
  • frontend/src/app/api/admin/auth/login/route.ts (1 hunks)
  • frontend/src/app/api/admin/media/[id]/route.ts (1 hunks)
  • frontend/src/app/api/admin/media/route.ts (1 hunks)
  • frontend/src/app/api/admin/users/[id]/route.ts (1 hunks)
  • frontend/src/app/api/admin/users/route.ts (1 hunks)
  • frontend/src/app/categorize/page.tsx (1 hunks)
  • frontend/src/app/contact/page.tsx (1 hunks)
  • frontend/src/app/features/site-location/page.tsx (1 hunks)
  • frontend/src/components/use-toast.ts (1 hunks)
  • frontend/src/lib/utils.ts (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • frontend/src/components/use-toast.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • frontend/src/lib/utils.ts
  • frontend/src/app/api/admin/media/[id]/route.ts
  • frontend/src/app/api/admin/media/route.ts
  • frontend/src/app/api/admin/auth/login/route.ts
🧰 Additional context used
🧬 Code Definitions (5)
frontend/src/app/admin/media/upload/page.tsx (6)
frontend/src/components/ui/card.tsx (6)
  • Card (76-76)
  • CardHeader (76-76)
  • CardTitle (76-76)
  • CardDescription (76-76)
  • CardContent (76-76)
  • CardFooter (76-76)
frontend/src/components/ui/label.tsx (1)
  • Label (26-26)
frontend/src/components/ui/input.tsx (1)
  • Input (22-22)
frontend/src/components/ui/textarea.tsx (1)
  • Textarea (22-22)
frontend/src/components/ui/select.tsx (5)
  • Select (149-149)
  • SelectTrigger (152-152)
  • SelectValue (151-151)
  • SelectContent (153-153)
  • SelectItem (155-155)
frontend/src/components/ui/button.tsx (1)
  • Button (57-57)
frontend/src/app/categorize/page.tsx (8)
frontend/src/components/ui/card.tsx (6)
  • Card (76-76)
  • CardHeader (76-76)
  • CardTitle (76-76)
  • CardDescription (76-76)
  • CardContent (76-76)
  • CardFooter (76-76)
frontend/src/components/ui/label.tsx (1)
  • Label (26-26)
frontend/src/components/ui/select.tsx (5)
  • Select (149-149)
  • SelectTrigger (152-152)
  • SelectValue (151-151)
  • SelectContent (153-153)
  • SelectItem (155-155)
frontend/src/components/ui/input.tsx (1)
  • Input (22-22)
frontend/src/components/ui/button.tsx (1)
  • Button (57-57)
frontend/src/components/ui/tabs.tsx (4)
  • Tabs (55-55)
  • TabsList (55-55)
  • TabsTrigger (55-55)
  • TabsContent (55-55)
frontend/src/components/ui/badge.tsx (1)
  • Badge (36-36)
frontend/src/components/ui/table.tsx (6)
  • Table (112-112)
  • TableHeader (113-113)
  • TableRow (117-117)
  • TableHead (116-116)
  • TableBody (114-114)
  • TableCell (118-118)
frontend/src/app/features/site-location/page.tsx (3)
frontend/src/components/ui/button.tsx (1)
  • Button (57-57)
frontend/src/lib/types.ts (2)
  • Site (134-148)
  • Location (1-4)
frontend/src/components/ui/card.tsx (2)
  • Card (76-76)
  • CardContent (76-76)
frontend/src/app/about/page.tsx (3)
frontend/src/components/ui/card.tsx (5)
  • Card (76-76)
  • CardHeader (76-76)
  • CardTitle (76-76)
  • CardDescription (76-76)
  • CardContent (76-76)
frontend/src/ui/button.tsx (1)
  • Button (56-56)
frontend/src/components/ui/button.tsx (1)
  • Button (57-57)
frontend/src/app/api/admin/users/[id]/route.ts (2)
frontend/src/app/api/admin/users/route.ts (1)
  • GET (7-62)
frontend/src/lib/mongodb.ts (1)
  • getCollection (40-43)
🪛 Biome (1.9.4)
frontend/src/app/api/admin/users/[id]/route.ts

[error] 102-102: Avoid the delete operator which can impact performance.

Unsafe fix: Use an undefined assignment instead.

(lint/performance/noDelete)

🔇 Additional comments (10)
frontend/src/app/api/admin/users/route.ts (1)

7-62: Well-structured GET handler with proper authentication and filtering.

The GET function effectively retrieves users with good filtering options while properly implementing authentication and authorization checks. The exclusion of password fields from the response enhances security.

frontend/src/app/api/admin/users/[id]/route.ts (2)

8-45: Well-implemented GET handler with proper ID validation.

The GET function correctly fetches a specific user by ID while implementing proper authentication, authorization, and error handling. Good practice to exclude the password from the response.


129-168: Good implementation of the DELETE handler with self-deletion protection.

The DELETE function properly validates permissions and includes a thoughtful restriction to prevent users from deleting their own accounts. This is an excellent security practice.

frontend/src/app/admin/media/upload/page.tsx (4)

22-22: Fix potential memory leak in image preview

When creating object URLs with URL.createObjectURL(), they should be revoked when no longer needed to prevent memory leaks.

export default function MediaUploadPage() {
  const [previewUrl, setPreviewUrl] = useState<string | null>(null)

+ // Clean up the object URL when component unmounts or when file changes
+ useEffect(() => {
+   return () => {
+     if (previewUrl) {
+       URL.revokeObjectURL(previewUrl);
+     }
+   };
+ }, [previewUrl]);

39-54: Handle non-image file previews appropriately

The component uses the Next.js Image component for previews, which works for images but not for videos or other media types.

{previewUrl && (
  <div className="border rounded-md p-4">
    <Label>Preview</Label>
    <div className="relative w-full h-full">
-     <Image
-       src={previewUrl || "/placeholder.svg"}
-       alt="Media preview"
-       fill
-       className="object-contain"
-       onError={(e) => {
-         ;(e.target as any).src = "/placeholder.svg?height=400&width=800&text=Image+Not+Found"
-       }}
-     />
+     {file?.type.startsWith('image/') ? (
+       <Image
+         src={previewUrl || "/placeholder.svg"}
+         alt="Media preview"
+         fill
+         className="object-contain"
+         onError={(e) => {
+           ;(e.target as any).src = "/placeholder.svg?height=400&width=800&text=Image+Not+Found"
+         }}
+       />
+     ) : file?.type.startsWith('video/') ? (
+       <video 
+         src={previewUrl || ""} 
+         controls 
+         className="w-full h-full object-contain"
+       />
+     ) : (
+       <div className="flex items-center justify-center h-full">
+         <p className="text-center">Preview not available for this file type ({file?.type})</p>
+       </div>
+     )}
    </div>
  </div>
)}

79-79: Implement the upload functionality for the button

The upload button doesn't have an onClick handler to actually trigger the upload process.

- <Button>Upload</Button>
+ <Button 
+   onClick={handleUpload}
+   disabled={isUploading || !file}
+ >
+   {isUploading ? "Uploading..." : "Upload"}
+ </Button>

You should also implement the handleUpload function and isUploading state:

const [isUploading, setIsUploading] = useState(false);

const handleUpload = async () => {
  if (!file) {
    // Show error message
    return;
  }
  
  setIsUploading(true);
  
  try {
    // Create FormData
    const formData = new FormData();
    formData.append('file', file);
    formData.append('name', name);
    formData.append('description', description);
    formData.append('category', category);
    
    // Send to API
    const response = await fetch('/api/admin/media', {
      method: 'POST',
      body: formData,
    });
    
    if (!response.ok) throw new Error('Upload failed');
    
    // Show success message
    // Redirect to media list
  } catch (error) {
    // Handle error
    console.error('Upload error:', error);
    // Show error message
  } finally {
    setIsUploading(false);
  }
};

1-84: Add validation for file size and type before upload

The upload handler lacks validation for file size and type restrictions, which could lead to errors or unnecessary resource usage when attempting to upload invalid files.

frontend/src/app/contact/page.tsx (2)

34-83: Form lacks submission handler and validation.

The contact form is visually well-structured but missing essential functionality:

  • No form submission handler to process user data
  • No form validation for required fields
  • No state management to track form input values

173-178: Replace placeholder map with actual map component.

The map section currently contains only a placeholder div. Consider implementing a real map component using Google Maps, Mapbox, or another mapping library to show the office location.

frontend/src/app/about/page.tsx (1)

298-305:

Details

❓ Verification inconclusive

Verify that the career page route exists.

The "Join Our Team" button links to "/careers", but it's not clear if this route exists in the application.

Please check if the "/careers" route is implemented or planned in the frontend application structure. If not, either implement the route or update the link to point to a valid destination (such as a section in the contact page).


🏁 Script executed:

#!/bin/bash
# Check if careers page exists in the codebase
fd -t f "careers/page\.tsx" frontend/src/app

Length of output: 46


Action: Verify the existence of the /careers route

Our automated search didn’t find any evidence of a file matching frontend/src/app/careers/page.tsx or frontend/src/app/careers/index.tsx, which suggests that the /careers route might not be implemented yet. Please manually verify whether the careers page is planned or exists:

  • Confirm if there is a careers directory (or similar) under frontend/src/app with the appropriate route definition.
  • If the route isn’t available, update the /careers link in the About page to point to an existing or interim destination (e.g., a section of the contact page).

Looking forward to your confirmation on the intended navigation.

Comment on lines +18 to +24
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const selectedFile = event.target.files?.[0]
if (selectedFile) {
// Create object URL for preview
setPreviewUrl(URL.createObjectURL(selectedFile))
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Update file state alongside previewUrl

The component creates a preview URL but doesn't store the file object itself, which will be needed for upload.

const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
  const selectedFile = event.target.files?.[0]
  if (selectedFile) {
    // Create object URL for preview
+   setFile(selectedFile)
    setPreviewUrl(URL.createObjectURL(selectedFile))
  }
}
📝 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 handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const selectedFile = event.target.files?.[0]
if (selectedFile) {
// Create object URL for preview
setPreviewUrl(URL.createObjectURL(selectedFile))
}
}
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const selectedFile = event.target.files?.[0]
if (selectedFile) {
// Create object URL for preview
setFile(selectedFile)
setPreviewUrl(URL.createObjectURL(selectedFile))
}
}

Comment on lines +62 to +63
<Textarea id="description" placeholder="Media Description" />
</div>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Connect textarea value to state

The description textarea should be connected to component state for controlled behavior.

- <Textarea id="description" placeholder="Media Description" />
+ <Textarea 
+   id="description" 
+   placeholder="Media Description"
+   value={description}
+   onChange={(e) => setDescription(e.target.value)}
+ />
📝 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
<Textarea id="description" placeholder="Media Description" />
</div>
<Textarea
id="description"
placeholder="Media Description"
value={description}
onChange={(e) => setDescription(e.target.value)}
/>
</div>

Comment on lines +66 to +75
<Select>
<SelectTrigger>
<SelectValue placeholder="Select a category" />
</SelectTrigger>
<SelectContent>
<SelectItem value="banners">Banners</SelectItem>
<SelectItem value="products">Products</SelectItem>
<SelectItem value="blog">Blog</SelectItem>
</SelectContent>
</Select>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Connect select value to state

The category select should be connected to component state for controlled behavior.

- <Select>
+ <Select value={category} onValueChange={setCategory}>
📝 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
<Select>
<SelectTrigger>
<SelectValue placeholder="Select a category" />
</SelectTrigger>
<SelectContent>
<SelectItem value="banners">Banners</SelectItem>
<SelectItem value="products">Products</SelectItem>
<SelectItem value="blog">Blog</SelectItem>
</SelectContent>
</Select>
<Select value={category} onValueChange={setCategory}>
<SelectTrigger>
<SelectValue placeholder="Select a category" />
</SelectTrigger>
<SelectContent>
<SelectItem value="banners">Banners</SelectItem>
<SelectItem value="products">Products</SelectItem>
<SelectItem value="blog">Blog</SelectItem>
</SelectContent>
</Select>

Comment on lines +58 to +59
<Input id="name" placeholder="Media Name" />
</div>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Connect input value to state

The name input should be connected to component state for controlled behavior.

- <Input id="name" placeholder="Media Name" />
+ <Input 
+   id="name" 
+   placeholder="Media Name"
+   value={name}
+   onChange={(e) => setName(e.target.value)}
+ />
📝 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
<Input id="name" placeholder="Media Name" />
</div>
<Input
id="name"
placeholder="Media Name"
value={name}
onChange={(e) => setName(e.target.value)}
/>
</div>

Comment on lines +1 to +14
"use client"

import type React from "react"

import Image from "next/image"

import { Button } from "@/components/ui/button"
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
import { Textarea } from "@/components/ui/textarea"
import { useState } from "react"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Import missing hooks

The component is missing imports for hooks that would be needed for proper implementation.

"use client"

import type React from "react"

import Image from "next/image"
+ import { useRouter } from "next/navigation"

import { Button } from "@/components/ui/button"
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
import { Textarea } from "@/components/ui/textarea"
- import { useState } from "react"
+ import { useState, useEffect } from "react"
+ import { useToast } from "@/components/ui/use-toast"
📝 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
"use client"
import type React from "react"
import Image from "next/image"
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
import { Textarea } from "@/components/ui/textarea"
import { useState } from "react"
"use client"
import type React from "react"
import Image from "next/image"
import { useRouter } from "next/navigation"
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
import { Textarea } from "@/components/ui/textarea"
import { useState, useEffect } from "react"
import { useToast } from "@/components/ui/use-toast"

Comment on lines +19 to +26
const handleAnalyze = () => {
setIsAnalyzing(true)
// Simulate API call
setTimeout(() => {
setIsAnalyzing(false)
setResultsReady(true)
}, 2000)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Implement actual API integration instead of simulation.

The current implementation uses a setTimeout to simulate an API call rather than making an actual request to a backend service. This should be replaced with a real API call before production.

  const handleAnalyze = () => {
    setIsAnalyzing(true)
-   // Simulate API call
-   setTimeout(() => {
-     setIsAnalyzing(false)
-     setResultsReady(true)
-   }, 2000)
+   // Make actual API call to categorize air quality
+   const formData = {
+     standard,
+     pm25: document.getElementById('pm25').value,
+     pm10: document.getElementById('pm10').value,
+     no2: document.getElementById('no2').value,
+     o3: document.getElementById('o3').value,
+     co: document.getElementById('co').value,
+     so2: document.getElementById('so2').value
+   }
+   
+   fetch('/api/categorize', {
+     method: 'POST',
+     headers: {
+       'Content-Type': 'application/json',
+     },
+     body: JSON.stringify(formData),
+   })
+     .then(response => response.json())
+     .then(data => {
+       // Process the response data
+       setIsAnalyzing(false)
+       setResultsReady(true)
+       // Additional state updates based on API response
+     })
+     .catch(error => {
+       console.error('Error categorizing air quality:', error)
+       setIsAnalyzing(false)
+       // Handle error state
+     })
  }
📝 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 handleAnalyze = () => {
setIsAnalyzing(true)
// Simulate API call
setTimeout(() => {
setIsAnalyzing(false)
setResultsReady(true)
}, 2000)
}
const handleAnalyze = () => {
setIsAnalyzing(true)
// Make actual API call to categorize air quality
const formData = {
standard,
pm25: document.getElementById('pm25').value,
pm10: document.getElementById('pm10').value,
no2: document.getElementById('no2').value,
o3: document.getElementById('o3').value,
co: document.getElementById('co').value,
so2: document.getElementById('so2').value
}
fetch('/api/categorize', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(formData),
})
.then(response => response.json())
.then(data => {
// Process the response data
setIsAnalyzing(false)
setResultsReady(true)
// Additional state updates based on API response
})
.catch(error => {
console.error('Error categorizing air quality:', error)
setIsAnalyzing(false)
// Handle error state
})
}

Comment on lines +84 to +129
<CardContent className="space-y-6">
<div className="space-y-2">
<Label htmlFor="standard">Air Quality Standard</Label>
<Select value={standard} onValueChange={setStandard}>
<SelectTrigger id="standard">
<SelectValue placeholder="Select standard" />
</SelectTrigger>
<SelectContent>
<SelectItem value="who">WHO Guidelines</SelectItem>
<SelectItem value="us-epa">US EPA AQI</SelectItem>
<SelectItem value="eu">European Union Standards</SelectItem>
<SelectItem value="custom">Custom Thresholds</SelectItem>
</SelectContent>
</Select>
</div>

<div className="space-y-2">
<Label htmlFor="pm25">PM2.5 (μg/m³)</Label>
<Input id="pm25" type="number" defaultValue="15" />
</div>

<div className="space-y-2">
<Label htmlFor="pm10">PM10 (μg/m³)</Label>
<Input id="pm10" type="number" defaultValue="30" />
</div>

<div className="space-y-2">
<Label htmlFor="no2">NO₂ (ppb)</Label>
<Input id="no2" type="number" defaultValue="25" />
</div>

<div className="space-y-2">
<Label htmlFor="o3">O₃ (ppb)</Label>
<Input id="o3" type="number" defaultValue="40" />
</div>

<div className="space-y-2">
<Label htmlFor="co">CO (ppm)</Label>
<Input id="co" type="number" defaultValue="1.5" />
</div>

<div className="space-y-2">
<Label htmlFor="so2">SO₂ (ppb)</Label>
<Input id="so2" type="number" defaultValue="10" />
</div>
</CardContent>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add state management for form inputs.

The form inputs currently use defaultValue props but lack proper state management. This means user inputs aren't being tracked for submission.

Implement state management for all form inputs:

+ const [formValues, setFormValues] = useState({
+   pm25: "15",
+   pm10: "30",
+   no2: "25",
+   o3: "40",
+   co: "1.5",
+   so2: "10"
+ })
+
+ const handleInputChange = (e) => {
+   const { id, value } = e.target
+   setFormValues(prev => ({
+     ...prev,
+     [id]: value
+   }))
+ }

// Then update each input field like:
- <Input id="pm25" type="number" defaultValue="15" />
+ <Input 
+   id="pm25" 
+   type="number" 
+   value={formValues.pm25} 
+   onChange={handleInputChange} 
+ />
📝 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
<CardContent className="space-y-6">
<div className="space-y-2">
<Label htmlFor="standard">Air Quality Standard</Label>
<Select value={standard} onValueChange={setStandard}>
<SelectTrigger id="standard">
<SelectValue placeholder="Select standard" />
</SelectTrigger>
<SelectContent>
<SelectItem value="who">WHO Guidelines</SelectItem>
<SelectItem value="us-epa">US EPA AQI</SelectItem>
<SelectItem value="eu">European Union Standards</SelectItem>
<SelectItem value="custom">Custom Thresholds</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label htmlFor="pm25">PM2.5 (μg/m³)</Label>
<Input id="pm25" type="number" defaultValue="15" />
</div>
<div className="space-y-2">
<Label htmlFor="pm10">PM10 (μg/m³)</Label>
<Input id="pm10" type="number" defaultValue="30" />
</div>
<div className="space-y-2">
<Label htmlFor="no2">NO₂ (ppb)</Label>
<Input id="no2" type="number" defaultValue="25" />
</div>
<div className="space-y-2">
<Label htmlFor="o3">O₃ (ppb)</Label>
<Input id="o3" type="number" defaultValue="40" />
</div>
<div className="space-y-2">
<Label htmlFor="co">CO (ppm)</Label>
<Input id="co" type="number" defaultValue="1.5" />
</div>
<div className="space-y-2">
<Label htmlFor="so2">SO₂ (ppb)</Label>
<Input id="so2" type="number" defaultValue="10" />
</div>
</CardContent>
import { useState } from "react"
// ... other imports
export default function CategorizePage() {
// Add state management for form inputs.
const [formValues, setFormValues] = useState({
pm25: "15",
pm10: "30",
no2: "25",
o3: "40",
co: "1.5",
so2: "10"
})
const handleInputChange = (e) => {
const { id, value } = e.target
setFormValues((prev) => ({
...prev,
[id]: value
}))
}
return (
<CardContent className="space-y-6">
<div className="space-y-2">
<Label htmlFor="standard">Air Quality Standard</Label>
<Select value={standard} onValueChange={setStandard}>
<SelectTrigger id="standard">
<SelectValue placeholder="Select standard" />
</SelectTrigger>
<SelectContent>
<SelectItem value="who">WHO Guidelines</SelectItem>
<SelectItem value="us-epa">US EPA AQI</SelectItem>
<SelectItem value="eu">European Union Standards</SelectItem>
<SelectItem value="custom">Custom Thresholds</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label htmlFor="pm25">PM2.5 (μg/m³)</Label>
<Input
id="pm25"
type="number"
value={formValues.pm25}
onChange={handleInputChange}
/>
</div>
<div className="space-y-2">
<Label htmlFor="pm10">PM10 (μg/m³)</Label>
<Input
id="pm10"
type="number"
value={formValues.pm10}
onChange={handleInputChange}
/>
</div>
<div className="space-y-2">
<Label htmlFor="no2">NO₂ (ppb)</Label>
<Input
id="no2"
type="number"
value={formValues.no2}
onChange={handleInputChange}
/>
</div>
<div className="space-y-2">
<Label htmlFor="o3">O₃ (ppb)</Label>
<Input
id="o3"
type="number"
value={formValues.o3}
onChange={handleInputChange}
/>
</div>
<div className="space-y-2">
<Label htmlFor="co">CO (ppm)</Label>
<Input
id="co"
type="number"
value={formValues.co}
onChange={handleInputChange}
/>
</div>
<div className="space-y-2">
<Label htmlFor="so2">SO₂ (ppb)</Label>
<Input
id="so2"
type="number"
value={formValues.so2}
onChange={handleInputChange}
/>
</div>
</CardContent>
)
}

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.

1 participant