Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Added devices overview page, sites coordinates generator(site creation), network dropdown, updated organisation dropdown #2375

Merged
merged 3 commits into from
Jan 13, 2025

Conversation

Codebmk
Copy link
Member

@Codebmk Codebmk commented Jan 13, 2025

Screenshots

image
image

Summary by CodeRabbit

Summary by CodeRabbit

Release Notes

  • New Features

    • Added devices overview page with advanced filtering, sorting, and pagination.
    • Introduced location coordinate optimization for site creation.
    • Implemented organization and network switching in sidebar.
    • Added a new Toaster component for notifications.
    • Introduced a Tooltip component for enhanced user guidance.
    • New environment variables for API configuration.
  • Improvements

    • Enhanced error handling and state management for devices.
    • Improved toast notifications system.
    • Enhanced tooltips and UI interactions.
  • Configuration

    • Updated environment variable configuration.
    • Added new API integration settings.
    • Introduced new dependencies for improved functionality.

Copy link

coderabbitai bot commented Jan 13, 2025

📝 Walkthrough

Walkthrough

This pull request introduces several enhancements to the netmanager-app, focusing on configuration, device management, and user interface improvements. The changes include adding new environment variables, implementing device overview functionality, introducing a toaster notification system, and creating new API and Redux slice for device management. The modifications span across configuration files, components, hooks, and state management to provide a more robust and interactive application experience.

Changes

File Change Summary
.env.example Added three new environment variables: NEXT_PUBLIC_API_TOKEN, NEXT_PUBLIC_API_URL, NEXT_PUBLIC_ENV
.gitignore Added .env.development to ignore development-specific environment files
app/(authenticated)/devices/overview/page.tsx New DevicesPage component for device management with search, sorting, and pagination
app/(authenticated)/layout.tsx Added Toaster component for notifications
app/types/devices.ts Introduced new TypeScript interfaces for devices, sites, and grids
core/apis/devices.ts Added getDevicesSummary method for fetching device data
core/redux/slices/devicesSlice.ts Created new Redux slice for managing device state
next.config.js Added configuration for development environment rewrites and CORS headers
package.json Added dependencies: @radix-ui/react-tooltip, next-themes, sonner

Possibly related PRs

Suggested Reviewers

  • OchiengPaul442
  • Baalmart

Poem

🌐 Devices dance in digital light,
Configurations set just right,
Redux and APIs intertwine,
A netmanager app that truly shines!
Code's symphony, a tech delight 🚀


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

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

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

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

CodeRabbit Commands (Invoked using PR comments)

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

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai 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

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 9

🧹 Nitpick comments (12)
netmanager-app/core/apis/devices.ts (1)

19-24: Improve error handling robustness

The current error handling could be more informative and robust.

Consider this enhancement:

-      const axiosError = error as AxiosError<ErrorResponse>;
-      throw new Error(
-        axiosError.response?.data?.message || "Failed to fetch devices summary"
-      );
+      if (error instanceof AxiosError) {
+        const message = error.response?.data?.message || error.message;
+        throw new Error(`Failed to fetch devices summary: ${message}`);
+      }
+      throw error;
netmanager-app/components/ui/sonner.tsx (1)

8-29: Enhance type safety and code organization

The component implementation looks good, but could benefit from some TypeScript enhancements and code organization improvements.

Consider these improvements:

+ const TOAST_CLASSES = {
+   toast: "group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg",
+   description: "group-[.toast]:text-muted-foreground",
+   actionButton: "group-[.toast]:bg-primary group-[.toast]:text-primary-foreground",
+   cancelButton: "group-[.toast]:bg-muted group-[.toast]:text-muted-foreground",
+ } as const;

- const Toaster = ({ ...props }: ToasterProps) => {
+ const Toaster = ({ ...props }: ToasterProps): JSX.Element => {
   const { theme = "system" } = useTheme();

   return (
     <Sonner
       theme={theme as ToasterProps["theme"]}
       className="toaster group"
       toastOptions={{
-        classNames: {
-          toast: "group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg",
-          description: "group-[.toast]:text-muted-foreground",
-          actionButton: "group-[.toast]:bg-primary group-[.toast]:text-primary-foreground",
-          cancelButton: "group-[.toast]:bg-muted group-[.toast]:text-muted-foreground",
-        },
+        classNames: TOAST_CLASSES,
       }}
       {...props}
     />
   );
 };
netmanager-app/components/ui/tooltip.tsx (1)

1-30: Clean implementation of Radix UI tooltip components! 👍

The implementation is well-structured and follows React best practices. The use of Radix UI primitives ensures good accessibility out of the box.

Consider adding aria-label support through props to enhance accessibility further:

 const TooltipContent = React.forwardRef<
   React.ElementRef<typeof TooltipPrimitive.Content>,
   React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
->(({ className, sideOffset = 4, ...props }, ref) => (
+>(({ className, sideOffset = 4, "aria-label": ariaLabel, ...props }, ref) => (
   <TooltipPrimitive.Content
     ref={ref}
     sideOffset={sideOffset}
+    aria-label={ariaLabel}
     className={cn(
netmanager-app/core/redux/slices/devicesSlice.ts (1)

5-9: Consider expanding the DevicesState interface

The current state interface could benefit from additional fields to handle common state management scenarios.

Consider adding these fields to improve state management:

 interface DevicesState {
   devices: Device[];
   error: string | null;
   selectedDevice: Device | null;
+  isLoading: boolean;
+  pagination: {
+    currentPage: number;
+    totalPages: number;
+    pageSize: number;
+  };
+  filters: {
+    status?: string;
+    category?: string;
+    isOnline?: boolean;
+  };
 }
netmanager-app/app/types/devices.ts (2)

11-22: Enhance site_category type safety

The site_category object could benefit from more specific types and validation.

Consider adding these improvements:

   site_category: {
-    tags: string[];
+    tags: readonly string[];
     area_name: string;
-    category: string;
+    category: "residential" | "commercial" | "industrial" | "rural";
     highway: string;
     landuse: string;
-    latitude: number;
-    longitude: number;
+    latitude: Readonly<number & { _brand: "latitude" }>;
+    longitude: Readonly<number & { _brand: "longitude" }>;
     natural: string;
-    search_radius: number;
+    search_radius: number & { _brand: "meters" };
     waterway: string;
   };

34-56: Add validation constraints to Device interface

Some fields in the Device interface could benefit from more specific types and validation.

Consider adding these constraints:

 export interface Device {
   _id: string;
   isOnline: boolean;
-  device_codes: string[];
+  device_codes: readonly string[];
   status: string;
-  category: string;
+  category: "monitor" | "calibrator" | "other";
   isActive: boolean;
   description: string;
   name: string;
   network: string;
   long_name: string;
-  createdAt: string;
+  createdAt: `${number}-${number}-${number}T${number}:${number}:${number}Z`;
   authRequired: boolean;
   serial_number: string;
   api_code: string;
-  latitude: number;
-  longitude: number;
+  latitude: number & { _brand: "latitude" };
+  longitude: number & { _brand: "longitude" };
netmanager-app/core/hooks/useSites.ts (1)

35-56: Consider using a more specific error type.

While the implementation is solid, consider defining a specific error type for coordinate-related errors to improve error handling predictability.

  } = useMutation<
    ApproximateCoordinatesResponse,
-   Error,
+   { code: string; message: string },
    { latitude: string; longitude: string }
  >({
netmanager-app/app/(authenticated)/sites/create-site-form.tsx (1)

270-314: Add error handling and coordinate validation.

The optimize location feature works well but could be improved:

  1. Add error toast notifications for failed optimizations
  2. Validate coordinate format before optimization
 onClick={() => {
   const lat = form.getValues("latitude");
   const lng = form.getValues("longitude");
-  if (lat && lng) {
+  const isValidCoord = (coord: string) => /^-?\d+\.?\d*$/.test(coord);
+  if (lat && lng && isValidCoord(lat) && isValidCoord(lng)) {
     getApproximateCoordinates(
       { latitude: lat, longitude: lng },
       {
         onSuccess: (data) => {
           const {
             approximate_latitude,
             approximate_longitude,
           } = data.approximate_coordinates;
           form.setValue(
             "latitude",
             approximate_latitude.toString()
           );
           form.setValue(
             "longitude",
             approximate_longitude.toString()
           );
+          toast.success("Location coordinates optimized successfully");
         },
+        onError: (error) => {
+          toast.error(error.message || "Failed to optimize coordinates");
+        },
       }
     );
+  } else {
+    toast.error("Please enter valid latitude and longitude values");
   }
 }}
netmanager-app/app/(authenticated)/devices/overview/page.tsx (3)

66-86: Improve date handling in sort function.

The current date handling could be more robust by using a proper date parsing utility.

 if (sortField === "createdAt") {
-  const dateA = new Date(a.createdAt || 0).getTime();
-  const dateB = new Date(b.createdAt || 0).getTime();
+  // Use a library like date-fns for more robust date parsing
+  const parseDate = (date: string | null) => {
+    if (!date) return 0;
+    const parsed = Date.parse(date);
+    return isNaN(parsed) ? 0 : parsed;
+  };
+  const dateA = parseDate(a.createdAt);
+  const dateB = parseDate(b.createdAt);
   return sortOrder === "asc" ? dateA - dateB : dateB - dateA;
 }

88-99: Extract search fields configuration for better maintainability.

Consider moving the searchable fields into a configuration object for easier maintenance.

+const SEARCHABLE_FIELDS = {
+  direct: ['name', 'long_name', '_id', 'description'],
+  array: [{
+    field: 'device_codes',
+    searchFn: (codes: string[], query: string) => 
+      codes.some(code => code.toLowerCase().includes(query))
+  }]
+};

 const filteredDevices = devices.filter((device: Device) => {
   const searchLower = searchQuery.toLowerCase();
-  return (
-    device.name?.toLowerCase().includes(searchLower) ||
-    device.long_name?.toLowerCase().includes(searchLower) ||
-    device._id?.toLowerCase().includes(searchLower) ||
-    device.description?.toLowerCase().includes(searchLower) ||
-    device.device_codes.some((code) =>
-      code.toLowerCase().includes(searchLower)
-    )
-  );
+  return SEARCHABLE_FIELDS.direct.some(field => 
+    device[field]?.toLowerCase().includes(searchLower)
+  ) || SEARCHABLE_FIELDS.array.some(({ field, searchFn }) =>
+    searchFn(device[field], searchLower)
+  );
 });

321-337: Type device statuses and extract status styles configuration.

Consider typing the status values and extracting the status styles for better maintainability.

+type DeviceStatus = 'deployed' | 'recalled' | 'not_deployed';

+const STATUS_STYLES: Record<DeviceStatus, { bg: string; text: string }> = {
+  deployed: { bg: 'bg-green-100', text: 'text-green-800' },
+  recalled: { bg: 'bg-yellow-100', text: 'text-yellow-800' },
+  not_deployed: { bg: 'bg-red-100', text: 'text-red-800' }
+};

+const STATUS_LABELS: Record<DeviceStatus, string> = {
+  deployed: 'Deployed',
+  recalled: 'Recalled',
+  not_deployed: 'Not Deployed'
+};

-className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${
-  device.status === "deployed"
-    ? "bg-green-100 text-green-800"
-    : device.status === "recalled"
-    ? "bg-yellow-100 text-yellow-800"
-    : "bg-red-100 text-red-800"
-}`}
+className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${
+  STATUS_STYLES[device.status as DeviceStatus].bg
+  STATUS_STYLES[device.status as DeviceStatus].text
+}`}
>
-{device.status === "deployed"
-  ? "Deployed"
-  : device.status === "recalled"
-  ? "Recalled"
-  : "Not Deployed"}
+{STATUS_LABELS[device.status as DeviceStatus]}
netmanager-app/components/sidebar.tsx (1)

95-125: Consider accessibility improvements for the organization switcher.

While the implementation is solid, consider adding ARIA labels and keyboard navigation support for better accessibility.

 <Button
   variant="secondary"
   className="w-full justify-between uppercase"
+  aria-label={`Current organization: ${activeGroup?.grp_title || "None selected"}`}
 >
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 3a79a45 and 71ec580.

⛔ Files ignored due to path filters (1)
  • netmanager-app/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (17)
  • netmanager-app/.env.example (1 hunks)
  • netmanager-app/.gitignore (1 hunks)
  • netmanager-app/app/(authenticated)/devices/overview/page.tsx (1 hunks)
  • netmanager-app/app/(authenticated)/layout.tsx (1 hunks)
  • netmanager-app/app/(authenticated)/sites/create-site-form.tsx (3 hunks)
  • netmanager-app/app/types/devices.ts (1 hunks)
  • netmanager-app/components/sidebar.tsx (7 hunks)
  • netmanager-app/components/ui/sonner.tsx (1 hunks)
  • netmanager-app/components/ui/tooltip.tsx (1 hunks)
  • netmanager-app/core/apis/devices.ts (1 hunks)
  • netmanager-app/core/apis/sites.ts (2 hunks)
  • netmanager-app/core/hooks/useDevices.ts (1 hunks)
  • netmanager-app/core/hooks/useSites.ts (2 hunks)
  • netmanager-app/core/redux/slices/devicesSlice.ts (1 hunks)
  • netmanager-app/core/redux/store.ts (1 hunks)
  • netmanager-app/next.config.js (1 hunks)
  • netmanager-app/package.json (2 hunks)
✅ Files skipped from review due to trivial changes (2)
  • netmanager-app/.gitignore
  • netmanager-app/.env.example
🔇 Additional comments (12)
netmanager-app/next.config.js (2)

1-2: Clean type definition! 👍

The JSDoc type annotation ensures proper TypeScript support for the Next.js configuration.


1-35: Verify configuration compatibility

Let's ensure this configuration works with the rest of your application setup.

✅ Verification successful

Configuration is compatible and well-isolated

The Next.js configuration is properly scoped to the netmanager-app and won't conflict with other parts of the codebase. The development-only rewrite rule and CORS headers are safely implemented.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for environment variable usage and related configuration files

# Look for environment variable references
echo "Checking environment variable usage..."
rg "NEXT_PUBLIC_ENV|API_BASE_URL" --type js --type ts

# Check for existing next.config.js in other directories
echo "Checking for other Next.js configs..."
fd "next.config.js"

# Look for related environment files
echo "Checking environment files..."
fd ".env"

Length of output: 3025


Script:

#!/bin/bash
# Check platform next.config.js content
echo "Checking platform next.config.js content..."
cat platform/next.config.js

# Look for staging-analytics.airqo.net references
echo -e "\nChecking staging analytics URL references..."
rg "staging-analytics.airqo.net" --type js --type ts --type json

Length of output: 1350

netmanager-app/app/(authenticated)/layout.tsx (1)

5-5: Nice work on the Toaster integration!

The placement of the Toaster component at the authenticated layout level is a solid choice, ensuring toast notifications are consistently available across all authenticated routes.

Also applies to: 12-17

netmanager-app/core/redux/store.ts (1)

4-4: Clean Redux store integration 👍

The devices reducer integration follows the established pattern and maintains a consistent store structure.

Also applies to: 10-10

netmanager-app/core/hooks/useSites.ts (1)

7-19: LGTM! Well-structured query implementation.

The hook correctly handles the dependency on both network and group context, with proper null checks in the enabled condition.

netmanager-app/core/apis/sites.ts (1)

7-22: Well-structured type definitions!

The interfaces are well-defined with proper optional fields and clear structure.

netmanager-app/app/(authenticated)/devices/overview/page.tsx (1)

358-407: Well-implemented pagination logic!

The pagination implementation handles edge cases properly and provides good user feedback.

netmanager-app/components/sidebar.tsx (4)

42-46: LGTM! Clean import organization.

The imports are well-organized, with related Redux actions grouped together. The type imports are properly separated.


62-63: LGTM! Proper state management setup.

The Redux selectors are correctly implemented using the useAppSelector hook.


88-91: LGTM! Consistent state management pattern.

The handleOrganizationChange function follows the same pattern as handleNetworkChange, maintaining consistency in state management.


292-292: LGTM! Consistent path updates.

The navigation paths have been consistently updated to use /devices/overview throughout the component.

Also applies to: 312-314

netmanager-app/package.json (1)

19-19: Verify dependency versions and check for security advisories.

The new dependencies have been added with caret ranges, which is good for minor version updates. However, let's verify these versions for security and compatibility.

Also applies to: 32-32, 38-38

✅ Verification successful

✓ Dependencies are secure and version ranges are appropriate

The security check revealed no known vulnerabilities for @radix-ui/react-tooltip, next-themes, and sonner. The use of caret ranges (^) is appropriate for these UI components, allowing for safe minor version updates while maintaining compatibility.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for security advisories for the new dependencies
echo "Checking @radix-ui/react-tooltip..."
gh api graphql -f query='
{
  securityVulnerabilities(first: 5, ecosystem: NPM, package: "@radix-ui/react-tooltip") {
    nodes {
      advisory {
        summary
        severity
        publishedAt
      }
      vulnerableVersionRange
    }
  }
}'

echo "Checking next-themes..."
gh api graphql -f query='
{
  securityVulnerabilities(first: 5, ecosystem: NPM, package: "next-themes") {
    nodes {
      advisory {
        summary
        severity
        publishedAt
      }
      vulnerableVersionRange
    }
  }
}'

echo "Checking sonner..."
gh api graphql -f query='
{
  securityVulnerabilities(first: 5, ecosystem: NPM, package: "sonner") {
    nodes {
      advisory {
        summary
        severity
        publishedAt
      }
      vulnerableVersionRange
    }
  }
}'

Length of output: 1059

Comment on lines +15 to +32
async headers() {
return [
{
source: "/api/v2/:path*",
headers: [
{ key: "Access-Control-Allow-Origin", value: "*" },
{
key: "Access-Control-Allow-Methods",
value: "GET,POST,PUT,DELETE,OPTIONS",
},
{
key: "Access-Control-Allow-Headers",
value: "Content-Type, Authorization",
},
],
},
];
},
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Security: Tighten CORS configuration

The current CORS configuration might be too permissive for production use:

  1. Using * for Access-Control-Allow-Origin in production is not recommended. Consider restricting it to specific domains.
  2. The allowed methods should be comma-separated with spaces for better compatibility.

Here's a suggested improvement:

headers: [
  { 
-   key: "Access-Control-Allow-Origin", 
-   value: "*" 
+   key: "Access-Control-Allow-Origin", 
+   value: process.env.NODE_ENV === "development" 
+     ? "*" 
+     : process.env.ALLOWED_ORIGINS 
  },
  {
    key: "Access-Control-Allow-Methods",
-   value: "GET,POST,PUT,DELETE,OPTIONS",
+   value: "GET, POST, PUT, DELETE, OPTIONS",
  },

Also consider adding these security headers:

{
  key: 'Content-Security-Policy',
  value: "default-src 'self';"
},
{
  key: 'X-Frame-Options',
  value: 'DENY'
}

Comment on lines +3 to +13
async rewrites() {
if (process.env.NEXT_PUBLIC_ENV === "development") {
return [
{
source: "/api/v2/:path*",
destination: "https://staging-analytics.airqo.net/api/v2/:path*",
},
];
}
return [];
},
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider these configuration improvements

  1. Using NEXT_PUBLIC_ prefix for the environment variable isn't recommended here since this is server-side configuration. Consider using a regular environment variable instead.
  2. The staging URL should be configurable through environment variables rather than hardcoded.

Here's a suggested improvement:

- if (process.env.NEXT_PUBLIC_ENV === "development") {
+ if (process.env.NODE_ENV === "development") {
  return [
    {
      source: "/api/v2/:path*",
-     destination: "https://staging-analytics.airqo.net/api/v2/:path*",
+     destination: `${process.env.API_BASE_URL}/api/v2/:path*`,
    },
  ];
}

Don't forget to update your environment files to include API_BASE_URL.

📝 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
async rewrites() {
if (process.env.NEXT_PUBLIC_ENV === "development") {
return [
{
source: "/api/v2/:path*",
destination: "https://staging-analytics.airqo.net/api/v2/:path*",
},
];
}
return [];
},
async rewrites() {
if (process.env.NODE_ENV === "development") {
return [
{
source: "/api/v2/:path*",
destination: `${process.env.API_BASE_URL}/api/v2/:path*`,
},
];
}
return [];
},

Comment on lines +15 to +17
const response = await axiosInstance.get<DevicesSummaryResponse>(
`${DEVICES_MGT_URL}/summary?network=${networkId}&group=${groupName}`
);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Enhance URL parameter safety

The URL parameters should be properly encoded to prevent potential injection vulnerabilities.

Here's how to fix it:

-        `${DEVICES_MGT_URL}/summary?network=${networkId}&group=${groupName}`
+        `${DEVICES_MGT_URL}/summary?network=${encodeURIComponent(networkId)}&group=${encodeURIComponent(groupName)}`
📝 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 response = await axiosInstance.get<DevicesSummaryResponse>(
`${DEVICES_MGT_URL}/summary?network=${networkId}&group=${groupName}`
);
const response = await axiosInstance.get<DevicesSummaryResponse>(
`${DEVICES_MGT_URL}/summary?network=${encodeURIComponent(networkId)}&group=${encodeURIComponent(groupName)}`
);

Comment on lines +21 to +24
setDevices(state: DevicesState, action: PayloadAction<Device[]>) {
state.devices = action.payload;
state.error = null;
},
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add error handling for edge cases

The setDevices reducer should handle potential edge cases.

Consider adding validation:

 setDevices(state: DevicesState, action: PayloadAction<Device[]>) {
+  if (!Array.isArray(action.payload)) {
+    state.error = "Invalid devices data received";
+    return;
+  }
   state.devices = action.payload;
   state.error = null;
 },
📝 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
setDevices(state: DevicesState, action: PayloadAction<Device[]>) {
state.devices = action.payload;
state.error = null;
},
setDevices(state: DevicesState, action: PayloadAction<Device[]>) {
if (!Array.isArray(action.payload)) {
state.error = "Invalid devices data received";
return;
}
state.devices = action.payload;
state.error = null;
},

Comment on lines +21 to +27
onSuccess: (data, error) => {
if (error) {
dispatch(setError(error.message));
} else if (data) {
dispatch(setDevices(data.devices));
}
},
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix incorrect error handling logic

The error handling in the onSuccess callback is logically incorrect. The onSuccess callback won't be called with an error parameter.

Implement proper error handling:

-    onSuccess: (data, error) => {
-      if (error) {
-        dispatch(setError(error.message));
-      } else if (data) {
-        dispatch(setDevices(data.devices));
-      }
+    onSuccess: (data) => {
+      dispatch(setDevices(data.devices));
     },
+    onError: (error: Error) => {
+      dispatch(setError(error.message));
+    },
📝 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
onSuccess: (data, error) => {
if (error) {
dispatch(setError(error.message));
} else if (data) {
dispatch(setDevices(data.devices));
}
},
onSuccess: (data) => {
dispatch(setDevices(data.devices));
},
onError: (error: Error) => {
dispatch(setError(error.message));
},

Comment on lines +13 to +28
const { data, isLoading, error } = useQuery<DevicesSummaryResponse, Error>({
queryKey: ["devices", activeNetwork?.net_name, activeGroup?.grp_title],
queryFn: () =>
devices.getDevicesSummary(
activeNetwork?.net_name || "",
activeGroup?.grp_title || ""
),
enabled: !!activeNetwork?.net_name && !!activeGroup?.grp_title,
onSuccess: (data, error) => {
if (error) {
dispatch(setError(error.message));
} else if (data) {
dispatch(setDevices(data.devices));
}
},
});
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add retry configuration and error boundaries

The query configuration could benefit from retry logic and better error handling.

Add retry configuration and validate network/group:

 const { data, isLoading, error } = useQuery<DevicesSummaryResponse, Error>({
   queryKey: ["devices", activeNetwork?.net_name, activeGroup?.grp_title],
   queryFn: () => {
+    if (!activeNetwork?.net_name || !activeGroup?.grp_title) {
+      throw new Error("Network and group must be selected");
+    }
     return devices.getDevicesSummary(
       activeNetwork?.net_name || "",
       activeGroup?.grp_title || ""
     );
   },
   enabled: !!activeNetwork?.net_name && !!activeGroup?.grp_title,
+  retry: (failureCount, error) => {
+    return failureCount < 3 && !error.message.includes("Network and group must be selected");
+  },
+  retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000),
📝 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 { data, isLoading, error } = useQuery<DevicesSummaryResponse, Error>({
queryKey: ["devices", activeNetwork?.net_name, activeGroup?.grp_title],
queryFn: () =>
devices.getDevicesSummary(
activeNetwork?.net_name || "",
activeGroup?.grp_title || ""
),
enabled: !!activeNetwork?.net_name && !!activeGroup?.grp_title,
onSuccess: (data, error) => {
if (error) {
dispatch(setError(error.message));
} else if (data) {
dispatch(setDevices(data.devices));
}
},
});
const { data, isLoading, error } = useQuery<DevicesSummaryResponse, Error>({
queryKey: ["devices", activeNetwork?.net_name, activeGroup?.grp_title],
queryFn: () => {
if (!activeNetwork?.net_name || !activeGroup?.grp_title) {
throw new Error("Network and group must be selected");
}
return devices.getDevicesSummary(
activeNetwork?.net_name || "",
activeGroup?.grp_title || ""
);
},
enabled: !!activeNetwork?.net_name && !!activeGroup?.grp_title,
retry: (failureCount, error) => {
return failureCount < 3 && !error.message.includes("Network and group must be selected");
},
retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000),
onSuccess: (data, error) => {
if (error) {
dispatch(setError(error.message));
} else if (data) {
dispatch(setDevices(data.devices));
}
},
});

Comment on lines +25 to 36
getSitesSummary: async (networkId: string, groupName: string) => {
try {
const response = await axiosInstance.get(
`${SITES_MGT_URL}/summary?network=${networkId}`
`${SITES_MGT_URL}/summary?network=${networkId}&group=${groupName}`
);
return response.data;
} catch (error: any) {
} catch (error) {
const axiosError = error as AxiosError<ErrorResponse>;
throw new Error(
error.response?.data?.message || "Failed to fetch sites summary"
axiosError.response?.data?.message || "Failed to fetch sites summary"
);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add URL parameter encoding for safety.

The URL parameters should be encoded to prevent potential injection attacks.

-        `${SITES_MGT_URL}/summary?network=${networkId}&group=${groupName}`
+        `${SITES_MGT_URL}/summary?network=${encodeURIComponent(networkId)}&group=${encodeURIComponent(groupName)}`
📝 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
getSitesSummary: async (networkId: string, groupName: string) => {
try {
const response = await axiosInstance.get(
`${SITES_MGT_URL}/summary?network=${networkId}`
`${SITES_MGT_URL}/summary?network=${networkId}&group=${groupName}`
);
return response.data;
} catch (error: any) {
} catch (error) {
const axiosError = error as AxiosError<ErrorResponse>;
throw new Error(
error.response?.data?.message || "Failed to fetch sites summary"
axiosError.response?.data?.message || "Failed to fetch sites summary"
);
}
getSitesSummary: async (networkId: string, groupName: string) => {
try {
const response = await axiosInstance.get(
`${SITES_MGT_URL}/summary?network=${encodeURIComponent(networkId)}&group=${encodeURIComponent(groupName)}`
);
return response.data;
} catch (error) {
const axiosError = error as AxiosError<ErrorResponse>;
throw new Error(
axiosError.response?.data?.message || "Failed to fetch sites summary"
);
}

Comment on lines +56 to 71
getApproximateCoordinates: async (
latitude: string,
longitude: string
): Promise<ApproximateCoordinatesResponse> => {
try {
const response = await axiosInstance.get<ApproximateCoordinatesResponse>(
`${SITES_MGT_URL}/approximate?latitude=${latitude}&longitude=${longitude}`
);
return response.data;
} catch (error) {
const axiosError = error as AxiosError<ErrorResponse>;
throw new Error(
axiosError.response?.data?.message ||
"Failed to get approximate coordinates"
);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add URL parameter encoding for coordinate values.

Similar to the previous issue, coordinate values in the URL should be encoded.

-        `${SITES_MGT_URL}/approximate?latitude=${latitude}&longitude=${longitude}`
+        `${SITES_MGT_URL}/approximate?latitude=${encodeURIComponent(latitude)}&longitude=${encodeURIComponent(longitude)}`
📝 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
getApproximateCoordinates: async (
latitude: string,
longitude: string
): Promise<ApproximateCoordinatesResponse> => {
try {
const response = await axiosInstance.get<ApproximateCoordinatesResponse>(
`${SITES_MGT_URL}/approximate?latitude=${latitude}&longitude=${longitude}`
);
return response.data;
} catch (error) {
const axiosError = error as AxiosError<ErrorResponse>;
throw new Error(
axiosError.response?.data?.message ||
"Failed to get approximate coordinates"
);
}
getApproximateCoordinates: async (
latitude: string,
longitude: string
): Promise<ApproximateCoordinatesResponse> => {
try {
const response = await axiosInstance.get<ApproximateCoordinatesResponse>(
`${SITES_MGT_URL}/approximate?latitude=${encodeURIComponent(latitude)}&longitude=${encodeURIComponent(longitude)}`
);
return response.data;
} catch (error) {
const axiosError = error as AxiosError<ErrorResponse>;
throw new Error(
axiosError.response?.data?.message ||
"Failed to get approximate coordinates"
);
}

Comment on lines +236 to +272
{/* Network Switcher */}
<div className="mb-4">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="outline"
className="w-full justify-between text-foreground"
>
<div className="flex items-center gap-2">
<NetworkIcon size={18} />
<span className="uppercase">
{activeNetwork?.net_name || "Select Network"}
</span>
</div>
<ChevronDown size={16} />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-56">
<DropdownMenuLabel>Select Network</DropdownMenuLabel>
<DropdownMenuSeparator />
{availableNetworks
.filter((network) => network._id === activeNetwork?._id)
.map((network) => (
<DropdownMenuItem
key={network._id}
onClick={() => handleNetworkChange(network)}
className="flex items-center justify-between uppercase"
>
{network.net_name}
{activeNetwork?._id === network._id && (
<Check size={16} />
)}
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
</div>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Verify the network filtering logic.

The network filtering logic seems overly restrictive:

.filter((network) => network._id === activeNetwork?._id)

This will only show the currently active network, preventing users from switching to other networks.

-availableNetworks
-  .filter((network) => network._id === activeNetwork?._id)
+availableNetworks

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
netmanager-app/app/(authenticated)/sites/create-site-form.tsx (1)

37-39: Consider renaming the Error import

The imported Error from 'next/error' shadows the global Error object, which could lead to confusion. Consider renaming it to something more specific like NextError.

-import Error from "next/error";
+import NextError from "next/error";
🧰 Tools
🪛 Biome (1.9.4)

[error] 39-39: Do not shadow the global "Error" 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)

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 71ec580 and cb48de4.

📒 Files selected for processing (1)
  • netmanager-app/app/(authenticated)/sites/create-site-form.tsx (3 hunks)
🧰 Additional context used
🪛 Biome (1.9.4)
netmanager-app/app/(authenticated)/sites/create-site-form.tsx

[error] 39-39: Do not shadow the global "Error" 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)


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

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

(lint/suspicious/noGlobalIsNan)


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

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

(lint/suspicious/noGlobalIsNan)

🔇 Additional comments (2)
netmanager-app/app/(authenticated)/sites/create-site-form.tsx (2)

144-144: Well-implemented coordinate optimization!

The coordinate optimization feature is thoughtfully implemented with:

  • Proper loading states and user feedback
  • Disabled state handling
  • Clean error handling
  • Type-safe API integration

Also applies to: 289-333


289-333: Clean and user-friendly UI implementation

The UI changes maintain consistency with the existing design while providing clear feedback through:

  • Loading indicators
  • Disabled states
  • Clear button labels
  • Proper spacing and layout

Comment on lines +45 to +62
latitude: z.string().refine(
(val) => {
const num = parseFloat(val);
return !isNaN(num) && num >= -90 && num <= 90;
},
{
message: "Latitude must be a valid number between -90 and 90",
}
),
longitude: z.string().refine(
(val) => {
const num = parseFloat(val);
return !isNaN(num) && num >= -180 && num <= 180;
},
{
message: "Longitude must be a valid number between -180 and 180",
}
),
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Use Number.isNaN for safer number validation

The latitude and longitude validation uses the unsafe global isNaN which performs type coercion. Replace it with Number.isNaN for more predictable validation.

  latitude: z.string().refine(
    (val) => {
      const num = parseFloat(val);
-     return !isNaN(num) && num >= -90 && num <= 90;
+     return !Number.isNaN(num) && num >= -90 && num <= 90;
    },
    {
      message: "Latitude must be a valid number between -90 and 90",
    }
  ),
  longitude: z.string().refine(
    (val) => {
      const num = parseFloat(val);
-     return !isNaN(num) && num >= -180 && num <= 180;
+     return !Number.isNaN(num) && num >= -180 && num <= 180;
    },
    {
      message: "Longitude must be a valid number between -180 and 180",
    }
  ),
📝 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
latitude: z.string().refine(
(val) => {
const num = parseFloat(val);
return !isNaN(num) && num >= -90 && num <= 90;
},
{
message: "Latitude must be a valid number between -90 and 90",
}
),
longitude: z.string().refine(
(val) => {
const num = parseFloat(val);
return !isNaN(num) && num >= -180 && num <= 180;
},
{
message: "Longitude must be a valid number between -180 and 180",
}
),
latitude: z.string().refine(
(val) => {
const num = parseFloat(val);
return !Number.isNaN(num) && num >= -90 && num <= 90;
},
{
message: "Latitude must be a valid number between -90 and 90",
}
),
longitude: z.string().refine(
(val) => {
const num = parseFloat(val);
return !Number.isNaN(num) && num >= -180 && num <= 180;
},
{
message: "Longitude must be a valid number between -180 and 180",
}
),
🧰 Tools
🪛 Biome (1.9.4)

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

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

(lint/suspicious/noGlobalIsNan)


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

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

(lint/suspicious/noGlobalIsNan)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants