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

[Netmanager]: Clients page with permissions restrictions #2405

Merged
merged 7 commits into from
Jan 29, 2025

Conversation

danielmarv
Copy link
Member

@danielmarv danielmarv commented Jan 24, 2025

Summary of Changes (What does this PR do?)

  • Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change.

Status of maturity (all need to be checked before merging):

  • I've tested this locally
  • I consider this code done
  • This change ready to hit production in its current state
  • The title of the PR states what changed and the related issues number (used for the release note).
  • I've included issue number in the "Closes #ISSUE-NUMBER" part of the "What are the relevant tickets?" section to link the issue.
  • I've updated corresponding documentation for the changes in this PR.
  • I have written unit and/or e2e tests for my change(s).

How should this be manually tested?

  • Please include the steps to be done inorder to setup and test this PR.

What are the relevant tickets?

Screenshots (optional)

Summary by CodeRabbit

  • New Features

    • Added a Client Management page for viewing and managing client accounts.
    • Introduced client activation and deactivation functionality.
    • Added a new "Clients" navigation link in the sidebar.
  • UI Improvements

    • Implemented Alert Dialog components for user confirmations.
    • Enhanced sidebar navigation with new client management route.
  • Dependencies

    • Added Radix UI Alert Dialog library.
  • Type Definitions

    • Created new interfaces for Access Tokens and Client data structures.

Copy link

coderabbitai bot commented Jan 24, 2025

📝 Walkthrough

Walkthrough

This pull request introduces a comprehensive client management system in the NetManager application. The changes include a new ClientManagement page component, type definitions for clients and access tokens, UI components like alert dialogs, and API functions for client retrieval and activation. The implementation provides a robust interface for managing client accounts, with features like data fetching, activation/deactivation, and user-friendly date formatting.

Changes

File Change Summary
app/(authenticated)/clients/page.tsx Added ClientManagement component for client account management
app/types/clients.ts Introduced AccessToken and Client interfaces for type safety
components/ui/alert-dialog.tsx Created comprehensive alert dialog components using Radix UI
components/ui/button.tsx Minor stylistic formatting updates
core/apis/analytics.ts Added getClientsApi and activateUserClientApi functions
package.json Added @radix-ui/react-alert-dialog dependency
components/sidebar.tsx Added "Clients" navigation link with permission guard
components/clients/dialogs.tsx Implemented ActivateClientDialog and DeactivateClientDialog

Possibly related PRs

Suggested Reviewers

  • OchiengPaul442
  • Baalmart

Poem

🖥️ Clients dance in digital grace,
Tokens tick, a rhythmic pace
Activate, deactivate with care
Management's magic floating there
Code weaves its intricate embrace! 🌐


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

🧹 Nitpick comments (11)
netmanager-app/app/types/clients.ts (1)

3-15: Consider clarifying naming conventions

The fields in AccessToken (e.g., _id, __v, etc.) seem to reflect MongoDB schema naming, which is fine if it matches your backend. But consider standardizing naming conventions across your TypeScript code to avoid confusion, especially with underscores vs. camelCase for fields like client_id.

netmanager-app/core/apis/analytics.ts (2)

30-32: Ensure robust error handling

Current usage of .then((response) => response.data) is valid, but consider adding more error handling or using async/await with try-catch to handle network or server failures more gracefully.


34-38: Unify activation and deactivation logic

While named for activation, this endpoint could also handle deactivation, which might cause confusion. Consider making a more general endpoint or function name (e.g., updateUserClientStatusApi) to handle both states, or splitting the functionality clearly.

netmanager-app/app/pageAccess.tsx (2)

6-9: Add tests for the permission-based HOC

A well-tested HOC ensures that permission checks and redirects behave as expected. Implementing unit tests or integration tests can catch potential user flow issues early.


17-28: Provide feedback instead of returning null

When the user lacks permission (or while checking permissions), returning null can abruptly hide the entire page. Consider rendering a spinner, placeholder, or an informative message until the check completes. This improves usability by informing users about what's happening.

Also applies to: 30-36

netmanager-app/app/(authenticated)/clients/dialogs.tsx (1)

19-36: Refactor repeated dialog logic

Both ActivateClientDialog and DeactivateClientDialog share similar structure. Consider creating a reusable, parameterized dialog component to reduce duplication and improve maintainability. For example:

+function ClientActionDialog({
+  open,
+  onOpenChange,
+  onConfirm,
+  clientName,
+  actionLabel,
+  title,
+  description
+}: {
+  open: boolean;
+  onOpenChange: (open: boolean) => void;
+  onConfirm: () => void;
+  clientName?: string;
+  actionLabel: string;
+  title: string;
+  description: string;
+}) {
+  return (
+    <AlertDialog open={open} onOpenChange={onOpenChange}>
+      <AlertDialogContent>
+        <AlertDialogHeader>
+          <AlertDialogTitle>{title}</AlertDialogTitle>
+          <AlertDialogDescription>
+            {description} {clientName}? This action cannot be undone.
+          </AlertDialogDescription>
+        </AlertDialogHeader>
+        <AlertDialogFooter>
+          <AlertDialogCancel>Cancel</AlertDialogCancel>
+          <AlertDialogAction onClick={onConfirm}>{actionLabel}</AlertDialogAction>
+        </AlertDialogFooter>
+      </AlertDialogContent>
+    </AlertDialog>
+  );
+}

Also applies to: 38-55

netmanager-app/app/(authenticated)/clients/columns.tsx (1)

42-47: Consider consolidating token expiry logic
You compute token expiration here very similarly to how it's done in page.tsx. Extracting this logic into a shared utility could help you avoid duplication and keep your code DRY.

netmanager-app/app/(authenticated)/clients/data-table.tsx (2)

52-57: Improve filter input accessibility
Consider adding an aria-label or a more descriptive placeholder text for the filter input, e.g. “Filter clients by name,” to enhance accessibility for screen readers.


74-88: Add loading or empty states for better UX
Currently, you show "No results." when there is no data. You could expand on this to handle scenarios where data is still loading or to provide additional info for the user.

netmanager-app/app/(authenticated)/clients/page.tsx (2)

13-26: Avoid duplicating the expiry logic
The formatDate function duplicates the token expiry logic seen in columns.tsx. Consider moving it into a shared helper for consistency and easier maintenance.

🧰 Tools
🪛 Biome (1.9.4)

[error] 16-16: 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)


90-95: Leverage optional chaining
This filter could be made more concise by employing optional chaining on access_token.

- .filter((client) => client.access_token && client.access_token.expires)
+ .filter((client) => client.access_token?.expires)
🧰 Tools
🪛 Biome (1.9.4)

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

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between fa361ca and 7bc0bc3.

⛔ Files ignored due to path filters (1)
  • netmanager-app/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (10)
  • netmanager-app/app/(authenticated)/clients/columns.tsx (1 hunks)
  • netmanager-app/app/(authenticated)/clients/data-table.tsx (1 hunks)
  • netmanager-app/app/(authenticated)/clients/dialogs.tsx (1 hunks)
  • netmanager-app/app/(authenticated)/clients/page.tsx (1 hunks)
  • netmanager-app/app/pageAccess.tsx (1 hunks)
  • netmanager-app/app/types/clients.ts (1 hunks)
  • netmanager-app/components/ui/alert-dialog.tsx (1 hunks)
  • netmanager-app/components/ui/button.tsx (2 hunks)
  • netmanager-app/core/apis/analytics.ts (2 hunks)
  • netmanager-app/package.json (2 hunks)
✅ Files skipped from review due to trivial changes (1)
  • netmanager-app/components/ui/button.tsx
🧰 Additional context used
🪛 Biome (1.9.4)
netmanager-app/app/(authenticated)/clients/page.tsx

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

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


[error] 16-16: 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)

netmanager-app/app/(authenticated)/clients/columns.tsx

[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)

🔇 Additional comments (8)
netmanager-app/app/types/clients.ts (1)

1-2: Imports look good

Everything here appears straightforward, and referencing the UserDetails interface from ./users is appropriate for maintaining type consistency.

netmanager-app/app/(authenticated)/clients/dialogs.tsx (1)

1-17: Dialog props are well-structured

Using a dedicated interface for dialog props is a clean approach that ensures both dialogs are consistent and type-safe.

netmanager-app/app/(authenticated)/clients/columns.tsx (1)

16-19: Interface definition is well-structured
The ColumnProps interface succinctly defines the callback functions for activating and deactivating clients, promoting clear usage and strong typing.

netmanager-app/app/(authenticated)/clients/data-table.tsx (1)

29-46: Well-structured table configuration
Your usage of useReactTable for sorting, filtering, and pagination is neatly integrated, making the table easy to maintain and extend. Nicely done.

netmanager-app/components/ui/alert-dialog.tsx (1)

1-141: Alert dialog implementation is solid
Using React.forwardRef in tandem with Radix UI primitives is cleanly executed. The code is readable and appears to cover key accessibility features.

netmanager-app/app/(authenticated)/clients/page.tsx (1)

155-156: Confirm correctness of the permission key
Double-check that 'CREATE_UPDATE_AND_DELETE_NETWORK_DEVICES' is the intended permission, as an incorrect key could lead to unintended access issues.

Would you like me to generate a shell script to search for references to this permission across the repository to confirm its usage?

netmanager-app/package.json (2)

13-13: LGTM! Consistent versioning with other Radix UI components.

The new alert dialog package follows the same major version as other Radix UI components, maintaining consistency across the UI library.


28-28: Consider code splitting for the table component.

The TanStack Table package is a substantial library. Since it's used only on the clients page, consider implementing dynamic imports to reduce the initial bundle size.

Example implementation:

// In your clients page component
const DataTable = dynamic(() => import('@/components/data-table'), {
  loading: () => <TableSkeleton />,
  ssr: false
})

Also, let's verify the security status of these new dependencies:

✅ Verification successful

Code splitting suggestion is valid and well-suited for this implementation

The analysis confirms that @tanstack/react-table is exclusively used in the clients page through a well-isolated table component. The suggested dynamic import approach is appropriate and can be implemented as proposed, which will help optimize the initial bundle size.

The current clean separation of the table component into its own files makes this refactoring straightforward to implement.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for known vulnerabilities in new packages

echo "Checking @radix-ui/react-alert-dialog..."
npm audit --json @radix-ui/react-alert-dialog

echo "Checking @tanstack/react-table..."
npm audit --json @tanstack/react-table

# Check if versions are latest stable
echo "Checking latest versions..."
npm view @radix-ui/react-alert-dialog version
npm view @tanstack/react-table version

Length of output: 1601


Script:

#!/bin/bash
# Find all imports of @tanstack/react-table and potential table components
echo "Searching for @tanstack/react-table imports..."
rg "@tanstack/react-table" -l

echo -e "\nSearching for potential table component imports..."
rg "import.*Table" -l

echo -e "\nChecking for existing dynamic imports..."
rg "dynamic\(" -l

echo -e "\nChecking package size..."
npm view @tanstack/react-table dist.size

Length of output: 4411


Script:

#!/bin/bash
# Check the actual implementation of the table component
echo "Checking clients page implementation..."
cat netmanager-app/app/\(authenticated\)/clients/page.tsx

echo -e "\nChecking data-table implementation..."
cat netmanager-app/app/\(authenticated\)/clients/data-table.tsx

Length of output: 8992

Comment on lines +17 to +25
export interface Client {
_id: string
isActive: boolean
ip_addresses: string[]
name: string
client_secret: string
user: UserDetails
access_token: AccessToken
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Hash sensitive credentials for security

Storing client_secret in plain text could pose security risks if the data is ever exposed. It is a best practice to store such credentials as securely hashed values on the server side. Consider clarifying or documenting how client_secret is stored and used.

const accessToken = row.original.access_token
if (!accessToken || !accessToken.expires) return "N/A"
const expires = new Date(accessToken.expires)
if (isNaN(expires.getTime())) return "Invalid Date"
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Use Number.isNaN for safer numeric checks
Relying on isNaN() can lead to type coercion issues. It's best to use Number.isNaN() for a more explicit numeric check.

- if (isNaN(expires.getTime())) return "Invalid Date"
+ if (Number.isNaN(expires.getTime())) return "Invalid Date"
📝 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 (isNaN(expires.getTime())) return "Invalid Date"
if (Number.isNaN(expires.getTime())) return "Invalid Date"
🧰 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)

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

♻️ Duplicate comments (1)
netmanager-app/app/(authenticated)/clients/page.tsx (1)

15-15: ⚠️ Potential issue

Use Number.isNaN to avoid unintended type coercion.

This issue has been flagged before. Replacing global isNaN with Number.isNaN ensures greater reliability and avoids unwanted type conversion.

Apply this diff to fix:

- if (isNaN(date.getTime())) return "Invalid Date"
+ if (Number.isNaN(date.getTime())) return "Invalid Date"
🧰 Tools
🪛 Biome (1.9.4)

[error] 15-15: 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)

🧹 Nitpick comments (1)
netmanager-app/app/(authenticated)/clients/page.tsx (1)

90-99: Consider merging the double filter into a single pass.

Using optional chaining can simplify the logic and reduce overhead from multiple filter calls. This is purely optional but can streamline the code.

Possible approach:

-  const nearestExpiringToken = clients
-    .filter((client) => client.access_token && client.access_token.expires)
-    .filter((client) => {
-      const expiryDate = new Date(client.access_token.expires)
-      return expiryDate > new Date()
-    })
-    .sort((a, b) => {
-      const dateA = new Date(a.access_token.expires).getTime()
-      const dateB = new Date(b.access_token.expires).getTime()
-      return dateA - dateB
-    })[0]
+  const nearestExpiringToken = clients
+    .filter((client) => {
+      const expiryDate = new Date(client.access_token?.expires ?? '')
+      return !Number.isNaN(expiryDate.getTime()) && expiryDate > new Date()
+    })
+    .sort((a, b) => {
+      const dateA = new Date(a.access_token?.expires ?? '').getTime()
+      const dateB = new Date(b.access_token?.expires ?? '').getTime()
+      return dateA - dateB
+    })[0]
🧰 Tools
🪛 Biome (1.9.4)

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

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 7bc0bc3 and cf84b1a.

📒 Files selected for processing (2)
  • netmanager-app/app/(authenticated)/clients/page.tsx (1 hunks)
  • netmanager-app/components/sidebar.tsx (2 hunks)
🧰 Additional context used
🪛 Biome (1.9.4)
netmanager-app/app/(authenticated)/clients/page.tsx

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

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


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

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

(lint/suspicious/noGlobalIsNan)

Comment on lines +214 to +228
<PermissionGuard permission="CREATE_UPDATE_AND_DELETE_NETWORK_ROLES">
<li>
<Link
href="/clients"
className={`flex items-center gap-2 text-sm text-foreground hover:bg-accent hover:text-accent-foreground p-2 rounded-md ${
isActive("/access-control")
? "bg-accent text-accent-foreground"
: ""
}`}
>
<Hospital size={18} />
<span>Clients</span>
</Link>
</li>
</PermissionGuard>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Ensure the correct active state for the "Clients" link.

Currently, isActive("/access-control") is used for the "Clients" link, which may cause the active style to remain unset when navigating to /clients. Consider updating it to isActive("/clients") or refining the active check logic.

Proposed fix:

-                  className={`flex items-center gap-2 text-sm text-foreground hover:bg-accent hover:text-accent-foreground p-2 rounded-md ${
-                    isActive("/access-control")
-                      ? "bg-accent text-accent-foreground"
-                      : ""
-                  }`}
+                  className={`flex items-center gap-2 text-sm text-foreground hover:bg-accent hover:text-accent-foreground p-2 rounded-md ${
+                    isActive("/clients")
+                      ? "bg-accent text-accent-foreground"
+                      : ""
+                  }`}
📝 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
<PermissionGuard permission="CREATE_UPDATE_AND_DELETE_NETWORK_ROLES">
<li>
<Link
href="/clients"
className={`flex items-center gap-2 text-sm text-foreground hover:bg-accent hover:text-accent-foreground p-2 rounded-md ${
isActive("/access-control")
? "bg-accent text-accent-foreground"
: ""
}`}
>
<Hospital size={18} />
<span>Clients</span>
</Link>
</li>
</PermissionGuard>
<PermissionGuard permission="CREATE_UPDATE_AND_DELETE_NETWORK_ROLES">
<li>
<Link
href="/clients"
className={`flex items-center gap-2 text-sm text-foreground hover:bg-accent hover:text-accent-foreground p-2 rounded-md ${
isActive("/clients")
? "bg-accent text-accent-foreground"
: ""
}`}
>
<Hospital size={18} />
<span>Clients</span>
</Link>
</li>
</PermissionGuard>

@danielmarv danielmarv changed the title [Netmanager]: Clieants page with permissions restrictions [Netmanager]: Clients page with permissions restrictions Jan 24, 2025
Copy link
Member

@Codebmk Codebmk left a comment

Choose a reason for hiding this comment

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

Lets put the client id right below the client name, no need for its own column, to create more space in the table

image

Copy link
Member

@Codebmk Codebmk left a comment

Choose a reason for hiding this comment

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

Let's use pagination in the table like we have done for other tables. Look at sites registry table to see how to execute such. Max number of rows per page should be 8

image

@Codebmk
Copy link
Member

Codebmk commented Jan 28, 2025

Action buttons missing ie Activate and Deactivate client.

image

On sorting by client name, the following warning appears in the console

image

Should be nearest client expiry. Am suggesting we remove this card and instead and add a sort button to the table that allows to sort by nearest client expiry date.

image

Client status chips too big, kindly use same chip style as used in sites registry table. Example shared below

image

},
},
]

Copy link
Member

@Codebmk Codebmk Jan 28, 2025

Choose a reason for hiding this comment

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

Is this a component or a page? @danielmarv

Anything added under app folder is considered a page. Now we can't have a page called columns😂😂 actually everything you put under clients is a component except for page. Kindly put these in the right folder!

image

And if it is a page, clients is not accessible by everyone. Refer to sites registry page to see how to add access control

@@ -24,6 +25,7 @@
"@reduxjs/toolkit": "^2.5.0",
"@tanstack/react-query": "^5.62.11",
"@tanstack/react-query-devtools": "^5.62.11",
"@tanstack/react-table": "^8.20.6",
"@types/leaflet": "^1.9.15",
Copy link
Member

Choose a reason for hiding this comment

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

No need for a new table library, kindly use shadcn for all your UI needs! @danielmarv

@Codebmk
Copy link
Member

Codebmk commented Jan 28, 2025

Clients should be a tab under profile settings and not a under the sidebar as we had discussed.
Refer to slack conversation: https://airqotech.slack.com/archives/D05NWAHJLM9/p1737543834434299

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

♻️ Duplicate comments (1)
netmanager-app/app/(authenticated)/clients/page.tsx (1)

24-37: ⚠️ Potential issue

Fix the date validation and enhance the formatting utility.

The date formatting utility has a few issues:

  1. Uses unsafe isNaN (as flagged by static analysis)
  2. Lacks timezone handling
  3. Could benefit from internationalization
 const formatDate = (dateString: string | undefined): string => {
   if (!dateString) return "N/A"
   const date = new Date(dateString)
-  if (isNaN(date.getTime())) return "Invalid Date"
+  if (Number.isNaN(date.getTime())) return "Invalid Date"
 
   const now = new Date()
+  // Normalize both dates to UTC midnight to avoid timezone issues
+  const utcDate = new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate()))
+  const utcNow = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()))
-  const diffTime = date.getTime() - now.getTime()
+  const diffTime = utcDate.getTime() - utcNow.getTime()
   const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24))
🧰 Tools
🪛 Biome (1.9.4)

[error] 27-27: 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)

🧹 Nitpick comments (5)
netmanager-app/components/clients/dialogs.tsx (3)

12-17: Props interface looks good, but could be more descriptive.

The interface is well-structured but could benefit from JSDoc comments to improve documentation.

+/**
+ * Props for client activation/deactivation dialogs
+ * @property {boolean} open - Controls dialog visibility
+ * @property {(open: boolean) => void} onOpenChange - Handles dialog open state changes
+ * @property {() => void} onConfirm - Callback fired when action is confirmed
+ * @property {string} [clientName] - Optional name of the client for display
+ */
 interface DialogProps {
   open: boolean
   onOpenChange: (open: boolean) => void
   onConfirm: () => void
   clientName?: string
 }

19-36: Consider adding loading state and error handling.

The dialog could benefit from:

  1. A loading state during activation
  2. Error handling for the confirmation action
-export function ActivateClientDialog({ open, onOpenChange, onConfirm, clientName }: DialogProps) {
+export function ActivateClientDialog({ 
+  open, 
+  onOpenChange, 
+  onConfirm, 
+  clientName,
+  isLoading = false 
+}: DialogProps & { isLoading?: boolean }) {
   return (
     <AlertDialog open={open} onOpenChange={onOpenChange}>
       <AlertDialogContent>
         <AlertDialogHeader>
           <AlertDialogTitle>Activate Client</AlertDialogTitle>
           <AlertDialogDescription>
             Are you sure you want to activate the client {clientName}? This action cannot be undone.
           </AlertDialogDescription>
         </AlertDialogHeader>
         <AlertDialogFooter>
           <AlertDialogCancel>Cancel</AlertDialogCancel>
-          <AlertDialogAction onClick={onConfirm}>Activate</AlertDialogAction>
+          <AlertDialogAction 
+            onClick={onConfirm} 
+            disabled={isLoading}
+          >
+            {isLoading ? "Activating..." : "Activate"}
+          </AlertDialogAction>
         </AlertDialogFooter>
       </AlertDialogContent>
     </AlertDialog>
   )
 }

38-55: Consider reducing code duplication with a shared base component.

Both dialog components share almost identical structure. Consider creating a base component to reduce duplication.

+interface ActionDialogProps extends DialogProps {
+  title: string;
+  actionText: string;
+  isLoading?: boolean;
+}
+
+function ActionDialog({ 
+  open, 
+  onOpenChange, 
+  onConfirm, 
+  clientName,
+  title,
+  actionText,
+  isLoading = false 
+}: ActionDialogProps) {
+  return (
+    <AlertDialog open={open} onOpenChange={onOpenChange}>
+      <AlertDialogContent>
+        <AlertDialogHeader>
+          <AlertDialogTitle>{title}</AlertDialogTitle>
+          <AlertDialogDescription>
+            Are you sure you want to {actionText.toLowerCase()} the client {clientName}? 
+            This action cannot be undone.
+          </AlertDialogDescription>
+        </AlertDialogHeader>
+        <AlertDialogFooter>
+          <AlertDialogCancel>Cancel</AlertDialogCancel>
+          <AlertDialogAction 
+            onClick={onConfirm}
+            disabled={isLoading}
+          >
+            {isLoading ? `${actionText}ing...` : actionText}
+          </AlertDialogAction>
+        </AlertDialogFooter>
+      </AlertDialogContent>
+    </AlertDialog>
+  )
+}
+
+export function ActivateClientDialog(props: DialogProps & { isLoading?: boolean }) {
+  return <ActionDialog {...props} title="Activate Client" actionText="Activate" />
+}
+
+export function DeactivateClientDialog(props: DialogProps & { isLoading?: boolean }) {
+  return <ActionDialog {...props} title="Deactivate Client" actionText="Deactivate" />
+}
netmanager-app/app/(authenticated)/clients/page.tsx (2)

139-150: Optimize token expiry calculation and add type safety.

The nearest expiring token calculation could be optimized and made more type-safe.

 const nearestExpiringToken = clients
-  .filter((client) => client.access_token && client.access_token.expires)
-  .filter((client) => {
+  .filter((client): client is Client & { access_token: { expires: string } } => {
+    if (!client.access_token?.expires) return false
     const expiryDate = new Date(client.access_token.expires)
-    return expiryDate > new Date()
+    return !Number.isNaN(expiryDate.getTime()) && expiryDate > new Date()
   })
   .sort((a, b) => {
     const dateA = new Date(a.access_token.expires).getTime()
     const dateB = new Date(b.access_token.expires).getTime()
     return dateA - dateB
-  })[0]
+  })[0] ?? null
🧰 Tools
🪛 Biome (1.9.4)

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

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


240-248: Enhance button accessibility and loading states.

The action buttons could benefit from improved accessibility and loading states.

 <Button
   variant="ghost"
+  aria-label={`${client.isActive ? "Deactivate" : "Activate"} ${client.name}`}
   onClick={() => (client.isActive ? handleDeactivateClick(client) : handleActivateClick(client))}
+  disabled={loading}
 >
   {client.isActive ? "Deactivate" : "Activate"}
 </Button>
 <Button 
   variant="ghost" 
+  aria-label={`Copy ID for ${client.name}`}
   onClick={() => handleCopyClientId(client._id)}
+  disabled={loading}
 >
   Copy ID
 </Button>
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between cf84b1a and e6fb3e0.

📒 Files selected for processing (2)
  • netmanager-app/app/(authenticated)/clients/page.tsx (1 hunks)
  • netmanager-app/components/clients/dialogs.tsx (1 hunks)
🧰 Additional context used
🪛 Biome (1.9.4)
netmanager-app/app/(authenticated)/clients/page.tsx

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

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


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

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

(lint/suspicious/noGlobalIsNan)

Comment on lines 52 to 67
const fetchClients = async () => {
setLoading(true)
try {
const response = await getClientsApi()
console.log(response.clients)
setClients(response.clients)
} catch (error) {
toast({
title: "Error",
description: "Failed to fetch clients",
variant: "destructive",
})
} finally {
setLoading(false)
}
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Remove console.log and enhance error handling.

The fetchClients function has a console.log statement and could benefit from more specific error handling.

 const fetchClients = async () => {
   setLoading(true)
   try {
     const response = await getClientsApi()
-    console.log(response.clients)
     setClients(response.clients)
   } catch (error) {
+    const errorMessage = error instanceof Error ? error.message : "Failed to fetch clients"
     toast({
       title: "Error",
-      description: "Failed to fetch clients",
+      description: errorMessage,
       variant: "destructive",
     })
+    console.error("Error fetching clients:", error)
   } finally {
     setLoading(false)
   }
 }
📝 Committable suggestion

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

Suggested change
const fetchClients = async () => {
setLoading(true)
try {
const response = await getClientsApi()
console.log(response.clients)
setClients(response.clients)
} catch (error) {
toast({
title: "Error",
description: "Failed to fetch clients",
variant: "destructive",
})
} finally {
setLoading(false)
}
}
const fetchClients = async () => {
setLoading(true)
try {
const response = await getClientsApi()
setClients(response.clients)
} catch (error) {
const errorMessage = error instanceof Error ? error.message : "Failed to fetch clients"
toast({
title: "Error",
description: errorMessage,
variant: "destructive",
})
console.error("Error fetching clients:", error)
} finally {
setLoading(false)
}
}

@airqo-platform airqo-platform deleted a comment from coderabbitai bot Jan 28, 2025
Copy link
Member

@Codebmk Codebmk left a comment

Choose a reason for hiding this comment

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

LGTM thanks @danielmarv

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

🧹 Nitpick comments (1)
netmanager-app/app/(authenticated)/clients/page.tsx (1)

140-287: Enhance accessibility for better user experience

Consider the following accessibility improvements:

  1. Add aria-label to the search input
  2. Add aria-sort to sortable table headers
  3. Add aria-live region for loading state
 <Input
   placeholder="Search clients..."
   className="pl-8"
   value={searchQuery}
+  aria-label="Search clients by name or email"
   onChange={(e) => setSearchQuery(e.target.value)}
 />

-<div className="flex justify-center items-center h-64">
+<div className="flex justify-center items-center h-64" aria-live="polite">
   <Loader2 className="h-8 w-8 animate-spin" />
 </div>

-<TableHead className="w-[250px]">Client Name</TableHead>
+<TableHead className="w-[250px]" aria-sort={sortField === "name" ? sortOrder : "none"}>
+  Client Name
+</TableHead>
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between e6fb3e0 and 2376cb4.

⛔ Files ignored due to path filters (1)
  • netmanager-app/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (2)
  • netmanager-app/app/(authenticated)/clients/page.tsx (1 hunks)
  • netmanager-app/package.json (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • netmanager-app/package.json
🧰 Additional context used
🪛 Biome (1.9.4)
netmanager-app/app/(authenticated)/clients/page.tsx

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

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

(lint/suspicious/noGlobalIsNan)

🔇 Additional comments (4)
netmanager-app/app/(authenticated)/clients/page.tsx (4)

40-50: LGTM! Well-organized state management

The state variables are properly typed and initialized with appropriate default values.


52-66: Remove console.log and enhance error handling.

The fetchClients function could benefit from more specific error handling.


120-137: LGTM! Efficient data processing

The filtering and pagination logic is well-implemented with case-insensitive search and proper calculations.


227-237: 🛠️ Refactor suggestion

Add missing action buttons as per PR comments

The implementation is missing activate/deactivate buttons in the style mentioned in the PR comments. Consider adding these buttons to match the sites registry table style.

 <TableCell className="text-right">
+  <div className="flex justify-end gap-2">
   <Button
-    variant="ghost"
+    variant="outline"
+    size="sm"
     onClick={() => (client.isActive ? handleDeactivateClick(client) : handleActivateClick(client))}
+    className={client.isActive ? "text-red-600" : "text-green-600"}
   >
     {client.isActive ? "Deactivate" : "Activate"}
   </Button>
   <Button variant="ghost" onClick={() => handleCopyClientId(client._id)}>
     Copy ID
   </Button>
+  </div>
 </TableCell>

Comment on lines +111 to +118
const handleSort = (field: keyof Client) => {
if (sortField === field) {
setSortOrder(sortOrder === "asc" ? "desc" : "asc")
} else {
setSortField(field)
setSortOrder("asc")
}
}
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 type safety to sorting function

The current sorting implementation might fail for nested properties like user.email. Consider adding type safety and proper handling for nested properties.

-  const sortedClients = [...filteredClients].sort((a, b) => {
-    if (a[sortField] < b[sortField]) return sortOrder === "asc" ? -1 : 1
-    if (a[sortField] > b[sortField]) return sortOrder === "asc" ? 1 : -1
-    return 0
-  })
+  const sortedClients = [...filteredClients].sort((a, b) => {
+    const getValue = (obj: Client, field: keyof Client) => {
+      if (field === "user.email") return obj.user.email
+      return obj[field]
+    }
+    const aValue = getValue(a, sortField)
+    const bValue = getValue(b, sortField)
+    return sortOrder === "asc" 
+      ? String(aValue).localeCompare(String(bValue))
+      : String(bValue).localeCompare(String(aValue))
+  })

Also applies to: 126-130

Comment on lines +24 to +37
const formatDate = (dateString: string | undefined): string => {
if (!dateString) return "N/A"
const date = new Date(dateString)
if (isNaN(date.getTime())) return "Invalid Date"

const now = new Date()
const diffTime = date.getTime() - now.getTime()
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24))

if (diffDays < 0) return "Expired"
if (diffDays === 0) return "Expires today"
if (diffDays === 1) return "Expires tomorrow"
return `Expires in ${diffDays} days`
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Replace unsafe isNaN with Number.isNaN

The current implementation uses the unsafe global isNaN which performs type coercion. Use Number.isNaN instead for more predictable type checking.

 const formatDate = (dateString: string | undefined): string => {
   if (!dateString) return "N/A"
   const date = new Date(dateString)
-  if (isNaN(date.getTime())) return "Invalid Date"
+  if (Number.isNaN(date.getTime())) return "Invalid Date"

   const now = new Date()
   const diffTime = date.getTime() - now.getTime()
   const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24))
📝 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 formatDate = (dateString: string | undefined): string => {
if (!dateString) return "N/A"
const date = new Date(dateString)
if (isNaN(date.getTime())) return "Invalid Date"
const now = new Date()
const diffTime = date.getTime() - now.getTime()
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24))
if (diffDays < 0) return "Expired"
if (diffDays === 0) return "Expires today"
if (diffDays === 1) return "Expires tomorrow"
return `Expires in ${diffDays} days`
}
const formatDate = (dateString: string | undefined): string => {
if (!dateString) return "N/A"
const date = new Date(dateString)
if (Number.isNaN(date.getTime())) return "Invalid Date"
const now = new Date()
const diffTime = date.getTime() - now.getTime()
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24))
if (diffDays < 0) return "Expired"
if (diffDays === 0) return "Expires today"
if (diffDays === 1) return "Expires tomorrow"
return `Expires in ${diffDays} days`
}
🧰 Tools
🪛 Biome (1.9.4)

[error] 27-27: 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)

@Baalmart Baalmart merged commit c06dcca into staging Jan 29, 2025
31 checks passed
@Baalmart Baalmart deleted the Daniel-Clients branch January 29, 2025 15:49
@Baalmart Baalmart mentioned this pull request Jan 29, 2025
1 task
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.

3 participants