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]: Profile Settings Page #2401

Open
wants to merge 23 commits into
base: staging
Choose a base branch
from
Open

[Netmanager]: Profile Settings Page #2401

wants to merge 23 commits into from

Conversation

danielmarv
Copy link
Member

@danielmarv danielmarv commented Jan 23, 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

Release Notes

  • New Features

    • Added a comprehensive User Profile management system.
    • Introduced client management functionality with token generation and activation requests.
    • Implemented password change capabilities.
    • Added support for managing API access tokens.
    • Introduced a new dialog system for user interactions.
    • Added a new component for displaying and managing user clients in a table format.
    • Created components for editing and creating client profiles.
    • Implemented a new component for managing client data with search and sorting features.
    • Added a new toast notification system for user feedback.
    • Introduced a customizable progress bar component.
  • Improvements

    • Enhanced profile editing with image upload and dynamic form handling.
  • Dependencies

    • Added new libraries for country and timezone management.
    • Integrated toast notification library.
    • Included a new library for progress indicators.
  • Technical Enhancements

    • Implemented Redux slice for client state management.
    • Created custom hooks for data fetching and toast notifications.
    • Introduced a new reducer for managing client-related state in the Redux store.

Copy link

coderabbitai bot commented Jan 23, 2025

📝 Walkthrough

Walkthrough

This pull request introduces a comprehensive user profile management system for the netmanager application. The changes include new components for profile editing, password management, API token handling, and client management. The implementation leverages React hooks, Redux for state management, and integrates various utility functions for handling countries, timezones, and API interactions.

Changes

File Change Summary
profile/components/* Added multiple components: ApiTokens, MyProfile, PasswordEdit, ProfileTabs, ClientTableRow, DialogWrapper, EditClientForm, CreateClientForm, ClientManagement
core/apis/settings.ts Introduced multiple API functions for client and user management
utils/countries.ts, utils/timezones.ts Added utility functions for retrieving country and timezone data
package.json Added dependencies: @radix-ui/react-progress, i18n-iso-countries, timezones.json
core/redux/* Created new Redux slice and updated store for client management
components/ui/* Added Textarea and modified toast and toaster components for better organization and functionality

Suggested labels

profile-management, client-configuration, user-settings

Suggested reviewers

  • Baalmart
  • Codebmk

Poem

🚀 Profiles dancing, clients in line,
Code weaving magic, feature design.
Tokens gleaming, tabs unfurled,
A developer's canvas, a digital world!
Redux state singing, APIs take flight 🌟

Possibly related PRs

  • [Netmanager]: Clients page with permissions restrictions #2405: The changes in the main PR, which introduce the UserClientsTable component for managing user clients, are directly related to the retrieved PR that also implements a ClientManagement component for managing client accounts, as both components utilize similar API functions for client data retrieval and management.
  • netmanager: Edit and register client feature #2157: The changes in the main PR, specifically the introduction of the UserClientsTable component for managing user clients, are related to the modifications in the retrieved PR that enhance the client registration and editing functionality, particularly through the management of multiple IP addresses. Both PRs involve similar concepts of client management and editing processes.
  • Analytics: Updated client creation and edit #2159: The changes in the main PR, specifically the introduction of the UserClientsTable component for managing user clients, are directly related to the modifications in the retrieved PR, which includes enhancements to the AddClientForm and EditClientForm components that also deal with client management functionalities. Both PRs focus on improving the handling and display of client data, including IP addresses.
✨ Finishing Touches
  • 📝 Generate Docstrings (Beta)

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

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

3-10: Clean implementation with room for accessibility enhancement.

The component structure is well-organized. Consider adding an aria-label to the container div to improve screen reader context.

-    <div className="container mx-auto px-4 py-8">
+    <div className="container mx-auto px-4 py-8" aria-label="Profile Settings">
netmanager-app/components/ui/textarea.tsx (1)

5-19: Enhance accessibility with aria-label support.

The component implementation is solid, but consider adding explicit support for aria-label to improve accessibility.

 const Textarea = React.forwardRef<
   HTMLTextAreaElement,
   React.ComponentProps<"textarea">
->(({ className, ...props }, ref) => {
+>(({ className, "aria-label": ariaLabel, ...props }, ref) => {
   return (
     <textarea
       className={cn(
         "flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-base ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
         className
       )}
+      aria-label={ariaLabel}
       ref={ref}
       {...props}
     />
netmanager-app/app/(authenticated)/profile/components/ApiTokens.tsx (2)

9-17: Consider adding JSDoc documentation and validation constraints to the Token interface.

Adding documentation and constraints would improve type safety and code maintainability.

+/**
+ * Represents an API token with its associated metadata
+ */
 interface Token {
   id: string
-  name: string
+  name: string  // @min 3 characters
   ipAddress: string
   status: "Active" | "Inactive"
   created: string  // ISO date string
   token: string
-  expires: string
+  expires: string  // ISO date string
 }

66-82: Enhance form validation and error handling.

Add proper validation and error feedback for better user experience.

+  const [error, setError] = useState<string>("")
+
   <form onSubmit={handleCreateToken} className="space-y-4">
     <div className="flex space-x-2">
       <div className="flex-grow">
         <Label htmlFor="new-token-name" className="sr-only">
           New Token Name
         </Label>
         <Input
           id="new-token-name"
           placeholder="Enter new token name"
           value={newTokenName}
-          onChange={(e) => setNewTokenName(e.target.value)}
+          onChange={(e) => {
+            setNewTokenName(e.target.value)
+            setError("")
+          }}
+          minLength={3}
+          maxLength={50}
+          pattern="[a-zA-Z0-9\s-_]+"
           required
         />
+        {error && <p className="text-red-500 text-sm mt-1">{error}</p>}
       </div>
       <Button type="submit">Create New Token</Button>
     </div>
   </form>
netmanager-app/utils/timezones.ts (1)

1-8: Add TypeScript types and error handling

The implementation looks clean, but could benefit from some TypeScript safety features:

+interface TimeZone {
+  utc: string[];
+  text: string;
+}
+
+interface FormattedTimeZone {
+  value: string;
+  label: string;
+}
+
-import timeZones from "timezones.json"
+import timeZones from "timezones.json" assert { type: "json" }
 
-export const getTimezones = () => {
+export const getTimezones = (): FormattedTimeZone[] => {
   return timeZones.map((tz) => ({
-    value: tz.utc[0],
+    value: tz.utc[0] ?? '',  // Fallback if array is empty
     label: `${tz.text} (${tz.utc[0]})`,
   }))
 }
netmanager-app/utils/countries.ts (1)

1-12: Add TypeScript safety and optimize performance

The implementation is good, but could be enhanced with types and memoization:

import countries from "i18n-iso-countries"
import enLocale from "i18n-iso-countries/langs/en.json"

+interface CountryOption {
+  value: string;
+  label: string;
+}
+
+let cachedCountries: CountryOption[] | null = null;
+
countries.registerLocale(enLocale)

-export const getCountries = () => {
+export const getCountries = (): CountryOption[] => {
+  if (cachedCountries) return cachedCountries;
+
   const countryObj = countries.getNames("en", { select: "official" })
-  return Object.entries(countryObj).map(([code, name]) => ({
+  cachedCountries = Object.entries(countryObj).map(([code, name]) => ({
     value: code,
     label: name,
   }))
+  return cachedCountries;
 }

Since the country list is static, we can memoize it to avoid unnecessary transformations on subsequent calls.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between b50166a and efeceff.

⛔ Files ignored due to path filters (1)
  • netmanager-app/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (9)
  • netmanager-app/app/(authenticated)/profile/components/ApiTokens.tsx (1 hunks)
  • netmanager-app/app/(authenticated)/profile/components/MyProfile.tsx (1 hunks)
  • netmanager-app/app/(authenticated)/profile/components/PasswordEdit.tsx (1 hunks)
  • netmanager-app/app/(authenticated)/profile/components/ProfileTabs.tsx (1 hunks)
  • netmanager-app/app/(authenticated)/profile/page.tsx (1 hunks)
  • netmanager-app/components/ui/textarea.tsx (1 hunks)
  • netmanager-app/package.json (2 hunks)
  • netmanager-app/utils/countries.ts (1 hunks)
  • netmanager-app/utils/timezones.ts (1 hunks)
🧰 Additional context used
🪛 Gitleaks (8.21.2)
netmanager-app/app/(authenticated)/profile/components/ApiTokens.tsx

27-27: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)


36-36: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

Comment on lines 9 to 30
export default function ProfileTabs() {
const [activeTab, setActiveTab] = useState("profile")

return (
<Tabs value={activeTab} onValueChange={setActiveTab} className="w-full">
<TabsList className="grid w-full grid-cols-3">
<TabsTrigger value="profile">My Profile</TabsTrigger>
<TabsTrigger value="password">Password Edit</TabsTrigger>
<TabsTrigger value="api">API Access Tokens</TabsTrigger>
</TabsList>
<TabsContent value="profile">
<MyProfile />
</TabsContent>
<TabsContent value="password">
<PasswordEdit />
</TabsContent>
<TabsContent value="api">
<ApiTokens />
</TabsContent>
</Tabs>
)
}
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 boundaries and enhance tab accessibility.

While the tab implementation is clean, consider these improvements:

  1. Wrap tab content components with error boundaries
  2. Add aria-labels to improve tab navigation
+import { ErrorBoundary } from "react-error-boundary"
+
 export default function ProfileTabs() {
   const [activeTab, setActiveTab] = useState("profile")
 
   return (
-    <Tabs value={activeTab} onValueChange={setActiveTab} className="w-full">
+    <Tabs 
+      value={activeTab} 
+      onValueChange={setActiveTab} 
+      className="w-full"
+      aria-label="Profile Settings Tabs"
+    >
       <TabsList className="grid w-full grid-cols-3">
         <TabsTrigger value="profile">My Profile</TabsTrigger>
         <TabsTrigger value="password">Password Edit</TabsTrigger>
         <TabsTrigger value="api">API Access Tokens</TabsTrigger>
       </TabsList>
       <TabsContent value="profile">
+        <ErrorBoundary fallback={<div>Error loading profile content</div>}>
           <MyProfile />
+        </ErrorBoundary>
       </TabsContent>
       // Apply similar error boundaries to other tab contents
📝 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 default function ProfileTabs() {
const [activeTab, setActiveTab] = useState("profile")
return (
<Tabs value={activeTab} onValueChange={setActiveTab} className="w-full">
<TabsList className="grid w-full grid-cols-3">
<TabsTrigger value="profile">My Profile</TabsTrigger>
<TabsTrigger value="password">Password Edit</TabsTrigger>
<TabsTrigger value="api">API Access Tokens</TabsTrigger>
</TabsList>
<TabsContent value="profile">
<MyProfile />
</TabsContent>
<TabsContent value="password">
<PasswordEdit />
</TabsContent>
<TabsContent value="api">
<ApiTokens />
</TabsContent>
</Tabs>
)
}
import { ErrorBoundary } from "react-error-boundary"
export default function ProfileTabs() {
const [activeTab, setActiveTab] = useState("profile")
return (
<Tabs
value={activeTab}
onValueChange={setActiveTab}
className="w-full"
aria-label="Profile Settings Tabs"
>
<TabsList className="grid w-full grid-cols-3">
<TabsTrigger value="profile">My Profile</TabsTrigger>
<TabsTrigger value="password">Password Edit</TabsTrigger>
<TabsTrigger value="api">API Access Tokens</TabsTrigger>
</TabsList>
<TabsContent value="profile">
<ErrorBoundary fallback={<div>Error loading profile content</div>}>
<MyProfile />
</ErrorBoundary>
</TabsContent>
<TabsContent value="password">
<ErrorBoundary fallback={<div>Error loading password edit content</div>}>
<PasswordEdit />
</ErrorBoundary>
</TabsContent>
<TabsContent value="api">
<ErrorBoundary fallback={<div>Error loading API tokens content</div>}>
<ApiTokens />
</ErrorBoundary>
</TabsContent>
</Tabs>
)
}

Comment on lines 19 to 25
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault()
// Here you would typically send the new password to your backend
console.log("Password change request:", passwords)
// Reset form after submission
setPasswords({ current: "", new: "", confirm: "" })
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Remove password logging and implement proper validation.

Critical security issue: Passwords should never be logged to the console, even during development.

   const handleSubmit = (e: React.FormEvent) => {
     e.preventDefault()
-    // Here you would typically send the new password to your backend
-    console.log("Password change request:", passwords)
+    if (passwords.new !== passwords.confirm) {
+      // Add error state handling
+      return
+    }
     // Reset form after submission
     setPasswords({ current: "", new: "", 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
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault()
// Here you would typically send the new password to your backend
console.log("Password change request:", passwords)
// Reset form after submission
setPasswords({ current: "", new: "", confirm: "" })
}
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault()
if (passwords.new !== passwords.confirm) {
// Add error state handling
return
}
// Reset form after submission
setPasswords({ current: "", new: "", confirm: "" })
}

Comment on lines 8 to 68
export default function PasswordEdit() {
const [passwords, setPasswords] = useState({
current: "",
new: "",
confirm: "",
})

const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setPasswords({ ...passwords, [e.target.name]: e.target.value })
}

const handleSubmit = (e: React.FormEvent) => {
e.preventDefault()
// Here you would typically send the new password to your backend
console.log("Password change request:", passwords)
// Reset form after submission
setPasswords({ current: "", new: "", confirm: "" })
}

return (
<div className="space-y-6">
<h2 className="text-2xl font-bold">Change Password</h2>
<form onSubmit={handleSubmit} className="space-y-4">
<div className="space-y-2">
<Label htmlFor="current-password">Current Password</Label>
<Input
id="current-password"
name="current"
type="password"
value={passwords.current}
onChange={handleInputChange}
required
/>
</div>
<div className="space-y-2">
<Label htmlFor="new-password">New Password</Label>
<Input
id="new-password"
name="new"
type="password"
value={passwords.new}
onChange={handleInputChange}
required
/>
</div>
<div className="space-y-2">
<Label htmlFor="confirm-password">Confirm New Password</Label>
<Input
id="confirm-password"
name="confirm"
type="password"
value={passwords.confirm}
onChange={handleInputChange}
required
/>
</div>
<Button type="submit">Change Password</Button>
</form>
</div>
)
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Enhance password change form with validation and UX improvements.

The form needs several security and usability improvements:

  1. Password match validation
  2. Password strength requirements
  3. Visual feedback for validation state

Consider implementing a more robust password change form:

 export default function PasswordEdit() {
   const [passwords, setPasswords] = useState({
     current: "",
     new: "",
     confirm: "",
   })
+  const [errors, setErrors] = useState({
+    new: "",
+    confirm: "",
+  })
+
+  const validatePassword = (password: string) => {
+    if (password.length < 8) {
+      return "Password must be at least 8 characters long"
+    }
+    if (!/[A-Z]/.test(password)) {
+      return "Password must contain at least one uppercase letter"
+    }
+    // Add more validation rules as needed
+    return ""
+  }
+
+  const handleNewPasswordChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+    const newPassword = e.target.value
+    setPasswords(prev => ({ ...prev, new: newPassword }))
+    setErrors(prev => ({
+      ...prev,
+      new: validatePassword(newPassword),
+      confirm: newPassword !== passwords.confirm ? "Passwords do not match" : ""
+    }))
+  }

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

Comment on lines 83 to 106
<Table>
<TableHeader>
<TableRow>
<TableHead>Client name</TableHead>
<TableHead>IP Address</TableHead>
<TableHead>Client Status</TableHead>
<TableHead>Created</TableHead>
<TableHead>Token</TableHead>
<TableHead>Expires</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{tokens.map((token) => (
<TableRow key={token.id}>
<TableCell>{token.name}</TableCell>
<TableCell>{token.ipAddress}</TableCell>
<TableCell>{token.status}</TableCell>
<TableCell>{token.created}</TableCell>
<TableCell>{token.token}</TableCell>
<TableCell>{token.expires}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
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 token management features and improve security.

Consider adding:

  1. Token masking
  2. Copy to clipboard functionality
  3. Token revocation
  4. Last used timestamp
   <Table>
     <TableHeader>
       <TableRow>
         <TableHead>Client name</TableHead>
         <TableHead>IP Address</TableHead>
         <TableHead>Client Status</TableHead>
         <TableHead>Created</TableHead>
         <TableHead>Token</TableHead>
         <TableHead>Expires</TableHead>
+        <TableHead>Actions</TableHead>
       </TableRow>
     </TableHeader>
     <TableBody>
       {tokens.map((token) => (
         <TableRow key={token.id}>
           <TableCell>{token.name}</TableCell>
           <TableCell>{token.ipAddress}</TableCell>
           <TableCell>{token.status}</TableCell>
           <TableCell>{token.created}</TableCell>
-          <TableCell>{token.token}</TableCell>
+          <TableCell>
+            <div className="flex items-center space-x-2">
+              <span>{token.token.substring(0, 8)}...</span>
+              <Button
+                variant="ghost"
+                size="sm"
+                onClick={() => navigator.clipboard.writeText(token.token)}
+              >
+                Copy
+              </Button>
+            </div>
+          </TableCell>
           <TableCell>{token.expires}</TableCell>
+          <TableCell>
+            <Button
+              variant="destructive"
+              size="sm"
+              onClick={() => handleRevokeToken(token.id)}
+            >
+              Revoke
+            </Button>
+          </TableCell>
         </TableRow>
       ))}
     </TableBody>
   </Table>

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

Comment on lines 20 to 39
const [tokens, setTokens] = useState<Token[]>([
{
id: "1",
name: "Production API",
ipAddress: "192.168.1.1",
status: "Active",
created: "2023-01-01",
token: "abc123xyz789",
expires: "2024-01-01",
},
{
id: "2",
name: "Development API",
ipAddress: "192.168.1.2",
status: "Active",
created: "2023-02-01",
token: "def456uvw890",
expires: "2024-02-01",
},
])
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: Remove hardcoded API tokens from the source code.

Exposing API tokens in the source code poses a security risk. Consider:

  1. Loading tokens from a secure backend API
  2. Using placeholder values for development
-  const [tokens, setTokens] = useState<Token[]>([
-    {
-      id: "1",
-      name: "Production API",
-      ipAddress: "192.168.1.1",
-      status: "Active",
-      created: "2023-01-01",
-      token: "abc123xyz789",
-      expires: "2024-01-01",
-    },
-    {
-      id: "2",
-      name: "Development API",
-      ipAddress: "192.168.1.2",
-      status: "Active",
-      created: "2023-02-01",
-      token: "def456uvw890",
-      expires: "2024-02-01",
-    },
-  ])
+  const [tokens, setTokens] = useState<Token[]>([])
+  
+  useEffect(() => {
+    // Fetch tokens from secure backend API
+    const fetchTokens = async () => {
+      try {
+        const response = await fetch('/api/tokens');
+        const data = await response.json();
+        setTokens(data);
+      } catch (error) {
+        console.error('Failed to fetch tokens:', error);
+      }
+    };
+    
+    fetchTokens();
+  }, []);
📝 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 [tokens, setTokens] = useState<Token[]>([
{
id: "1",
name: "Production API",
ipAddress: "192.168.1.1",
status: "Active",
created: "2023-01-01",
token: "abc123xyz789",
expires: "2024-01-01",
},
{
id: "2",
name: "Development API",
ipAddress: "192.168.1.2",
status: "Active",
created: "2023-02-01",
token: "def456uvw890",
expires: "2024-02-01",
},
])
const [tokens, setTokens] = useState<Token[]>([])
useEffect(() => {
// Fetch tokens from secure backend API
const fetchTokens = async () => {
try {
const response = await fetch('/api/tokens');
const data = await response.json();
setTokens(data);
} catch (error) {
console.error('Failed to fetch tokens:', error);
}
};
fetchTokens();
}, []);
🧰 Tools
🪛 Gitleaks (8.21.2)

27-27: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)


36-36: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

Comment on lines 43 to 57
const handleCreateToken = (e: React.FormEvent) => {
e.preventDefault()
// Here you would typically send a request to your backend to create a new token
const newToken: Token = {
id: String(tokens.length + 1),
name: newTokenName,
ipAddress: "192.168.1." + (tokens.length + 1),
status: "Active",
created: new Date().toISOString().split("T")[0],
token: Math.random().toString(36).substring(2, 15),
expires: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000).toISOString().split("T")[0],
}
setTokens([...tokens, newToken])
setNewTokenName("")
}
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: Improve token generation and implement backend integration.

Current implementation has several security concerns:

  1. Using Math.random() for token generation is not cryptographically secure
  2. IP address generation is predictable
  3. Missing backend integration for token creation
   const handleCreateToken = (e: React.FormEvent) => {
     e.preventDefault()
-    // Here you would typically send a request to your backend to create a new token
-    const newToken: Token = {
-      id: String(tokens.length + 1),
-      name: newTokenName,
-      ipAddress: "192.168.1." + (tokens.length + 1),
-      status: "Active",
-      created: new Date().toISOString().split("T")[0],
-      token: Math.random().toString(36).substring(2, 15),
-      expires: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000).toISOString().split("T")[0],
-    }
-    setTokens([...tokens, newToken])
+    const createToken = async () => {
+      try {
+        const response = await fetch('/api/tokens', {
+          method: 'POST',
+          headers: {
+            'Content-Type': 'application/json',
+          },
+          body: JSON.stringify({ name: newTokenName }),
+        });
+        const newToken = await response.json();
+        setTokens([...tokens, newToken]);
+      } catch (error) {
+        console.error('Failed to create token:', error);
+      }
+    };
+    createToken();
     setNewTokenName("")
   }

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

Comment on lines 55 to 59
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
console.log("Updated profile:", profile);
setIsEditing(false);
};
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Implement proper form submission with API integration.

Current implementation only logs to console. Add proper API integration and error handling.

   const handleSubmit = (e: React.FormEvent) => {
     e.preventDefault();
-    console.log("Updated profile:", profile);
-    setIsEditing(false);
+    const updateProfile = async () => {
+      try {
+        const response = await fetch(`/api/users/${currentuser._id}`, {
+          method: 'PUT',
+          headers: {
+            'Content-Type': 'application/json',
+          },
+          body: JSON.stringify(profile),
+        });
+        if (!response.ok) throw new Error('Failed to update profile');
+        setIsEditing(false);
+      } catch (error) {
+        console.error('Error updating profile:', error);
+        // Show error message to user
+      }
+    };
+    updateProfile();
   };

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

Comment on lines 61 to 70
const handleImageUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (file) {
const reader = new FileReader();
reader.onloadend = () => {
setProfile({ ...profile, profilePicture: reader.result as string });
};
reader.readAsDataURL(file);
}
};
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Enhance image upload with validation and backend integration.

Add proper file validation and backend integration for image uploads.

+  const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB
+  const ALLOWED_TYPES = ['image/jpeg', 'image/png', 'image/gif'];

   const handleImageUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
     const file = e.target.files?.[0];
     if (file) {
+      if (!ALLOWED_TYPES.includes(file.type)) {
+        alert('Please upload an image file (JPEG, PNG, or GIF)');
+        return;
+      }
+      if (file.size > MAX_FILE_SIZE) {
+        alert('File size should not exceed 5MB');
+        return;
+      }
+
       const reader = new FileReader();
       reader.onloadend = () => {
-        setProfile({ ...profile, profilePicture: reader.result as string });
+        // Show preview
+        setProfile({ ...profile, profilePicture: reader.result as string });
+        
+        // Upload to backend
+        const formData = new FormData();
+        formData.append('image', file);
+        fetch('/api/upload-profile-image', {
+          method: 'POST',
+          body: formData,
+        })
+        .then(response => response.json())
+        .then(data => {
+          setProfile({ ...profile, profilePicture: data.imageUrl });
+        })
+        .catch(error => {
+          console.error('Error uploading image:', error);
+          // Show error message to user
+        });
       };
       reader.readAsDataURL(file);
     }
   };
📝 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 handleImageUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (file) {
const reader = new FileReader();
reader.onloadend = () => {
setProfile({ ...profile, profilePicture: reader.result as string });
};
reader.readAsDataURL(file);
}
};
const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB
const ALLOWED_TYPES = ['image/jpeg', 'image/png', 'image/gif'];
const handleImageUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (file) {
if (!ALLOWED_TYPES.includes(file.type)) {
alert('Please upload an image file (JPEG, PNG, or GIF)');
return;
}
if (file.size > MAX_FILE_SIZE) {
alert('File size should not exceed 5MB');
return;
}
const reader = new FileReader();
reader.onloadend = () => {
// Show preview
setProfile({ ...profile, profilePicture: reader.result as string });
// Upload to backend
const formData = new FormData();
formData.append('image', file);
fetch('/api/upload-profile-image', {
method: 'POST',
body: formData,
})
.then(response => response.json())
.then(data => {
setProfile({ ...profile, profilePicture: data.imageUrl });
})
.catch(error => {
console.error('Error uploading image:', error);
// Show error message to user
});
};
reader.readAsDataURL(file);
}
};

@@ -32,6 +32,7 @@
"clsx": "^2.1.1",
"cmdk": "^1.0.4",
"date-fns": "^3.6.0",
"i18n-iso-countries": "^7.13.0",
Copy link

Choose a reason for hiding this comment

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

💡 Codebase verification

Consider alternatives to timezones.json package

While i18n-iso-countries is healthy and well-maintained, timezones.json shows concerning signs of inactive maintenance (health score 57/100). Consider using more actively maintained alternatives like moment-timezone (91/100) or @js-joda/core (89/100).

Bundle sizes are reasonable:

  • i18n-iso-countries: 6.8KB gzipped
  • timezones.json: 5.4KB gzipped
🔗 Analysis chain

Verify package versions and security

The new dependencies look good, but let's verify their compatibility and security:

Also applies to: 53-53

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for known vulnerabilities and latest versions

echo "Checking npm audit for security issues..."
npm audit --json | jq '.advisories'

echo "Checking latest versions..."
npm view i18n-iso-countries version
npm view timezones.json version

echo "Checking bundle sizes..."
npm view i18n-iso-countries size
npm view timezones.json size

Length of output: 757


Script:

#!/bin/bash
# Check package security and size using public APIs

echo "Checking bundle sizes via bundlephobia..."
curl -s "https://bundlephobia.com/api/[email protected]" | jq '{name: "i18n-iso-countries", size: .size, gzip: .gzip}'
curl -s "https://bundlephobia.com/api/[email protected]" | jq '{name: "timezones.json", size: .size, gzip: .gzip}'

echo -e "\nChecking security via Snyk..."
curl -s "https://snyk.io/advisor/npm-package/i18n-iso-countries" | grep -A 2 "Health Score"
curl -s "https://snyk.io/advisor/npm-package/timezones.json" | grep -A 2 "Health Score"

Length of output: 17883

status: "Active",
created: "2023-02-01",
token: "def456uvw890",
expires: "2024-02-01",
Copy link
Member

Choose a reason for hiding this comment

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

Any reason for using dummy data here, the corresponding apis should be working just fine @danielmarv

expires: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000).toISOString().split("T")[0],
}
setTokens([...tokens, newToken])
setNewTokenName("")
Copy link
Member

Choose a reason for hiding this comment

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

Use token creation api @danielmarv

@Codebmk
Copy link
Member

Codebmk commented Jan 28, 2025

  • Add space between edit and delete buttons
  • Update and delete profile buttons functionality not working
  • Updating profile details, functionality not working
  • Let's use token creation form in analytics
  • Token creation functionality not working

image
image

@airqo-platform airqo-platform deleted a comment from coderabbitai bot Jan 28, 2025
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

♻️ Duplicate comments (4)
netmanager-app/app/(authenticated)/profile/components/MyProfile.tsx (3)

55-58: ⚠️ Potential issue

Implement proper form submission with API integration.

The form submission is not functional. This needs to be implemented with proper API integration.


60-69: ⚠️ Potential issue

Enhance image upload with validation and backend integration.

The image upload needs proper file validation and backend integration.


114-198: 🛠️ Refactor suggestion

Fix form field validation and editing state.

The form fields have incorrect editing state and lack proper validation.

netmanager-app/app/(authenticated)/profile/components/ApiTokens.tsx (1)

66-116: ⚠️ Potential issue

Mask the tokens for security and add copy functionality.
Displaying raw tokens may leak sensitive information. Consider masking them and adding a “Copy” button, adopting best practices for token management.

-<TableCell>{client.access_token.token}</TableCell>
+<TableCell>
+  <div className="flex items-center space-x-2">
+    <span>{client.access_token.token.substring(0, 8)}...</span>
+    <Button
+      variant="ghost"
+      size="sm"
+      onClick={() => navigator.clipboard.writeText(client.access_token.token)}
+    >
+      Copy
+    </Button>
+  </div>
+</TableCell>
🧹 Nitpick comments (5)
netmanager-app/app/(authenticated)/profile/components/MyProfile.tsx (1)

94-111: Add spacing between avatar buttons and improve UI.

The avatar update and delete buttons need proper spacing, and the delete button needs confirmation dialog.

-          <div>
+          <div className="space-y-2">
             <Button type="button" variant="outline" className="mb-2">
               <Label htmlFor="avatar-upload" className="cursor-pointer">
                 Update
               </Label>
             </Button>
             <Input
               id="avatar-upload"
               type="file"
               accept="image/*"
               className="hidden"
               onChange={handleImageUpload}
-              disabled={isEditing}
+              disabled={!isEditing}
             />
-            <Button type="button" variant="outline" className="text-red-500">
+            <Button
+              type="button"
+              variant="outline"
+              className="text-red-500 ml-2"
+              onClick={() => {
+                if (window.confirm('Are you sure you want to delete your profile picture?')) {
+                  setProfile({ ...profile, profilePicture: null });
+                }
+              }}
+            >
               Delete
             </Button>
           </div>
netmanager-app/app/(authenticated)/profile/components/ApiTokens.tsx (3)

13-18: Nitpick: rename currentuser to currentUser.
Renaming the variable to currentUser improves consistency with conventional naming.

-  const currentuser = useAppSelector((state) => state.user.userDetails)
+  const currentUser = useAppSelector((state) => state.user.userDetails)

19-34: Add early-return check for missing user ID.
Consider returning early when userID is empty, to avoid unnecessary API calls or potential undefined behavior.

+    if (!userID) {
+      setIsLoading(false);
+      return;
+    }

36-41: Implement an actual token creation flow.
The handleCreateToken handler currently only logs that the feature is not implemented. Consider integrating the appropriate API to create tokens.

 const handleCreateToken = async (e: React.FormEvent) => {
   e.preventDefault();
-  console.log("Create token feature is not implemented yet.");
+  try {
+    const response = await fetch("/api/tokens", {
+      method: "POST",
+      headers: { "Content-Type": "application/json" },
+      body: JSON.stringify({ name: newTokenName }),
+    });
+    const newToken = await response.json();
+    // TODO: handle newToken (e.g., set state to show updated tokens in the table)
+  } catch (err) {
+    console.error("Error creating token:", err);
+  }
 };
netmanager-app/core/apis/settings.ts (1)

7-17: Enhance error handling for improved resiliency.
Currently, errors are logged, but the function rethrows them without user-facing feedback. Consider returning a fallback or providing a user-friendly error message for better usability.

  export const getUserClientsApi = async (userID: string): Promise<Client[]> => {
    try {
      const response = await axiosInstance.get<Client[]>(USERS_MGT_URL, { params: { user_id: userID } });
      return response.data;
    } catch (error) {
      console.error("Error fetching clients:", error);
+     // Optionally handle errors more gracefully:
+     // return [];
      throw error;
    }
  };
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 99c47c8 and 36741ae.

📒 Files selected for processing (4)
  • netmanager-app/app/(authenticated)/profile/components/ApiTokens.tsx (1 hunks)
  • netmanager-app/app/(authenticated)/profile/components/MyProfile.tsx (1 hunks)
  • netmanager-app/app/types/clients.ts (1 hunks)
  • netmanager-app/core/apis/settings.ts (1 hunks)
🔇 Additional comments (7)
netmanager-app/app/(authenticated)/profile/components/MyProfile.tsx (1)

1-14: LGTM! Well-structured imports and setup.

The component follows Next.js 13+ conventions and includes all necessary imports for UI components, utilities, and state management.

netmanager-app/app/(authenticated)/profile/components/ApiTokens.tsx (4)

1-2: No issues with the client directive.
Your "use client" directive properly indicates that this component should be rendered on the client side.


3-12: Imports appear valid and well-organized.
All imported modules are used, and there's no indication of any redundant imports.


42-49: Heading and description are clear.
This section effectively explains the purpose of API tokens and user responsibilities. Good job.


50-65: Form design follows good accessibility practices.
Using a visually hidden label (sr-only) while ensuring the input is required provides a user-friendly form. Great approach!

netmanager-app/app/types/clients.ts (2)

3-15: Interface for AccessToken looks clear.
Defining strict field types benefits maintainability. Confirm that string fields like expires and createdAt align with how the API returns date/time data.


17-25: Client interface is well-defined.
The Client interface comprehensively describes an entity with an access_token sub-object. No major issues found.


export default function MyProfile() {
const currentuser = useAppSelector((state) => state.user.userDetails);
const [profile, setProfile] = useState<any>(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 proper TypeScript interface for profile state.

Using any type reduces type safety. Define a proper interface for the profile state.

+interface Profile {
+  firstName: string;
+  lastName: string;
+  email: string;
+  jobTitle?: string;
+  country?: string;
+  timezone?: string;
+  bio?: string;
+  profilePicture?: string;
+}

-const [profile, setProfile] = useState<any>(null);
+const [profile, setProfile] = useState<Profile | null>(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
const [profile, setProfile] = useState<any>(null);
interface Profile {
firstName: string;
lastName: string;
email: string;
jobTitle?: string;
country?: string;
timezone?: string;
bio?: string;
profilePicture?: string;
}
const [profile, setProfile] = useState<Profile | null>(null);

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

🧹 Nitpick comments (8)
netmanager-app/app/(authenticated)/profile/components/DialogWrapper.tsx (3)

7-7: Consider using a more specific type for ModalIcon.

Instead of using React.ComponentType with an optional className, consider using a more specific type that better represents the expected icon component interface.

-  ModalIcon?: React.ComponentType<{ className?: string }>
+  ModalIcon?: React.ComponentType<{ className?: string; size?: number; color?: string }>

40-40: Consider making footer alignment configurable.

The footer alignment is hardcoded to 'sm:justify-start'. Consider making this configurable through props to support different alignment needs.

-  <DialogFooter className="sm:justify-start">
+  <DialogFooter className={`${footerClassName ?? 'sm:justify-start'}`}>

53-54: Choose a single export pattern.

Having both named and default exports for the same component can lead to inconsistent import patterns across the codebase. Consider using only one export method.

-export const DialogWrapper: React.FC<DialogWrapperProps> = ({
-export default DialogWrapper
+export const DialogWrapper: React.FC<DialogWrapperProps> = ({
netmanager-app/app/(authenticated)/profile/components/ClientTableRow.tsx (1)

53-77: Enhance token security and copy feedback.

Consider these security and UX improvements:

  1. Use a more secure token masking pattern
  2. Add visual feedback for the copy action
-            {getClientToken(client._id).slice(0, 2)}....
-            {getClientToken(client._id).slice(-2)}
+            {getClientToken(client._id).slice(0, 4)}•••••
+            {getClientToken(client._id).slice(-4)}
             <div
               className="w-6 h-6 bg-white rounded border border-gray-200 flex justify-center items-center gap-2 cursor-pointer"
-              onClick={() => onCopyToken(getClientToken(client._id))}
+              onClick={() => {
+                onCopyToken(getClientToken(client._id));
+                // Add toast notification for copy feedback
+                toast.success('Token copied to clipboard');
+              }}
netmanager-app/core/apis/settings.ts (1)

60-70: Remove or restore commented code.

The commented-out code should either be removed if it's no longer needed or restored if it's still required. Keeping commented code can lead to confusion.

netmanager-app/app/(authenticated)/profile/components/ApiTokens.tsx (3)

17-18: Consider consolidating error states.

The component maintains separate error states for general errors and activation request errors. Consider consolidating them into a single error state manager for better maintainability.

-  const [isError, setIsError] = useState({ isError: false, message: "", type: "" })
-  const [isActivationRequestError, setIsActivationRequestError] = useState({ isError: false, message: "", type: "" })
+  const [errors, setErrors] = useState({
+    general: { isError: false, message: "", type: "" },
+    activationRequest: { isError: false, message: "", type: "" }
+  })

29-29: Consider making itemsPerPage configurable.

The itemsPerPage value is hardcoded to 4, which limits flexibility. Consider making it configurable through props or environment variables.

-  const itemsPerPage = 4
+  const itemsPerPage = props.itemsPerPage || process.env.NEXT_PUBLIC_DEFAULT_ITEMS_PER_PAGE || 4

78-100: Refactor client token utility functions.

The client token utility functions share similar patterns and could be consolidated into a more maintainable structure.

+  const getClientData = (clientId: string) => {
+    return Array.isArray(clientsDetails) 
+      ? clientsDetails?.find((client: any) => client._id === clientId)
+      : undefined
+  }
+
+  const getClientTokenData = (clientId: string, field?: string) => {
+    const client = getClientData(clientId)
+    if (!client?.access_token) return undefined
+    return field ? client.access_token[field] : client.access_token
+  }
+
-  const hasAccessToken = (clientId: string) => {
-    const client =
-      Array.isArray(clientsDetails) && (clientsDetails)
-        ? clientsDetails?.find((client: any) => client._id === clientId)
-        : undefined
-    return client && client.access_token
-  }
+  const hasAccessToken = (clientId: string) => Boolean(getClientTokenData(clientId))
+
-  const getClientToken = (clientID: string) => {
-    const client =
-      Array.isArray(clientsDetails) && (clientsDetails)
-        ? clientsDetails?.find((client: any) => client._id === clientID)
-        : undefined
-    return client && client.access_token && client.access_token.token
-  }
+  const getClientToken = (clientId: string) => getClientTokenData(clientId, 'token')
+
-  const getClientTokenExpiryDate = (clientID: string) => {
-    const client =
-      Array.isArray(clientsDetails) && (clientsDetails)
-        ? clientsDetails?.find((client: any) => client._id === clientID)
-        : undefined
-    return client && client.access_token && client.access_token.expires
-  }
+  const getClientTokenExpiryDate = (clientId: string) => getClientTokenData(clientId, 'expires')
🧰 Tools
🪛 Biome (1.9.4)

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

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


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

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


[error] 99-99: 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 36741ae and 3c4ea58.

⛔ Files ignored due to path filters (1)
  • netmanager-app/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (6)
  • netmanager-app/app/(authenticated)/profile/components/ApiTokens.tsx (1 hunks)
  • netmanager-app/app/(authenticated)/profile/components/ClientTableRow.tsx (1 hunks)
  • netmanager-app/app/(authenticated)/profile/components/DialogWrapper.tsx (1 hunks)
  • netmanager-app/app/(authenticated)/profile/components/EditClientForm.tsx (1 hunks)
  • netmanager-app/core/apis/settings.ts (1 hunks)
  • netmanager-app/package.json (2 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)/profile/components/ApiTokens.tsx

[error] 13-13: Expected a semicolon or an implicit semicolon after a statement, but found none

An explicit or implicit semicolon is expected here...

...Which is required to end this statement

(parse)


[error] 13-13: unterminated string literal

The closing quote must be on the same line.

(parse)


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

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


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

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


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

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

netmanager-app/app/(authenticated)/profile/components/EditClientForm.tsx

[error] 30-30: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)

🔇 Additional comments (1)
netmanager-app/app/(authenticated)/profile/components/ApiTokens.tsx (1)

109-109: Replace dummy data with proper API integration.

Based on previous review comments, ensure proper backend integration for token management instead of using dummy data.

#!/bin/bash
# Check for API integration implementation
rg -l "getUserDetailsAccount|generateToken|activationRequest" --type ts

Comment on lines +26 to +28
const displayIPAddresses = (client) => {
return Array.isArray(client.ip_addresses) ? client.ip_addresses.join(", ") : client.ip_addresses
}
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 IP address format validation.

The current implementation doesn't validate IP address formats. Consider adding validation to ensure proper IP address formatting.

 const displayIPAddresses = (client) => {
+  const isValidIP = (ip: string) => {
+    const ipv4Regex = /^(\d{1,3}\.){3}\d{1,3}$/;
+    return ipv4Regex.test(ip) && ip.split('.').every(num => parseInt(num) <= 255);
+  };
+  const addresses = Array.isArray(client.ip_addresses) ? client.ip_addresses : [client.ip_addresses];
+  return addresses.filter(ip => ip && isValidIP(ip)).join(", ") || "Invalid IP";
-  return Array.isArray(client.ip_addresses) ? client.ip_addresses.join(", ") : client.ip_addresses
 }
📝 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 displayIPAddresses = (client) => {
return Array.isArray(client.ip_addresses) ? client.ip_addresses.join(", ") : client.ip_addresses
}
const displayIPAddresses = (client) => {
const isValidIP = (ip: string) => {
const ipv4Regex = /^(\d{1,3}\.){3}\d{1,3}$/;
return ipv4Regex.test(ip) && ip.split('.').every(num => parseInt(num) <= 255);
};
const addresses = Array.isArray(client.ip_addresses) ? client.ip_addresses : [client.ip_addresses];
return addresses.filter(ip => ip && isValidIP(ip)).join(", ") || "Invalid IP";
}

import EditIcon from "@/icons/Common/edit-pencil.svg"

interface ClientTableRowProps {
client: any
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 'any' type with a proper Client interface.

Using 'any' type reduces type safety and IDE support. Consider using the Client interface that's already defined in your types.

-  client: any
+  client: Client

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

Comment on lines +96 to +105
} catch (error: any) {
setIsError({
isError: true,
message: error?.response?.data?.message || "Failed to Edit client",
type: "error",
})
} 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

Improve error handling implementation.

The current error handling implementation can be enhanced:

  1. Replace any with proper error type
  2. Make the error message timeout configurable
  3. Add retry mechanism for API calls
-    } catch (error: any) {
+    } catch (error: Error | ApiError) {
+      const errorMessage = isApiError(error) 
+        ? error.response?.data?.message 
+        : error.message;
       setIsError({
         isError: true,
-        message: error?.response?.data?.message || "Failed to Edit client",
+        message: errorMessage || "Failed to Edit client",
         type: "error",
       })
+      // Optional: Add retry logic here
     } finally {

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

Comment on lines 28 to 30
useEffect(() => {
handleInitialData()
}, [handleInitialData]) // Updated dependency
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 useEffect dependency cycle.

The current implementation creates a dependency cycle. Move the handleInitialData function inside useEffect or use useCallback.

-  useEffect(() => {
-    handleInitialData()
-  }, [handleInitialData])
+  useEffect(() => {
+    const handleInitialData = () => {
+      setClientName(data?.name || "")
+      const ipAddresses = Array.isArray(data?.ip_addresses)
+        ? data?.ip_addresses
+        : data?.ip_addresses
+          ? [data?.ip_addresses]
+          : [""]
+      setIpAddresses(ipAddresses)
+    }
+    handleInitialData()
+  }, [data])

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

🧰 Tools
🪛 Biome (1.9.4)

[error] 30-30: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)

userId: string,
tenant: string,
userData: UserDetails
): Promise<any> => {
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 Promise with specific return types.

Using Promise<any> reduces type safety. Define and use specific return types for each API response.

-): Promise<any> => {
+): Promise<{ success: boolean; message: string; data: UserDetails }> => {

-): Promise<any> => {
+): Promise<{ success: boolean; message: string; data: Client }> => {

-export const generateTokenApi = async (data: AccessToken): Promise<any> => {
+export const generateTokenApi = async (data: AccessToken): Promise<{ success: boolean; token: string; expiresAt: string }> => {

Also applies to: 49-49, 55-55

Comment on lines 102 to 120
const handleGenerateToken = async (res: any) => {
setIsLoadingToken(true)
if (!res?.isActive) {
setShowInfoModal(true)
setIsLoadingToken(false)
} else {
try {
const response = await api.generateToken(res)
if (response.success === true) {
setErrorState("Token generated", "success")
}
dispatch(performRefresh())
} catch (error: any) {
setErrorState(error.message, "error")
} finally {
setIsLoadingToken(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

Improve error handling in token generation.

The handleGenerateToken function could benefit from more specific error handling and user feedback.

   const handleGenerateToken = async (res: any) => {
     setIsLoadingToken(true)
     if (!res?.isActive) {
       setShowInfoModal(true)
       setIsLoadingToken(false)
     } else {
       try {
         const response = await api.generateToken(res)
         if (response.success === true) {
           setErrorState("Token generated", "success")
+          await dispatch(performRefresh())
         }
-        dispatch(performRefresh())
       } catch (error: any) {
-        setErrorState(error.message, "error")
+        const errorMessage = error.response?.data?.message || error.message || "Failed to generate token"
+        setErrorState(errorMessage, "error")
+        console.error("Token generation failed:", error)
       } finally {
         setIsLoadingToken(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 handleGenerateToken = async (res: any) => {
setIsLoadingToken(true)
if (!res?.isActive) {
setShowInfoModal(true)
setIsLoadingToken(false)
} else {
try {
const response = await api.generateToken(res)
if (response.success === true) {
setErrorState("Token generated", "success")
}
dispatch(performRefresh())
} catch (error: any) {
setErrorState(error.message, "error")
} finally {
setIsLoadingToken(false)
}
}
}
const handleGenerateToken = async (res: any) => {
setIsLoadingToken(true)
if (!res?.isActive) {
setShowInfoModal(true)
setIsLoadingToken(false)
} else {
try {
const response = await api.generateToken(res)
if (response.success === true) {
setErrorState("Token generated", "success")
await dispatch(performRefresh())
}
} catch (error: any) {
const errorMessage = error.response?.data?.message || error.message || "Failed to generate token"
setErrorState(errorMessage, "error")
console.error("Token generation failed:", error)
} finally {
setIsLoadingToken(false)
}
}
}

)}
</TableBody>
</Table>
{/* Add your Pagination component here */}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Implement pagination component.

The comment indicates a missing pagination component. This is essential for proper table navigation.

-      {/* Add your Pagination component here */}
+      {clients?.length > itemsPerPage && (
+        <div className="flex justify-center mt-4">
+          <div className="flex space-x-2">
+            {Array.from({ length: Math.ceil(clients.length / itemsPerPage) }).map((_, index) => (
+              <Button
+                key={index}
+                variant={currentPage === index + 1 ? "default" : "outline"}
+                size="sm"
+                onClick={() => onPageChange(index + 1)}
+              >
+                {index + 1}
+              </Button>
+            ))}
+          </div>
+        </div>
+      )}

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

Comment on lines 194 to 211
{getClientToken(client._id) ? (
<div className="flex items-center">
<span className="mr-2">
{getClientToken(client._id).slice(0, 2)}....
{getClientToken(client._id).slice(-2)}
</span>
<Button
variant="ghost"
size="sm"
onClick={() => {
navigator.clipboard.writeText(getClientToken(client._id))
setErrorState("Token copied to clipboard!", "success")
}}
>
<Copy className="h-4 w-4" />
</Button>
</div>
) : (
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Enhance token display security and UX.

The current token display implementation could be improved for better security and user experience.

   <TableCell>
     {getClientToken(client._id) ? (
       <div className="flex items-center">
-        <span className="mr-2">
-          {getClientToken(client._id).slice(0, 2)}....
-          {getClientToken(client._id).slice(-2)}
-        </span>
+        <span className="mr-2 font-mono">
+          {getClientToken(client._id).slice(0, 4)}
+          <span className="mx-1">•••••••</span>
+          {getClientToken(client._id).slice(-4)}
+        </span>
         <Button
           variant="ghost"
           size="sm"
           onClick={() => {
             navigator.clipboard.writeText(getClientToken(client._id))
-            setErrorState("Token copied to clipboard!", "success")
+            setErrorState("API token copied to clipboard", "success")
           }}
+          title="Copy full token"
         >
           <Copy className="h-4 w-4" />
         </Button>
       </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
{getClientToken(client._id) ? (
<div className="flex items-center">
<span className="mr-2">
{getClientToken(client._id).slice(0, 2)}....
{getClientToken(client._id).slice(-2)}
</span>
<Button
variant="ghost"
size="sm"
onClick={() => {
navigator.clipboard.writeText(getClientToken(client._id))
setErrorState("Token copied to clipboard!", "success")
}}
>
<Copy className="h-4 w-4" />
</Button>
</div>
) : (
{getClientToken(client._id) ? (
<div className="flex items-center">
<span className="mr-2 font-mono">
{getClientToken(client._id).slice(0, 4)}
<span className="mx-1">•••••••</span>
{getClientToken(client._id).slice(-4)}
</span>
<Button
variant="ghost"
size="sm"
onClick={() => {
navigator.clipboard.writeText(getClientToken(client._id))
setErrorState("API token copied to clipboard", "success")
}}
title="Copy full token"
>
<Copy className="h-4 w-4" />
</Button>
</div>
) : (

import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
import { Skeleton } from "@/components/ui/skeleton"
import { Edit, Copy, Info } from "lucide-react"
import { api } from "../utils/api"tore"
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 syntax error in import statement.

There's a typo in the import statement that would prevent compilation.

-import { api } from "../utils/api"tore"
+import { api } from "../utils/api"

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

🧰 Tools
🪛 Biome (1.9.4)

[error] 13-13: Expected a semicolon or an implicit semicolon after a statement, but found none

An explicit or implicit semicolon is expected here...

...Which is required to end this statement

(parse)


[error] 13-13: unterminated string literal

The closing quote must be on the same line.

(parse)

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

🧹 Nitpick comments (9)
netmanager-app/core/hooks/useClients.ts (2)

13-14: Improve type safety by replacing any type

The data parameter uses the any type which bypasses TypeScript's type checking benefits. Consider defining a proper interface for the API response.

-    onSuccess: (data: any) => {
+    onSuccess: (data: { clients: Client[] }) => {
       dispatch(setClients(data));
     },

16-18: Enhance error handling

Consider adding error details to help with debugging.

     onError: (error: Error) => {
-      dispatch(setError(error.message));
+      dispatch(setError(`Failed to fetch clients: ${error.message}`));
+      console.error('Client fetch error:', error);
     },
netmanager-app/core/redux/slices/clientsSlice.ts (2)

30-32: Add type safety to action payload

The action payload type is missing which could lead to type-related bugs.

-    addClients: (state, action) => {
+    addClients: (state, action: PayloadAction<Client[]>) => {
       state.clients = action.payload;
     },

33-35: Add type safety to clientsDetails action

Similar to above, add type safety to the action payload.

-    addClientsDetails: (state, action) => {
+    addClientsDetails: (state, action: PayloadAction<Client[]>) => {
       state.clientsDetails = action.payload;
     },
netmanager-app/hooks/use-toast.ts (1)

96-104: Consider extracting side effect into separate action

The comment suggests extracting the side effect into a separate action. This would improve code organization and testability.

Consider creating a separate dismissToast action to handle the timeout side effect.

netmanager-app/app/(authenticated)/profile/components/CreateClientForm.tsx (1)

20-24: Consider adding error state management.

The component could benefit from explicit error state management to handle API failures and validation errors gracefully.

 const [clientName, setClientName] = useState('')
 const [ipAddresses, setIpAddresses] = useState([''])
 const [isLoading, setIsLoading] = useState(false)
+const [error, setError] = useState<{ message: string } | null>(null)
netmanager-app/app/(authenticated)/profile/components/ApiTokens.tsx (3)

35-54: Add error type and improve error handling in fetchClients.

The error handling could be more specific and informative.

-    } catch (error) {
-      console.error(error)
+    } catch (error: unknown) {
+      const errorMessage = error instanceof Error ? error.message : 'An unexpected error occurred'
+      console.error('Failed to fetch clients:', error)
       toast({
         title: "Error",
-        description: "Failed to fetch user details",
+        description: errorMessage,
         variant: "destructive",
       })

133-160: Remove unnecessary setTimeout in error handling.

The setTimeout calls in the error handling don't serve a clear purpose and could be removed.

     try {
       const clientID = selectedClient?._id
       const response = await activationRequestApi(clientID)
       if (response) {
         setShowInfoModal(false)
-        setTimeout(() => {
-          toast({
-            title: "Success",
-            description: "Activation request sent successfully",
-            variant: "success",
-          })
-        }, 3000)
+        toast({
+          title: "Success",
+          description: "Activation request sent successfully",
+          variant: "success",
+        })
       }
     } catch (error: any) {
       setShowInfoModal(false)
-      setTimeout(() => {
-        toast({
-          title: "Error",
-          description: error.message || "Failed to send activation request",
-          variant: "destructive",
-        })
-      }, 3000)
+      toast({
+        title: "Error",
+        description: error.message || "Failed to send activation request",
+        variant: "destructive",
+      })
     }

81-103: Optimize client lookup functions with optional chaining.

The helper functions for token-related operations could be simplified using optional chaining.

   const hasAccessToken = (clientId: string) => {
-    const client =
-      Array.isArray(clientsDetails) && clientsDetails
-        ? clientsDetails?.find((client: Client) => client._id === clientId)
-        : undefined
-    return client && client.access_token
+    return clientsDetails?.find((client: Client) => client._id === clientId)?.access_token
   }

   const getClientToken = (clientID: string) => {
-    const client =
-      Array.isArray(clientsDetails) && clientsDetails
-        ? clientsDetails?.find((client: Client) => client._id === clientID)
-        : undefined
-    return client && client.access_token && client.access_token.token
+    return clientsDetails?.find((client: Client) => client._id === clientID)?.access_token?.token
   }

   const getClientTokenExpiryDate = (clientID: string) => {
-    const client =
-      Array.isArray(clientsDetails) && clientsDetails
-        ? clientsDetails?.find((client: Client) => client._id === clientID)
-        : undefined
-    return client && client.access_token && client.access_token.expires
+    return clientsDetails?.find((client: Client) => client._id === clientID)?.access_token?.expires
   }
🧰 Tools
🪛 Biome (1.9.4)

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

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


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

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


[error] 102-102: 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 3c4ea58 and cc432de.

⛔ Files ignored due to path filters (1)
  • netmanager-app/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (11)
  • netmanager-app/app/(authenticated)/profile/components/ApiTokens.tsx (1 hunks)
  • netmanager-app/app/(authenticated)/profile/components/CreateClientForm.tsx (1 hunks)
  • netmanager-app/app/(authenticated)/profile/components/EditClientForm.tsx (1 hunks)
  • netmanager-app/components/ui/toast.tsx (9 hunks)
  • netmanager-app/components/ui/toaster.tsx (1 hunks)
  • netmanager-app/core/apis/settings.ts (1 hunks)
  • netmanager-app/core/hooks/useClients.ts (1 hunks)
  • netmanager-app/core/redux/slices/clientsSlice.ts (1 hunks)
  • netmanager-app/core/redux/store.ts (2 hunks)
  • netmanager-app/hooks/use-toast.ts (1 hunks)
  • netmanager-app/package.json (3 hunks)
✅ Files skipped from review due to trivial changes (1)
  • netmanager-app/components/ui/toast.tsx
🧰 Additional context used
🪛 Biome (1.9.4)
netmanager-app/app/(authenticated)/profile/components/ApiTokens.tsx

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

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


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

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


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

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

🔇 Additional comments (13)
netmanager-app/core/redux/store.ts (1)

Line range hint 9-18: LGTM on the store configuration!

The integration of the clients reducer follows the established pattern and Redux Toolkit best practices. The structure is consistent with other reducers in the store.

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

21-25: LGTM! Good error handling pattern

The fallback to empty array and proper error typing is a good practice.

netmanager-app/components/ui/toaster.tsx (1)

3-3: LGTM! Clean implementation

The import path change and component implementation follow React best practices.

netmanager-app/core/redux/slices/clientsSlice.ts (1)

5-11: LGTM! Well-structured state interface

The state interface is well-defined with proper typing and initial state values.

Also applies to: 13-19

netmanager-app/hooks/use-toast.ts (2)

11-12: Review toast configuration constants

  1. TOAST_LIMIT = 1 seems very restrictive. Consider increasing it to allow multiple toasts.
  2. TOAST_REMOVE_DELAY = 1000000 (~16.7 minutes) seems too long for a toast notification.

Would you like to adjust these values? Common values are:

  • TOAST_LIMIT: 3-5
  • TOAST_REMOVE_DELAY: 3000-5000 (3-5 seconds)

174-192: LGTM! Clean hook implementation

The hook implementation is clean with proper cleanup of listeners in the useEffect.

netmanager-app/package.json (1)

55-55: Consider alternatives to timezones.json package.

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

50-53: 🛠️ Refactor suggestion

Use consistent axios instance.

The function creates a new axios instance while other functions reuse the existing one.

-  return await createAxiosInstance()
+  return await axiosInstance
     .put(`${USERS_MGT_URL}/clients/${client_id}`, data)
     .then((response) => response.data);

Likely invalid or redundant comment.


60-68: 🛠️ Refactor suggestion

Add explicit return type definitions.

Functions are missing return type definitions which reduces type safety.

-export const getClientsApi = async () => {
+export const getClientsApi = async (): Promise<Client[]> => {

-export const activateUserClientApi = async (data: { _id: string; isActive: boolean }) => {
+export const activateUserClientApi = async (data: { _id: string; isActive: boolean }): Promise<{ success: boolean; message: string }> => {

Likely invalid or redundant comment.

netmanager-app/app/(authenticated)/profile/components/EditClientForm.tsx (1)

98-107: Improve error handling implementation.

netmanager-app/app/(authenticated)/profile/components/ApiTokens.tsx (3)

1-18: LGTM! Clean and well-organized imports.

The imports are logically grouped and all dependencies seem necessary for the component's functionality.


284-284: Implement the missing pagination component.

The comment indicates a missing pagination component. This issue was previously identified and a solution was proposed in the past review comments.


219-256: 🛠️ Refactor suggestion

Enhance token security with proper masking and clipboard handling.

The token display and clipboard functionality could be improved for better security.

   <TableCell>
     {getClientToken(client._id) ? (
       <div className="flex items-center">
         <span className="mr-2 font-mono">
           {getClientToken(client._id).slice(0, 4)}
           <span className="mx-1">•••••••</span>
           {getClientToken(client._id).slice(-4)}
         </span>
         <Button
           title="Copy full token"
           variant="ghost"
           size="sm"
           onClick={() => {
-            navigator.clipboard.writeText(getClientToken(client._id))
+            const token = getClientToken(client._id)
+            if (token) {
+              navigator.clipboard.writeText(token)
+                .then(() => {
+                  toast({
+                    title: "Success",
+                    description: "API token copied to clipboard",
+                    variant: "success",
+                  })
+                })
+                .catch(() => {
+                  toast({
+                    title: "Error",
+                    description: "Failed to copy token to clipboard",
+                    variant: "destructive",
+                  })
+                })
+            }
-            toast({
-              title: "Success",
-              description: "API token copied to clipboard",
-              variant: "success",
-            })
           }}
         >
           <Copy className="h-4 w-4" />

Likely invalid or redundant comment.

@@ -12,6 +13,7 @@ export const store = configureStore({
devices: devicesReducer,
grids: gridsReducer,
cohorts: cohortsReducer,
clients: clientsRudcer,
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Update the reducer name to match the import

Once the import is fixed, update the reducer configuration to use the correct name.

-    clients: clientsRudcer,
+    clients: clientsReducer,
📝 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
clients: clientsRudcer,
clients: clientsReducer,

@@ -4,6 +4,7 @@ import sitesReducer from "./slices/sitesSlice";
import devicesReducer from "./slices/devicesSlice";
import cohortsReducer from "./slices/cohortsSlice";
import gridsReducer from "./slices/gridsSlice";
import clientsRudcer from "./slices/clientsSlice";
Copy link

Choose a reason for hiding this comment

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

💡 Codebase verification

⚠️ Potential issue

Fix import statement to match source file naming

The import statement has incorrect naming. The source file exports the reducer as clientSlice.reducer (singular form).

-import clientsRudcer from "./slices/clientsSlice";
+import clientReducer from "./slices/clientsSlice";
🔗 Analysis chain

Fix typo in import name: clientsRudcerclientsReducer

There's a typo in the import name that should be corrected for better code maintainability and clarity.

-import clientsRudcer from "./slices/clientsSlice";
+import clientsReducer from "./slices/clientsSlice";

Let's verify if this typo exists in the source file as well:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if the typo exists in the source file
rg -l "clientsRudcer" "netmanager-app/core/redux/slices/clientsSlice.ts"

Length of output: 70


Script:

#!/bin/bash
# Find the file and inspect its content
fd "clientsSlice.ts" --type f
echo "=== File content ==="
cat netmanager-app/core/redux/slices/clientsSlice.ts 2>/dev/null || echo "File not found"

Length of output: 1522

Comment on lines +52 to +56
const data = {
name: clientName,
user_id: userInfo?._id,
ip_addresses: ipAddresses.filter((ip) => ip.trim() !== ''),
}
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 IP address format validation.

The code filters empty IP addresses but doesn't validate the format.

+const isValidIp = (ip: string): boolean => {
+  const ipv4Regex = /^(\d{1,3}\.){3}\d{1,3}$/;
+  if (!ipv4Regex.test(ip)) return false;
+  return ip.split('.').every(num => parseInt(num) >= 0 && parseInt(num) <= 255);
+}

 const data = {
   name: clientName,
   user_id: userInfo?._id,
-  ip_addresses: ipAddresses.filter((ip) => ip.trim() !== ''),
+  ip_addresses: ipAddresses.filter((ip) => {
+    const trimmedIp = ip.trim();
+    return trimmedIp !== '' && isValidIp(trimmedIp);
+  }),
 }
📝 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 = {
name: clientName,
user_id: userInfo?._id,
ip_addresses: ipAddresses.filter((ip) => ip.trim() !== ''),
}
const isValidIp = (ip: string): boolean => {
const ipv4Regex = /^(\d{1,3}\.){3}\d{1,3}$/;
if (!ipv4Regex.test(ip)) return false;
return ip.split('.').every(num => parseInt(num) >= 0 && parseInt(num) <= 255);
}
const data = {
name: clientName,
user_id: userInfo?._id,
ip_addresses: ipAddresses.filter((ip) => {
const trimmedIp = ip.trim();
return trimmedIp !== '' && isValidIp(trimmedIp);
}),
}

Comment on lines +68 to +74
} catch (error: any) {
toast({
title: "Error",
description: error.message || "Failed to create client",
variant: "destructive",
})
} finally {
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 error handling.

The error catch block uses any type which reduces type safety.

-    } catch (error: any) {
+    } catch (error) {
+      const errorMessage = error instanceof Error 
+        ? error.message 
+        : "Failed to create client"
       toast({
         title: "Error",
-        description: error.message || "Failed to create client",
+        description: errorMessage,
         variant: "destructive",
       })
📝 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
} catch (error: any) {
toast({
title: "Error",
description: error.message || "Failed to create client",
variant: "destructive",
})
} finally {
} catch (error) {
const errorMessage = error instanceof Error
? error.message
: "Failed to create client"
toast({
title: "Error",
description: errorMessage,
variant: "destructive",
})
} finally {

Comment on lines +92 to +96
const res = await users.getUserDetails(userInfo?._id || "")
const resp = await getUserClientsApi(userInfo?._id || "")
dispatch(addClients(res.users[0].clients))
dispatch(addClientsDetails(resp.clients))
dispatch(performRefresh())
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve API call sequence handling.

Multiple sequential API calls without individual error handling could lead to inconsistent state.

-      const res = await users.getUserDetails(userInfo?._id || "")
-      const resp = await getUserClientsApi(userInfo?._id || "")
-      dispatch(addClients(res.users[0].clients))
-      dispatch(addClientsDetails(resp.clients))
-      dispatch(performRefresh())
+      try {
+        const [userDetails, userClients] = await Promise.all([
+          users.getUserDetails(userInfo?._id || ""),
+          getUserClientsApi(userInfo?._id || "")
+        ]);
+        dispatch(addClients(userDetails.users[0].clients));
+        dispatch(addClientsDetails(userClients.clients));
+        dispatch(performRefresh());
+      } catch (error) {
+        console.error('Failed to refresh data:', error);
+        // Consider showing a warning toast that data might be stale
+      }
📝 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 res = await users.getUserDetails(userInfo?._id || "")
const resp = await getUserClientsApi(userInfo?._id || "")
dispatch(addClients(res.users[0].clients))
dispatch(addClientsDetails(resp.clients))
dispatch(performRefresh())
try {
const [userDetails, userClients] = await Promise.all([
users.getUserDetails(userInfo?._id || ""),
getUserClientsApi(userInfo?._id || "")
]);
dispatch(addClients(userDetails.users[0].clients));
dispatch(addClientsDetails(userClients.clients));
dispatch(performRefresh());
} catch (error) {
console.error('Failed to refresh data:', error);
// Consider showing a warning toast that data might be stale
}

Comment on lines +105 to +131
const handleGenerateToken = async (res: Client) => {
setIsLoadingToken(true)
if (!res?.isActive) {
setShowInfoModal(true)
setIsLoadingToken(false)
} else {
try {
const response = await generateTokenApi(res)
if (response) {
toast({
title: "Success",
description: "Token generated successfully",
variant: "success",
})
}
dispatch(performRefresh())
} catch (error: any) {
toast({
title: "Error",
description: error.message || "Failed to generate token",
variant: "destructive",
})
} finally {
setIsLoadingToken(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

Improve token generation error handling and add rate limiting.

The token generation could benefit from additional security measures.

   const handleGenerateToken = async (res: Client) => {
     setIsLoadingToken(true)
+    const rateLimitKey = `token_gen_${res._id}`
+    const lastAttempt = localStorage.getItem(rateLimitKey)
+    
+    if (lastAttempt && Date.now() - Number(lastAttempt) < 60000) {
+      toast({
+        title: "Error",
+        description: "Please wait 1 minute before generating another token",
+        variant: "destructive",
+      })
+      setIsLoadingToken(false)
+      return
+    }
+    
     if (!res?.isActive) {
       setShowInfoModal(true)
       setIsLoadingToken(false)
     } else {
       try {
         const response = await generateTokenApi(res)
         if (response) {
+          localStorage.setItem(rateLimitKey, Date.now().toString())
           toast({
             title: "Success",
             description: "Token generated successfully",
             variant: "success",
           })
         }
📝 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 handleGenerateToken = async (res: Client) => {
setIsLoadingToken(true)
if (!res?.isActive) {
setShowInfoModal(true)
setIsLoadingToken(false)
} else {
try {
const response = await generateTokenApi(res)
if (response) {
toast({
title: "Success",
description: "Token generated successfully",
variant: "success",
})
}
dispatch(performRefresh())
} catch (error: any) {
toast({
title: "Error",
description: error.message || "Failed to generate token",
variant: "destructive",
})
} finally {
setIsLoadingToken(false)
}
}
}
const handleGenerateToken = async (res: Client) => {
setIsLoadingToken(true)
const rateLimitKey = `token_gen_${res._id}`
const lastAttempt = localStorage.getItem(rateLimitKey)
if (lastAttempt && Date.now() - Number(lastAttempt) < 60000) {
toast({
title: "Error",
description: "Please wait 1 minute before generating another token",
variant: "destructive",
})
setIsLoadingToken(false)
return
}
if (!res?.isActive) {
setShowInfoModal(true)
setIsLoadingToken(false)
} else {
try {
const response = await generateTokenApi(res)
if (response) {
localStorage.setItem(rateLimitKey, Date.now().toString())
toast({
title: "Success",
description: "Token generated successfully",
variant: "success",
})
}
dispatch(performRefresh())
} catch (error: any) {
toast({
title: "Error",
description: error.message || "Failed to generate token",
variant: "destructive",
})
} finally {
setIsLoadingToken(false)
}
}
}

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 (2)
netmanager-app/app/(authenticated)/profile/components/Clients.tsx (2)

24-37: Enhance date formatting utility for better safety and efficiency.

The formatDate function has a few areas for improvement:

  1. Replace unsafe isNaN with Number.isNaN
  2. Use more efficient date comparisons
 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 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)


41-49: Consider consolidating related state variables.

The component uses multiple independent state variables that could be combined into a single state object for better maintainability.

-  const [clients, setClients] = useState<Client[]>([])
-  const [loading, setLoading] = useState(false)
-  const [selectedClient, setSelectedClient] = useState<Client | null>(null)
-  const [activateDialogOpen, setActivateDialogOpen] = useState(false)
-  const [deactivateDialogOpen, setDeactivateDialogOpen] = useState(false)
-  const [searchQuery, setSearchQuery] = useState("")
-  const [currentPage, setCurrentPage] = useState(1)
-  const [sortField, setSortField] = useState<keyof Client>("name")
-  const [sortOrder, setSortOrder] = useState<"asc" | "desc">("asc")
+  const [state, setState] = useState({
+    clients: [] as Client[],
+    loading: false,
+    selectedClient: null as Client | null,
+    dialogs: {
+      activate: false,
+      deactivate: false
+    },
+    search: {
+      query: "",
+      currentPage: 1,
+      sortField: "name" as keyof Client,
+      sortOrder: "asc" as "asc" | "desc"
+    }
+  })
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 91afbf1 and 6d5b0d6.

📒 Files selected for processing (3)
  • netmanager-app/app/(authenticated)/profile/components/Clients.tsx (1 hunks)
  • netmanager-app/app/(authenticated)/profile/components/ProfileTabs.tsx (1 hunks)
  • netmanager-app/app/(authenticated)/profile/page.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • netmanager-app/app/(authenticated)/profile/page.tsx
  • netmanager-app/app/(authenticated)/profile/components/ProfileTabs.tsx
🧰 Additional context used
🪛 Biome (1.9.4)
netmanager-app/app/(authenticated)/profile/components/Clients.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)

Comment on lines 227 to 237
<TableCell className="text-right">
<Button
variant="ghost"
onClick={() => (client.isActive ? handleDeactivateClick(client) : handleActivateClick(client))}
>
{client.isActive ? "Deactivate" : "Activate"}
</Button>
<Button variant="ghost" onClick={() => handleCopyClientId(client._id)}>
Copy ID
</Button>
</TableCell>
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve button spacing and accessibility in actions column.

Addressing the PR comment about button spacing and enhancing accessibility:

   <TableCell className="text-right">
+    <div className="space-x-2 flex justify-end">
       <Button
         variant="ghost"
+        aria-label={`${client.isActive ? "Deactivate" : "Activate"} ${client.name}`}
         onClick={() => (client.isActive ? handleDeactivateClick(client) : handleActivateClick(client))}
       >
         {client.isActive ? "Deactivate" : "Activate"}
       </Button>
       <Button
         variant="ghost"
+        aria-label={`Copy ID for ${client.name}`}
         onClick={() => handleCopyClientId(client._id)}
       >
         Copy ID
       </Button>
+    </div>
   </TableCell>
📝 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
<TableCell className="text-right">
<Button
variant="ghost"
onClick={() => (client.isActive ? handleDeactivateClick(client) : handleActivateClick(client))}
>
{client.isActive ? "Deactivate" : "Activate"}
</Button>
<Button variant="ghost" onClick={() => handleCopyClientId(client._id)}>
Copy ID
</Button>
</TableCell>
<TableCell className="text-right">
<div className="space-x-2 flex justify-end">
<Button
variant="ghost"
aria-label={`${client.isActive ? "Deactivate" : "Activate"} ${client.name}`}
onClick={() => (client.isActive ? handleDeactivateClick(client) : handleActivateClick(client))}
>
{client.isActive ? "Deactivate" : "Activate"}
</Button>
<Button
variant="ghost"
aria-label={`Copy ID for ${client.name}`}
onClick={() => handleCopyClientId(client._id)}
>
Copy ID
</Button>
</div>
</TableCell>

Comment on lines 120 to 130
const filteredClients = clients.filter(
(client) =>
client.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
client.user.email.toLowerCase().includes(searchQuery.toLowerCase()),
)

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
})
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Optimize data processing with memoization.

The filtering and sorting operations are recalculated on every render. Consider using useMemo to cache these results.

+  const filteredClients = useMemo(() =>
-  const filteredClients = clients.filter(
     (client) =>
       client.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
       client.user.email.toLowerCase().includes(searchQuery.toLowerCase()),
-  )
+  ), [clients, searchQuery])

+  const sortedClients = useMemo(() =>
-  const sortedClients = [...filteredClients].sort((a, b) => {
+    [...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
-  })
+  }), [filteredClients, sortField, sortOrder])
📝 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 filteredClients = clients.filter(
(client) =>
client.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
client.user.email.toLowerCase().includes(searchQuery.toLowerCase()),
)
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 filteredClients = useMemo(() =>
clients.filter(
(client) =>
client.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
client.user.email.toLowerCase().includes(searchQuery.toLowerCase()),
), [clients, searchQuery])
const sortedClients = useMemo(() =>
[...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
}), [filteredClients, sortField, sortOrder])

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 (2)
netmanager-app/app/(authenticated)/profile/components/ProfileTabs.tsx (2)

11-16: Add error handling for permission check.

Consider adding error handling for the permission check to gracefully handle any potential issues:

 export default function ProfileTabs() {
   const [activeTab, setActiveTab] = useState("profile")
-  const { hasPermission } = usePermissions()
+  const { hasPermission, error } = usePermissions()
+  
+  if (error) {
+    return <div>Error loading permissions: {error.message}</div>
+  }

   const canManageClients = hasPermission("CREATE_UPDATE_AND_DELETE_NETWORK_USERS")

17-41: Add loading states for better UX.

Consider adding loading states to improve user experience while content is being fetched:

 export default function ProfileTabs() {
   const [activeTab, setActiveTab] = useState("profile")
-  const { hasPermission } = usePermissions()
+  const { hasPermission, isLoading } = usePermissions()
+
+  if (isLoading) {
+    return <div>Loading profile settings...</div>
+  }

   const canManageClients = hasPermission("CREATE_UPDATE_AND_DELETE_NETWORK_USERS")

   return (
     <Tabs value={activeTab} onValueChange={setActiveTab} className="w-full">
       <TabsList className={`grid w-full ${canManageClients ? "grid-cols-4" : "grid-cols-3"}`}>
         <TabsTrigger value="profile" disabled={isLoading}>My Profile</TabsTrigger>
         <TabsTrigger value="password" disabled={isLoading}>Password Edit</TabsTrigger>
         <TabsTrigger value="api" disabled={isLoading}>API Access Tokens</TabsTrigger>
         {canManageClients && <TabsTrigger value="clients" disabled={isLoading}>Clients</TabsTrigger>}
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 6d5b0d6 and d97fc00.

📒 Files selected for processing (1)
  • netmanager-app/app/(authenticated)/profile/components/ProfileTabs.tsx (1 hunks)
🔇 Additional comments (2)
netmanager-app/app/(authenticated)/profile/components/ProfileTabs.tsx (2)

1-10: LGTM! Well-organized imports and proper Next.js setup.

The imports are logically grouped and the "use client" directive is correctly placed.


17-41: Previous accessibility improvements are still relevant.

The previous review's suggestions for error boundaries and accessibility improvements haven't been addressed yet.

Comment on lines +25 to +33
<TabsContent value="profile">
<MyProfile />
</TabsContent>
<TabsContent value="password">
<PasswordEdit />
</TabsContent>
<TabsContent value="api">
<ApiTokens />
</TabsContent>
Copy link

Choose a reason for hiding this comment

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

💡 Codebase verification

Implementation gaps found in profile management components

The profile management features are indeed non-functional:

  • MyProfile component has a form submission that only toggles editing state without actual profile updates
  • ApiTokens component appears to be missing token creation implementation entirely
🔗 Analysis chain

Verify functionality of profile management features.

Based on PR comments, there are issues with update/delete buttons and token creation. Let's verify the implementation in the child components:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check implementation of profile management features

# Check MyProfile component for update/delete functionality
echo "Checking MyProfile component..."
rg -A 10 "function.*update.*profile|function.*delete.*profile" "netmanager-app/app/(authenticated)/profile/components/MyProfile.tsx"

# Check ApiTokens component for token creation
echo "Checking ApiTokens component..."
rg -A 10 "function.*create.*token" "netmanager-app/app/(authenticated)/profile/components/ApiTokens.tsx"

Length of output: 387


Script:

#!/bin/bash
# Broader search for profile management implementations

echo "Checking MyProfile component..."
rg -A 10 "onSubmit|handleSubmit|mutation|updateProfile|deleteProfile" "netmanager-app/app/(authenticated)/profile/components/MyProfile.tsx"

echo "Checking ApiTokens component..."
rg -A 10 "onSubmit|handleSubmit|mutation|createToken|addToken" "netmanager-app/app/(authenticated)/profile/components/ApiTokens.tsx"

# Check for API endpoints usage
echo "Checking API endpoints usage..."
rg -A 5 "api/profile|api/token" "netmanager-app/app/(authenticated)/profile/components/"

# Check for form components
echo "Checking form implementations..."
ast-grep --pattern 'form onSubmit={$_}' "netmanager-app/app/(authenticated)/profile/components/"

Length of output: 1666

@airqo-platform airqo-platform deleted a comment from coderabbitai bot Feb 1, 2025
@airqo-platform airqo-platform deleted a comment from Codebmk Feb 1, 2025
@airqo-platform airqo-platform deleted a comment from coderabbitai bot Feb 1, 2025
…r handling; add profile image upload functionality with size validation and success/error notifications.
@airqo-platform airqo-platform deleted a comment from coderabbitai bot Feb 1, 2025
… improve profile picture upload handling and state management.
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: 4

♻️ Duplicate comments (1)
netmanager-app/app/(authenticated)/profile/components/MyProfile.tsx (1)

92-95: ⚠️ Potential issue

Implement profile update functionality.

The handleSubmit function is incomplete and doesn't update the profile data in the backend.

🧹 Nitpick comments (4)
netmanager-app/app/(authenticated)/profile/components/MyProfile.tsx (1)

40-74: Prevent potential memory leak in useEffect.

The fetch operation could complete after the component unmounts. Consider adding a cleanup function.

 useEffect(() => {
+  let isSubscribed = true;
   const fetchUserData = async () => {
     if (!currentuser) return;
 
     setIsLoading(true);
     setError(null);
 
     try {
       const response = await users.getUserDetails(currentuser._id);
       const userData = response?.users?.[0];
 
-      if (userData) {
+      if (userData && isSubscribed) {
         setProfile({
           firstName: userData.firstName || "",
           lastName: userData.lastName || "",
           email: userData.email || "",
           jobTitle: userData.jobTitle || "",
           country: userData.country || "",
           timezone: userData.timezone || "",
           bio: userData.description || "",
           profilePicture: userData.profilePicture || "",
         });
       } else {
         setError("User data not found");
       }
     } catch (error) {
-      setError("Failed to fetch user data");
+      if (isSubscribed) {
+        setError("Failed to fetch user data");
+      }
       console.error("Error fetching user data:", error);
     } finally {
-      setIsLoading(false);
+      if (isSubscribed) {
+        setIsLoading(false);
+      }
     }
   };
 
   fetchUserData();
+  return () => {
+    isSubscribed = false;
+  };
 }, [currentuser]);
netmanager-app/core/apis/settings.ts (1)

7-13: Add JSDoc documentation to the interface.

Consider adding JSDoc documentation to describe the purpose and properties of the CreateClientData interface.

+/**
+ * Interface representing the data required to create a new client.
+ * @property {string} name - The name of the client.
+ * @property {string[]} ip_addresses - List of allowed IP addresses.
+ * @property {boolean} isActive - Whether the client is active.
+ * @property {string} client_secret - The client's secret key.
+ * @property {string} user_id - The associated user ID.
+ */
 interface CreateClientData {
   name: string;
   ip_addresses: string[];
   isActive: boolean;
   client_secret: string;
   user_id: string;
 }
netmanager-app/app/(authenticated)/profile/components/Clients.tsx (2)

74-97: Simplify boolean assignment.

Remove the unnecessary ternary operator for the boolean assignment.

   const data = {
     _id: clientId,
-    isActive: activate ? true : false,
+    isActive: activate,
   };
🧰 Tools
🪛 Biome (1.9.4)

[error] 77-77: Unnecessary use of boolean literals in conditional expression.

Simplify your code by directly assigning the result without using a ternary operator.
If your goal is negation, you may use the logical NOT (!) or double NOT (!!) operator for clearer and concise code.
Check for more details about NOT operator.
Unsafe fix: Remove the conditional expression with

(lint/complexity/noUselessTernary)


209-209: Use optional chaining for cleaner code.

Simplify the nested property access using optional chaining.

-<TableCell>{client.user && client.user.email ? client.user.email : "N/A"}</TableCell>
+<TableCell>{client.user?.email ?? "N/A"}</TableCell>
🧰 Tools
🪛 Biome (1.9.4)

[error] 209-209: 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 d97fc00 and 6cb5db2.

📒 Files selected for processing (4)
  • netmanager-app/app/(authenticated)/profile/components/Clients.tsx (1 hunks)
  • netmanager-app/app/(authenticated)/profile/components/MyProfile.tsx (1 hunks)
  • netmanager-app/components/sidebar.tsx (0 hunks)
  • netmanager-app/core/apis/settings.ts (1 hunks)
💤 Files with no reviewable changes (1)
  • netmanager-app/components/sidebar.tsx
🧰 Additional context used
🪛 Biome (1.9.4)
netmanager-app/app/(authenticated)/profile/components/Clients.tsx

[error] 77-77: Unnecessary use of boolean literals in conditional expression.

Simplify your code by directly assigning the result without using a ternary operator.
If your goal is negation, you may use the logical NOT (!) or double NOT (!!) operator for clearer and concise code.
Check for more details about NOT operator.
Unsafe fix: Remove the conditional expression with

(lint/complexity/noUselessTernary)


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

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


[error] 28-28: 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)/profile/components/MyProfile.tsx (1)

18-27: LGTM! Well-defined TypeScript interface.

The Profile interface is properly typed with all necessary fields and optional properties.

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

46-53: Use the existing axios instance.

A new axios instance is created for this request while an existing instance is available.

 export const updateClientApi = async (
   data: CreateClientData,
   client_id: string
 ): Promise<Client> => {
-  return await createAxiosInstance()
+  return await axiosInstance
     .put(`${USERS_MGT_URL}/clients/${client_id}`, data)
     .then((response) => response.data);
 };
netmanager-app/app/(authenticated)/profile/components/Clients.tsx (2)

227-234: Improve button spacing and accessibility in actions column.

Add proper spacing between buttons and enhance accessibility with ARIA labels.

 <TableCell className="text-right">
+  <div className="space-x-2 flex justify-end">
     <Button
       variant="ghost"
+      aria-label={`${client.isActive ? "Deactivate" : "Activate"} ${client.name}`}
       onClick={() => (client.isActive ? handleDeactivateClick(client) : handleActivateClick(client))}
     >
       {client.isActive ? "Deactivate" : "Activate"}
     </Button>
+  </div>
 </TableCell>

118-129: Optimize data processing with memoization.

The filtering and sorting operations are recalculated on every render. Use useMemo to cache these results.

+import { useMemo } from "react"

-const filteredClients = clients.filter(
+const filteredClients = useMemo(() => clients.filter(
   (client) =>
     client.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
     (client.user?.email?.toLowerCase().includes(searchQuery.toLowerCase()) ?? false)
-  )
+), [clients, searchQuery])

-const sortedClients = [...filteredClients].sort((a, b) => {
+const sortedClients = useMemo(() => [...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
-})
+}), [filteredClients, sortField, sortOrder])

Comment on lines 206 to 208
<Button type="button" variant="outline" className="text-red-500">
Delete
</Button>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Implement profile picture deletion.

The delete button is present but lacks an onClick handler for removing the profile picture.

-<Button type="button" variant="outline" className="text-red-500">
+<Button 
+  type="button" 
+  variant="outline" 
+  className="text-red-500"
+  onClick={async () => {
+    try {
+      await fetch('/api/delete-profile-image', {
+        method: 'DELETE',
+      });
+      setProfile(prev => prev ? ({
+        ...prev,
+        profilePicture: '',
+      }) : null);
+      toast({
+        title: "Success",
+        description: "Profile image deleted successfully",
+      });
+    } catch (error) {
+      toast({
+        title: "Error",
+        description: "Failed to delete profile image",
+        variant: "destructive",
+      });
+    }
+  }}
+>
   Delete
 </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 type="button" variant="outline" className="text-red-500">
Delete
</Button>
<Button
type="button"
variant="outline"
className="text-red-500"
onClick={async () => {
try {
await fetch('/api/delete-profile-image', {
method: 'DELETE',
});
setProfile(prev => prev ? ({
...prev,
profilePicture: '',
}) : null);
toast({
title: "Success",
description: "Profile image deleted successfully",
});
} catch (error) {
toast({
title: "Error",
description: "Failed to delete profile image",
variant: "destructive",
});
}
}}
>
Delete
</Button>

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

const [isEditing, setIsEditing] = useState(false);
Copy link

Choose a reason for hiding this comment

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

💡 Codebase verification

Issue Detected: Immediate Toggle of isEditing Without Awaiting API Response

  • The isEditing state is initialized as shown, but in the handleSubmit function it’s set to false immediately after preventing the default form submission—without any asynchronous operation for API integration.
  • This means the form inputs meant to be disabled during submission aren’t truly protected against premature user interaction if an update fails.
  • Consider adopting a dedicated submission state (e.g., isSubmitting) and waiting for the API response before updating the UI, ensuring a more robust user experience.
🔗 Analysis chain

Review isEditing state usage.

The isEditing state is used to disable form inputs, but it's set to false immediately in handleSubmit without waiting for the API response. This could lead to a poor user experience if the update fails.

Consider implementing proper form submission with API integration:

-const [isEditing, setIsEditing] = useState(false);
+const [isSubmitting, setIsSubmitting] = useState(false);
🏁 Scripts executed

The following scripts were executed for the analysis:

Error: No shell scripts found


Script:

#!/bin/bash
echo "=== Searching for handleSubmit usage and context ==="
rg --context 5 "handleSubmit" "netmanager-app/app/(authenticated)/profile/components/MyProfile.tsx"
echo ""
echo "=== Searching for setIsEditing usage and context ==="
rg --context 5 "setIsEditing" "netmanager-app/app/(authenticated)/profile/components/MyProfile.tsx"

Length of output: 1927

Comment on lines +64 to +68
export const activateUserClientApi = async (data: { _id: string; isActive: boolean }) => {
return axiosInstance
.post(`${USERS_MGT_URL}/clients/activate/${data._id}`, data)
.then((response) => response.data);
};
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 return type annotation and error handling.

The function should have proper type annotations and error handling for better type safety and robustness.

-export const activateUserClientApi = async (data: { _id: string; isActive: boolean }) => {
+export const activateUserClientApi = async (data: { _id: string; isActive: boolean }): Promise<{ success: boolean; message: string }> => {
+  try {
     return axiosInstance
       .post(`${USERS_MGT_URL}/clients/activate/${data._id}`, data)
       .then((response) => response.data);
+  } catch (error) {
+    console.error('Error activating client:', error);
+    throw error;
+  }
 };
📝 Committable suggestion

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

Suggested change
export const activateUserClientApi = async (data: { _id: string; isActive: boolean }) => {
return axiosInstance
.post(`${USERS_MGT_URL}/clients/activate/${data._id}`, data)
.then((response) => response.data);
};
export const activateUserClientApi = async (data: { _id: string; isActive: boolean }): Promise<{ success: boolean; message: string }> => {
try {
return axiosInstance
.post(`${USERS_MGT_URL}/clients/activate/${data._id}`, data)
.then((response) => response.data);
} catch (error) {
console.error('Error activating client:', error);
throw error;
}
};

Comment on lines +25 to +38
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

Use Number.isNaN for safer type checking.

Replace the unsafe isNaN function with Number.isNaN as recommended by static analysis.

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

   if (diffDays < 0) return "Expired"
   if (diffDays === 0) return "Expires today"
   if (diffDays === 1) return "Expires tomorrow"
   return `Expires in ${diffDays} days`
 }
📝 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] 28-28: 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)

@airqo-platform airqo-platform deleted a comment from coderabbitai bot Feb 1, 2025
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: 4

🧹 Nitpick comments (1)
netmanager-app/app/(authenticated)/profile/components/MyProfile.tsx (1)

305-308: Add loading state indicator for form submission.

The submit button should show a loading state during form submission.

-          <Button type="submit" disabled={isLoading}>
-            {isLoading ? <Loader2 className="h-4 w-4 animate-spin mr-2" /> : null}
+          <Button type="submit" disabled={isLoading} className="w-full sm:w-auto">
+            {isLoading ? (
+              <>
+                <Loader2 className="h-4 w-4 animate-spin mr-2" />
+                Saving...
+              </>
+            ) : (
+              'Save Changes'
+            )}
           </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 6cb5db2 and 5307a67.

📒 Files selected for processing (4)
  • netmanager-app/app/(authenticated)/profile/components/MyProfile.tsx (1 hunks)
  • netmanager-app/components/ui/toaster.tsx (1 hunks)
  • netmanager-app/core/apis/cloudinary.ts (1 hunks)
  • netmanager-app/core/apis/settings.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • netmanager-app/components/ui/toaster.tsx

Comment on lines +3 to +8
export const cloudinaryImageUpload = async (formData: any) => {
return await fetch(`${cloudinaryUrl}/image/upload`, {
method: 'POST',
body: formData,
}).then((response) => response.json());
};
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 and error handling to cloudinaryImageUpload.

The function could be improved with proper TypeScript types and error handling.

+interface CloudinaryResponse {
+  secure_url: string;
+  public_id: string;
+}
+
-export const cloudinaryImageUpload = async (formData: any) => {
+export const cloudinaryImageUpload = async (formData: FormData): Promise<CloudinaryResponse> => {
+  if (!process.env.NEXT_PUBLIC_CLOUDINARY_NAME) {
+    throw new Error('Cloudinary configuration is missing');
+  }
+
+  try {
     return await fetch(`${cloudinaryUrl}/image/upload`, {
       method: 'POST',
       body: formData,
     }).then((response) => response.json());
+  } catch (error) {
+    console.error('Error uploading to Cloudinary:', error);
+    throw new Error('Failed to upload image');
+  }
 };
📝 Committable suggestion

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

Suggested change
export const cloudinaryImageUpload = async (formData: any) => {
return await fetch(`${cloudinaryUrl}/image/upload`, {
method: 'POST',
body: formData,
}).then((response) => response.json());
};
interface CloudinaryResponse {
secure_url: string;
public_id: string;
}
export const cloudinaryImageUpload = async (formData: FormData): Promise<CloudinaryResponse> => {
if (!process.env.NEXT_PUBLIC_CLOUDINARY_NAME) {
throw new Error('Cloudinary configuration is missing');
}
try {
return await fetch(`${cloudinaryUrl}/image/upload`, {
method: 'POST',
body: formData,
}).then((response) => response.json());
} catch (error) {
console.error('Error uploading to Cloudinary:', error);
throw new Error('Failed to upload image');
}
};

Comment on lines +100 to +130
const handleProfileImageUpdate = async (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0]
if (!file) return

const formData = new FormData()
formData.append("file", file)
formData.append("upload_preset", process.env.NEXT_PUBLIC_CLOUDINARY_PRESET || "")
formData.append("folder", "profiles")

setProfileUploading(true)
try {
const responseData = await cloudinaryImageUpload(formData)
if (profile && currentUser) {
const updatedProfile = { ...profile, profilePicture: responseData.secure_url }
setProfile(updatedProfile)
await updateUserDetailsApi({ profilePicture: responseData.secure_url }, currentUser._id)
dispatch(setUserDetails({ ...currentUser, profilePicture: responseData.secure_url }))
toast({
title: "Success",
description: "Profile picture updated successfully",
})
}
} catch (error) {
toast({
title: "Error",
description: "Failed to update profile picture",
})
} finally {
setProfileUploading(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

Add file validation to handleProfileImageUpdate.

The function lacks file size and type validation for image uploads.

+const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB
+const ALLOWED_FILE_TYPES = ['image/jpeg', 'image/png', 'image/gif'];

 const handleProfileImageUpdate = async (e: React.ChangeEvent<HTMLInputElement>) => {
   const file = e.target.files?.[0]
   if (!file) return
 
+  if (!ALLOWED_FILE_TYPES.includes(file.type)) {
+    toast({
+      title: "Error",
+      description: "Please upload a valid image file (JPEG, PNG, or GIF)",
+    });
+    return;
+  }
+
+  if (file.size > MAX_FILE_SIZE) {
+    toast({
+      title: "Error",
+      description: "File size should not exceed 5MB",
+    });
+    return;
+  }

   const formData = new FormData()
📝 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 handleProfileImageUpdate = async (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0]
if (!file) return
const formData = new FormData()
formData.append("file", file)
formData.append("upload_preset", process.env.NEXT_PUBLIC_CLOUDINARY_PRESET || "")
formData.append("folder", "profiles")
setProfileUploading(true)
try {
const responseData = await cloudinaryImageUpload(formData)
if (profile && currentUser) {
const updatedProfile = { ...profile, profilePicture: responseData.secure_url }
setProfile(updatedProfile)
await updateUserDetailsApi({ profilePicture: responseData.secure_url }, currentUser._id)
dispatch(setUserDetails({ ...currentUser, profilePicture: responseData.secure_url }))
toast({
title: "Success",
description: "Profile picture updated successfully",
})
}
} catch (error) {
toast({
title: "Error",
description: "Failed to update profile picture",
})
} finally {
setProfileUploading(false)
}
}
+const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB
+const ALLOWED_FILE_TYPES = ['image/jpeg', 'image/png', 'image/gif'];
const handleProfileImageUpdate = async (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0]
if (!file) return
+ if (!ALLOWED_FILE_TYPES.includes(file.type)) {
+ toast({
+ title: "Error",
+ description: "Please upload a valid image file (JPEG, PNG, or GIF)",
+ });
+ return;
+ }
+
+ if (file.size > MAX_FILE_SIZE) {
+ toast({
+ title: "Error",
+ description: "File size should not exceed 5MB",
+ });
+ return;
+ }
const formData = new FormData()
formData.append("file", file)
formData.append("upload_preset", process.env.NEXT_PUBLIC_CLOUDINARY_PRESET || "")
formData.append("folder", "profiles")
setProfileUploading(true)
try {
const responseData = await cloudinaryImageUpload(formData)
if (profile && currentUser) {
const updatedProfile = { ...profile, profilePicture: responseData.secure_url }
setProfile(updatedProfile)
await updateUserDetailsApi({ profilePicture: responseData.secure_url }, currentUser._id)
dispatch(setUserDetails({ ...currentUser, profilePicture: responseData.secure_url }))
toast({
title: "Success",
description: "Profile picture updated successfully",
})
}
} catch (error) {
toast({
title: "Error",
description: "Failed to update profile picture",
})
} finally {
setProfileUploading(false)
}
}

@airqo-platform airqo-platform deleted a comment from coderabbitai bot Feb 2, 2025
…indicator; enhance user feedback with loading states and error handling
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 (6)
netmanager-app/app/(authenticated)/profile/components/PasswordEdit.tsx (4)

17-21: Consider ephemeral handling of password fields.
Storing passwords in state potentially keeps them in memory for longer than necessary. If feasible, implement ephemeral handling or secure input fields that don't persist password data extensively in state.


68-75: Refine password strength logic.
Currently, the strength calculation only checks length, uppercase, lowercase, and digits. It might be beneficial to integrate the same criteria used in passwordRegex (including special characters) to keep the strength meter consistent with final validations.


77-86: Potential missing 2FA prompt or extra check.
When changing a password, consider adding a 2FA prompt or extra confirmation step for enhanced security. This ensures only authenticated users can proceed with password changes.


200-203: Align password strength message with complexity requirements.
It may be clearer to display a short descriptive message (e.g., "Weak", "Moderate", "Strong") aligning with numeric progress. This provides better usability than a raw percentage.

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

39-45: Return type mismatch for updateUserPasswordApi.
This API returns a response shape with a success message (or an error string). Using Promise<PasswordData> could be misleading if the server actually returns a different structure. Consider defining and returning a more explicit response type.


65-69: Add error handling for activateUserClientApi.
Enhance type safety and handle potential HTTP errors for robust production scenarios. Logging or throwing within a .catch() block can prevent silent failures when the request fails.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 5307a67 and c0f547d.

📒 Files selected for processing (2)
  • netmanager-app/app/(authenticated)/profile/components/PasswordEdit.tsx (1 hunks)
  • netmanager-app/core/apis/settings.ts (1 hunks)
🔇 Additional comments (2)
netmanager-app/app/(authenticated)/profile/components/PasswordEdit.tsx (1)

37-41: Prevent overshadowing validations.
When updating passwords state, ensure the subsequent validateField call doesn't overwrite or reset prior user error messages. For instance, consider using functional updates or merging partial validations to preserve earlier checks.

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

22-32: Confirm existence of user ID route.
Ensure the GET route (${USERS_MGT_URL}/clients?user_id=${userID}) is valid. An unexpected route or mismatch can lead to silent failures or 404 errors.

Comment on lines +61 to +63
export const getClientsApi = async () => {
return axiosInstance.get(`${USERS_MGT_URL}/clients`).then((response) => response.data);
};
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Incorporate explicit return type for getClientsApi.
Currently, the function returns a promise of unknown shape. Specifying an interface (e.g., Promise<Client[]>) or a properly typed object helps avoid confusion and reduces runtime errors.

…dd Radix UI progress component for enhanced loading indicators
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 (2)
netmanager-app/components/ui/progress.tsx (1)

12-25: Consider adding bounds checking for the value prop.

While the implementation is solid, we could make the transform calculation more robust by ensuring the value stays within valid bounds.

Consider this enhancement:

-      style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
+      style={{ transform: `translateX(-${100 - Math.min(Math.max(value || 0, 0), 100)}%)` }}

This ensures the value stays between 0 and 100, preventing potential visual glitches.

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

15-18: Consider adding password validation constraints.

The PasswordData interface could benefit from JSDoc comments specifying password requirements (e.g., minimum length, required characters).

 interface PasswordData {
+  /** Password must be at least 8 characters long and include uppercase, lowercase, number, and special character */
   password: string;
+  /** Current password for verification */
   old_password: string;
 }
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between c0f547d and 71ff5a8.

⛔ Files ignored due to path filters (1)
  • netmanager-app/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (3)
  • netmanager-app/components/ui/progress.tsx (1 hunks)
  • netmanager-app/core/apis/settings.ts (1 hunks)
  • netmanager-app/package.json (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • netmanager-app/package.json
🔇 Additional comments (6)
netmanager-app/components/ui/progress.tsx (4)

1-7: LGTM! Clean and well-organized imports.

The imports are properly structured with the "use client" directive correctly placed at the top, followed by external and internal dependencies.


8-11: Well-typed component definition!

Excellent use of TypeScript with proper type definitions for both the ref and component props using Radix UI's types.


26-28: LGTM! Proper component naming and export.

The displayName is correctly set for better debugging experience, and the export is clean and well-structured.


1-28: Verify integration with profile settings page.

Since this Progress component is part of the Profile Settings Page PR, let's verify its integration and usage.

✅ Verification successful

Integration Verified:

The Progress component is correctly integrated within the Profile Settings Page in the file:

  • netmanager-app/app/(authenticated)/profile/components/PasswordEdit.tsx

This confirms that the component is being utilized as expected for profile settings.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for Progress component usage in profile-related files
rg -l "Progress" netmanager-app/app/\(authenticated\)/profile/

Length of output: 133

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

63-65: Add return type and error handling to getClientsApi.

The function is missing a return type annotation and error handling.


67-71: Add return type and error handling to activateUserClientApi.

The function is missing a return type annotation and error handling.

Comment on lines +34 to +37
export const createClientApi = async (data: CreateClientData): Promise<Client> => {
return await axiosInstance.post<CreateClientData, AxiosResponse<Client>>(`${USERS_MGT_URL}/clients`, data)
.then((response) => response.data);
};
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 consistent error handling across all API functions.

Several API functions lack error handling. Follow the pattern used in getUserClientsApi for consistency.

Example implementation:

 export const createClientApi = async (data: CreateClientData): Promise<Client> => {
+  try {
     return await axiosInstance.post<CreateClientData, AxiosResponse<Client>>(`${USERS_MGT_URL}/clients`, data)
       .then((response) => response.data);
+  } catch (error) {
+    console.error("Error creating client:", error);
+    throw error;
+  }
 };

Apply similar error handling to updateUserPasswordApi, updateClientApi, generateTokenApi, and activationRequestApi.

Also applies to: 39-47, 50-56, 58-61, 73-76

Comment on lines +78 to +81
export const updateUserDetailsApi = async (data: any, userID: string): Promise<UserDetails> => {
return await axiosInstance.put(`${USERS_MGT_URL}/${userID}`, data)
.then((response) => response.data);
}
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 any type with a proper interface.

Using any type reduces type safety. Define a proper interface for the data parameter.

+interface UserUpdateData {
+  name?: string;
+  email?: string;
+  phone?: string;
+  // Add other relevant fields
+}

-export const updateUserDetailsApi = async (data: any, userID: string): Promise<UserDetails> => {
+export const updateUserDetailsApi = async (data: UserUpdateData, userID: string): Promise<UserDetails> => {
+  try {
     return await axiosInstance.put(`${USERS_MGT_URL}/${userID}`, data)
       .then((response) => response.data);
+  } catch (error) {
+    console.error("Error updating user details:", error);
+    throw error;
+  }
 }
📝 Committable suggestion

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

Suggested change
export const updateUserDetailsApi = async (data: any, userID: string): Promise<UserDetails> => {
return await axiosInstance.put(`${USERS_MGT_URL}/${userID}`, data)
.then((response) => response.data);
}
interface UserUpdateData {
name?: string;
email?: string;
phone?: string;
// Add other relevant fields
}
export const updateUserDetailsApi = async (data: UserUpdateData, userID: string): Promise<UserDetails> => {
try {
return await axiosInstance.put(`${USERS_MGT_URL}/${userID}`, data)
.then((response) => response.data);
} catch (error) {
console.error("Error updating user details:", error);
throw error;
}
}

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