Skip to content

Conversation

@omsherikar
Copy link
Collaborator

@omsherikar omsherikar commented Jan 25, 2026

Summary by CodeRabbit

Release Notes

  • New Features

    • Added API Keys management interface with create, revoke, and delete capabilities
    • Introduced Billing page displaying current plan and available features
    • Added Organizations section for managing team organizations
    • Introduced Organization Settings page for managing organization details and members
    • Redesigned dashboard with improved sidebar navigation, organization selector, and profile menu
    • Implemented slug-based URL routing for improved organization-specific navigation
  • Style

    • Minor styling refinement to repository selection interface

✏️ Tip: You can customize this high-level summary in your review settings.

Copilot AI review requested due to automatic review settings January 25, 2026 09:42
@vercel
Copy link

vercel bot commented Jan 25, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Review Updated (UTC)
refactron Ready Ready Preview, Comment Jan 25, 2026 9:42am

@github-actions
Copy link

⚠️ Deprecation Warning: The deny-licenses option is deprecated for possible removal in the next major release. For more information, see issue 997.

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Scanned Files

None

@github-actions github-actions bot added type:refactor Code refactoring type:feature New feature labels Jan 25, 2026
@coderabbitai
Copy link

coderabbitai bot commented Jan 25, 2026

📝 Walkthrough

Walkthrough

This PR introduces a comprehensive dashboard redesign with slug-based organization routing, new settings pages for Billing and API Keys management, and a refactored layout system with a collapsible sidebar. An API key service is added to support CRUD operations on keys, while authentication and routing are updated to support the new URL structure.

Changes

Cohort / File(s) Summary
Dashboard Layout & Navigation
src/components/DashboardLayout.tsx, src/components/Dashboard.tsx
Introduces comprehensive DashboardLayout with animated collapsible sidebar, organization/profile dropdowns, navigation menu, and GitHub OAuth integration. Dashboard refactored to simplify from sidebar-driven state to use DashboardLayout wrapper; repository selector modal retained.
Settings Pages
src/components/Billing.tsx, src/components/ApiKeys.tsx, src/components/Organizations.tsx, src/components/OrganizationSettings.tsx
Four new settings pages: Billing displays plan details and upgrade button; ApiKeys manages key CRUD with create/revoke/delete modals and copy-to-clipboard; Organizations shows organization info with member management UI; OrganizationSettings enables name editing via API with tab-based layout (General, Members, Danger Zone).
API Key Service & Management
src/services/apiKey.service.ts
New service module with four public functions (createApiKey, listApiKeys, revokeApiKey, deleteApiKey) using authenticated HTTP calls to manage API keys; includes interfaces for request/response payloads and centralized error handling.
Routing & Utilities
src/components/AuthApp.tsx, src/components/SlugRedirect.tsx, src/hooks/useAuth.tsx
AuthApp adds new slug-based routes for dashboard, billing, API keys, and organization settings with legacy path redirects via SlugRedirect. SlugRedirect utility component navigates to slug-based URLs derived from organization name. useAuth updated to centralize API base URL via getApiBaseUrl() helper.
Modal & UI Components
src/components/ConfirmationModal.tsx, src/components/RepositorySelector.tsx
New ConfirmationModal for destructive actions (delete/revoke) with danger/warning type variants and loading states. RepositorySelector receives minor background color style adjustment.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant ApiKeys as ApiKeys Component
    participant Service as apiKeyService
    participant Server as API Server
    
    User->>ApiKeys: Click "Create Key"
    ApiKeys->>ApiKeys: Open create modal
    User->>ApiKeys: Enter name, select environment
    User->>ApiKeys: Click "Create"
    ApiKeys->>Service: createApiKey(name, environment)
    Service->>Server: POST /api/auth/api-keys
    Server-->>Service: { success, apiKey }
    Service-->>ApiKeys: response
    ApiKeys->>ApiKeys: Show new key modal
    User->>ApiKeys: Copy key
    ApiKeys->>User: (clipboard feedback)
    User->>ApiKeys: Click "Done"
    ApiKeys->>Service: listApiKeys()
    Service->>Server: GET /api/auth/api-keys
    Server-->>Service: { success, apiKeys[] }
    Service-->>ApiKeys: response
    ApiKeys->>ApiKeys: Update key list display
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested labels

type:feature, type:refactor

Poem

🐰 A dashboard so sleek, with a sidebar that glides,
Settings and billing now tucked safely inside,
API keys managed with modals so clean,
Organizations unified—a dashboard supreme! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'Feature/auth persistence and UI refinements' is vague and overly broad, using generic terms like 'refinements' that don't convey specific information about the actual changes (new API keys component, billing page, organizations features, dashboard layout refactor). Consider a more specific title that highlights the primary change, such as 'Add API keys management and organization settings UI' or 'Refactor dashboard with new billing and API keys features'.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@omsherikar omsherikar changed the title Feature/auth persistence and UI refinements feature:auth persistence and UI refinements Jan 25, 2026
@omsherikar omsherikar merged commit bc95dc1 into main Jan 25, 2026
18 of 20 checks passed
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 9

🤖 Fix all issues with AI agents
In `@src/components/ApiKeys.tsx`:
- Around line 50-63: The loadApiKeys function currently calls
apiKeyService.listApiKeys without error handling; wrap the async API call in a
try-catch (similar to handleCreateKey) so any thrown errors (network,
unexpected) are caught, setError with a useful message (e.g., error.message or a
default), and ensure setLoading(false) runs in a finally block; reference
loadApiKeys, apiKeyService.listApiKeys, setLoading, setError so you place the
try/catch/finally around the call and update state in catch/finally.
- Around line 147-151: The copyToClipboard function currently calls
navigator.clipboard.writeText(key) without handling failures; wrap the writeText
call in a try-catch inside copyToClipboard, setCopiedKey(true) only on success,
and in the catch setCopiedKey(false) and surface/log an error or show a
user-friendly fallback (e.g., alert or fallback copy approach). Ensure you
reference the existing copyToClipboard function, navigator.clipboard.writeText,
and setCopiedKey so the clipboard failure is handled gracefully and the UI state
stays consistent.

In `@src/components/Billing.tsx`:
- Around line 81-85: The conditional rendering for the Upgrade button compares
user?.plan to the lowercase string 'enterprise' but user?.plan may have
different casing; update the check in the Billing component to normalize the
plan string (e.g., use user?.plan?.toLowerCase()) or reuse
getPlanDetails(user?.plan) to determine if the plan is enterprise before
rendering the button (so the comparison is case-insensitive and consistent with
getPlanDetails).

In `@src/components/OrganizationSettings.tsx`:
- Around line 244-276: The Danger Zone UI in OrganizationSettings is missing
state, validation, and a delete handler: add a local state (e.g., confirmName)
tied to the input, compute a boolean match (compare confirmName ===
formatOrgName(user?.organizationName)), and disable the Delete Organization
button unless match is true; implement an onClick handler (e.g.,
handleDeleteOrganization) that calls your delete organization API (await
deleteOrganization or similar), handles loading/error states and shows feedback,
and ensure you use existing symbols formatOrgName and user?.organizationName for
the comparison and confirmation text.
- Around line 47-71: The fetch block in OrganizationSettings.tsx calls
response.json() without checking response.ok, which can throw on non-2xx
responses or non-JSON bodies; update the request handling in the function
containing this code to first check response.ok (or response.status) and handle
error status paths before attempting to parse JSON: if response.ok, parse JSON
and proceed to call updateUser and setMessage with success, otherwise attempt to
safely parse error text/json (with a try/catch or response.text fallback) and
call setMessage({ type: 'error', text: parsedError || response.statusText ||
'Failed to update organization' }); ensure Authorization and API_BASE_URL usage
remain unchanged.
- Line 36: Remove the hardcoded localhost fallback for API_BASE_URL and ensure
missing configuration fails loudly: update the code using getApiBaseUrl() (and
the constant API_BASE_URL) to throw or log a clear error and abort
initialization when the env var is undefined rather than defaulting to
'http://localhost:3001'; alternatively, validate getApiBaseUrl() at startup and
surface a descriptive exception (including guidance to set
REACT_APP_API_BASE_URL) so production builds do not silently call localhost.

In `@src/components/RepositorySelector.tsx`:
- Line 146: The class string in RepositorySelector (the className containing
"bg-neutral-800 hover:bg-neutral-750 ...") uses a nonstandard Tailwind color
neutral-750; either add neutral-750 to your Tailwind config under
theme.extend.colors with the desired hex value, or change the class on
RepositorySelector to use an existing color (e.g., hover:bg-neutral-700 or
hover:bg-neutral-800) so Tailwind can generate the utility; update the className
in the RepositorySelector component accordingly.

In `@src/services/apiKey.service.ts`:
- Around line 38-56: In getAuthToken remove the debug console.log calls that
print token presence and absence (the three console.log statements inside
getAuthToken) to avoid leaking auth state; either delete them or replace them
with calls to a configured logger that respects environment (e.g., a debug/info
logger behind a feature flag) so production builds do not emit these messages,
ensuring you only reference the accessToken value and return it as currently
implemented.
- Around line 66-77: In createApiKey, remove the sensitive debug console.log
statements that print token presence and request headers (the lines referencing
token, getAuthToken(), and headers) so no auth token or headers are emitted to
logs; if you need runtime visibility use a secure/redacting logger or log only
non-sensitive high-level states (e.g., "createApiKey called") without including
token or header content.
🧹 Nitpick comments (19)
src/hooks/useAuth.tsx (1)

45-46: Remove placeholder comment.

The // ... comment appears to be a leftover placeholder or debug artifact that serves no purpose. Consider removing it.

🧹 Suggested cleanup
   // Initialize auth state
   useEffect(() => {
-    // ...
-
     const checkAuth = async () => {
src/components/ConfirmationModal.tsx (2)

29-50: Consider adding keyboard accessibility.

The modal lacks Escape key handling to close, and the backdrop/X button remain clickable during loading state which could cause race conditions if the user dismisses during an in-flight operation.

♻️ Suggested improvements
 const ConfirmationModal: React.FC<ConfirmationModalProps> = ({
   isOpen,
   onClose,
   onConfirm,
   title,
   message,
   confirmText = 'Confirm',
   cancelText = 'Cancel',
   isLoading = false,
   type = 'danger',
 }) => {
+  // Handle Escape key
+  React.useEffect(() => {
+    const handleKeyDown = (e: KeyboardEvent) => {
+      if (e.key === 'Escape' && isOpen && !isLoading) {
+        onClose();
+      }
+    };
+    document.addEventListener('keydown', handleKeyDown);
+    return () => document.removeEventListener('keydown', handleKeyDown);
+  }, [isOpen, isLoading, onClose]);
+
   return (
     <AnimatePresence>
       {isOpen && (
         <div className="fixed inset-0 z-50 flex items-center justify-center p-4">
           <motion.div
             initial={{ opacity: 0 }}
             animate={{ opacity: 1 }}
             exit={{ opacity: 0 }}
-            onClick={onClose}
+            onClick={isLoading ? undefined : onClose}
             className="absolute inset-0 bg-black/60 backdrop-blur-sm"
           />

45-50: X button should be disabled during loading.

For consistency with the action buttons, the close button should also be disabled while isLoading is true to prevent dismissal during an in-flight operation.

♻️ Suggested fix
             <button
               onClick={onClose}
-              className="absolute top-4 right-4 p-1 text-neutral-500 hover:text-neutral-300 transition-colors"
+              disabled={isLoading}
+              className="absolute top-4 right-4 p-1 text-neutral-500 hover:text-neutral-300 transition-colors disabled:opacity-50"
             >
               <X className="w-5 h-5" />
             </button>
src/components/Dashboard.tsx (1)

12-16: Stub implementation with TODO.

The handleRepositorySelect function only logs and closes the modal without actually creating a project. This is noted with a TODO comment.

Would you like me to help implement the project creation flow, or should I open an issue to track this work?

src/services/apiKey.service.ts (3)

47-51: Cookie value parsing may fail for URL-encoded tokens.

If the token contains special characters and was URL-encoded when set, split('=')[1] won't decode it. Consider using decodeURIComponent.

♻️ Suggested fix
   const tokenCookie = cookies.find(c => c.trim().startsWith('accessToken='));
   if (tokenCookie) {
-    return tokenCookie.split('=')[1];
+    return decodeURIComponent(tokenCookie.split('=')[1]);
   }

140-142: Return type CreateKeyResponse is misleading for revoke operation.

revokeApiKey returns Promise<CreateKeyResponse>, but this type name suggests key creation. Consider defining a dedicated RevokeKeyResponse type for clarity, or use a generic ApiKeyOperationResponse.


3-3: **Inconsistent fallback with other files using getApiBaseUrl().**This file has a hardcoded localhost fallback while useAuth.tsx uses getApiBaseUrl() directly without a fallback. For consistency and to avoid accidentally hitting localhost in production if the env var is misconfigured, consider aligning with the pattern used elsewhere.

♻️ Suggested alignment

Either remove the fallback to rely solely on getApiBaseUrl() (matching useAuth.tsx), or ensure the fallback is intentional and documented:

-const API_BASE_URL = getApiBaseUrl() || 'http://localhost:3001';
+const API_BASE_URL = getApiBaseUrl();

Alternatively, if the localhost fallback is intentional for development, add a comment explaining the rationale.

src/components/Billing.tsx (2)

43-53: Default case duplicates Free plan logic.

The default case is identical to the 'free' case. Consider falling through or extracting the default plan details to avoid duplication.

♻️ Suggested refactor
   const getPlanDetails = (plan: string | null | undefined) => {
+    const freePlan = {
+      name: 'Free (Community)',
+      features: [
+        'Local analysis & refactor suggestions',
+        'Safe-mode refactoring',
+        'Git diffs & reports',
+        'Open-source & personal use',
+      ],
+    };
+
     switch (plan?.toLowerCase()) {
       case 'free':
-        return {
-          name: 'Free (Community)',
-          features: [
-            'Local analysis & refactor suggestions',
-            'Safe-mode refactoring',
-            'Git diffs & reports',
-            'Open-source & personal use',
-          ],
-        };
+        return freePlan;
       case 'pro':
         // ... pro case unchanged
       case 'enterprise':
         // ... enterprise case unchanged
       default:
-        return {
-          name: 'Free (Community)',
-          features: [
-            'Local analysis & refactor suggestions',
-            'Safe-mode refactoring',
-            'Git diffs & reports',
-            'Open-source & personal use',
-          ],
-        };
+        return freePlan;
     }
   };

82-84: Upgrade button has no click handler.

The Upgrade Plan button is rendered but lacks an onClick handler. Consider adding a placeholder or connecting it to a future upgrade flow.

Would you like me to help implement the upgrade flow or create a placeholder handler that shows a "Coming Soon" toast?

src/components/Organizations.tsx (3)

11-23: Extract shared slug generation and formatting utilities.

The formatOrgName helper (lines 11-15) and slug generation logic (lines 18-22) are duplicated across multiple files:

  • src/components/DashboardLayout.tsx (lines 60-64, 76-80)
  • src/components/SlugRedirect.tsx (lines 17-21)
  • src/components/OrganizationSettings.tsx (lines 20-24, 30-34)

Consider extracting these into a shared utility (e.g., src/utils/organizationUtils.ts) to maintain consistency and reduce maintenance burden.

♻️ Proposed utility extraction

Create a new file src/utils/organizationUtils.ts:

export const formatOrgName = (name: string | null | undefined): string => {
  if (!name) return "User's Organization";
  const capitalized = name.charAt(0).toUpperCase() + name.slice(1);
  return `${capitalized}'s Organization`;
};

export const generateOrgSlug = (organizationName: string | null | undefined): string => {
  return (organizationName || 'user')
    .toLowerCase()
    .replace(/[^a-z0-9]+/g, '-')
    .replace(/^-+|-+$/g, '') + '-organization';
};

Then import and use in all components:

-  const formatOrgName = (name: string | null | undefined) => {
-    if (!name) return "User's Organization";
-    const capitalized = name.charAt(0).toUpperCase() + name.slice(1);
-    return `${capitalized}'s Organization`;
-  };
-
-  const orgName = formatOrgName(user?.organizationName);
-  const orgSlug =
-    (user?.organizationName || 'user')
-      .toLowerCase()
-      .replace(/[^a-z0-9]+/g, '-')
-      .replace(/^-+|-+$/g, '') + '-organization';
+  import { formatOrgName, generateOrgSlug } from '../utils/organizationUtils';
+  // ...
+  const orgName = formatOrgName(user?.organizationName);
+  const orgSlug = generateOrgSlug(user?.organizationName);

39-42: "New Organization" button lacks functionality.

The button is rendered but has no onClick handler. Consider either implementing the functionality, disabling the button with a tooltip indicating it's coming soon, or hiding it until the feature is ready.

💡 Option: Disable with coming soon indicator
-            <button className="px-6 py-3 bg-transparent border border-neutral-700 text-white rounded-lg font-medium hover:bg-neutral-800 transition-colors flex items-center gap-2">
+            <button
+              disabled
+              title="Coming soon"
+              className="px-6 py-3 bg-transparent border border-neutral-700 text-white rounded-lg font-medium hover:bg-neutral-800 transition-colors flex items-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed"
+            >
               <Plus className="w-5 h-5" />
               New Organization
             </button>

67-69: Users button lacks onClick handler.

Similar to the "New Organization" button, this icon button has no functionality attached.

src/components/SlugRedirect.tsx (1)

14-27: Potential infinite loading state when user is null.

If loading is false but user is null (e.g., unauthenticated user somehow reaches this component), the spinner will display indefinitely since no redirect occurs and nothing else is rendered.

While ProtectedRoute should prevent this scenario, adding a fallback improves robustness.

💡 Add fallback for edge case
   useEffect(() => {
     if (!loading && user) {
       // Generate slug from organization name or fallback to 'user'
       const orgSlug =
         (user.organizationName || 'user')
           .toLowerCase()
           .replace(/[^a-z0-9]+/g, '-')
           .replace(/^-+|-+$/g, '') + '-organization';

       navigate(`/${orgSlug}/${to}`, { replace: true });
+    } else if (!loading && !user) {
+      // Fallback: redirect to login if somehow reached without auth
+      navigate('/login', { replace: true });
     }
   }, [user, loading, navigate, to]);
src/components/AuthApp.tsx (2)

111-155: Inconsistent routing pattern for settings pages.

The routing strategy is inconsistent:

  • /settings/billing — non-slug-based (line 113)
  • /settings/organizations — non-slug-based (line 147)
  • /:slug/settings/api-keys — slug-based with redirect (lines 125, 135)
  • /:slug/settings/organizations/settings — slug-based with redirect (lines 159, 169)

This inconsistency may confuse users and developers. Consider aligning all settings routes to the same pattern (either all slug-based or all non-slug-based).


231-236: Use Link instead of anchor tag for internal navigation.

The 404 page uses a plain <a href="/login"> which causes a full page reload instead of client-side navigation.

💡 Use React Router's Link
-                    <a
-                      href="/login"
-                      className="text-white hover:text-neutral-300 font-medium transition-colors"
-                    >
+                    <Link
+                      to="/login"
+                      className="text-white hover:text-neutral-300 font-medium transition-colors"
+                    >
                       Go to Login
-                    </a>
+                    </Link>
src/components/DashboardLayout.tsx (3)

84-93: Provide user feedback on GitHub connection failure.

The error is only logged to console. Users won't know if the connection attempt failed.

💡 Add user-facing error state
+  const [connectError, setConnectError] = useState<string | null>(null);
+
   const handleGitHubConnect = async () => {
     try {
+      setConnectError(null);
       const { initiateOAuth } = await import('../utils/oauth');
       await initiateOAuth('github', 'connect', {
         redirectUri: `${window.location.origin}/auth/callback`,
       });
     } catch (error) {
       console.error('Failed to initiate GitHub connection:', error);
+      setConnectError('Failed to connect to GitHub. Please try again.');
     }
   };

Then render the error near the GitHub button.


66-74: Multiple menu items lack navigation paths.

Items like Sessions, Users, Issues, Research Room, Codebase Intelligence, and Experiments have no path property and render as non-functional buttons. Consider hiding them or adding a "coming soon" indicator to set proper user expectations.


351-379: Close profile dropdown after navigation.

The organization dropdown closes itself on navigation (line 171), but the profile dropdown buttons don't. This could cause a brief visual glitch.

💡 Close dropdown on navigation
                          <button
-                            onClick={() => navigate('/settings/organizations')}
+                            onClick={() => {
+                              navigate('/settings/organizations');
+                              setIsProfileDropdownOpen(false);
+                            }}
                             className="w-full flex items-center gap-3 p-2 hover:bg-neutral-800/50 rounded-lg group transition-colors text-neutral-400 hover:text-white"
                           >

Apply the same pattern to API Keys and Billing buttons.

src/components/OrganizationSettings.tsx (1)

186-209: Invite member functionality is not implemented.

The invite section has an email input, role selector, and "Invite" button, but no state tracking or onClick handler. Consider either implementing the feature or indicating it's coming soon.

Comment on lines +50 to +63
const loadApiKeys = async () => {
setLoading(true);
setError(null);

const response = await apiKeyService.listApiKeys();

if (response.success && response.apiKeys) {
setApiKeys(response.apiKeys);
} else {
setError(response.error || 'Failed to load API keys');
}

setLoading(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 | 🟡 Minor

Add try-catch for API call in loadApiKeys.

Unlike handleCreateKey, this function doesn't wrap the API call in a try-catch block. If the service throws an unexpected error (e.g., network failure), it won't be caught.

🐛 Proposed fix
   const loadApiKeys = async () => {
     setLoading(true);
     setError(null);

+    try {
       const response = await apiKeyService.listApiKeys();

       if (response.success && response.apiKeys) {
         setApiKeys(response.apiKeys);
       } else {
         setError(response.error || 'Failed to load API keys');
       }
+    } catch (err) {
+      setError('Failed to load API keys');
+    }

     setLoading(false);
   };
📝 Committable suggestion

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

Suggested change
const loadApiKeys = async () => {
setLoading(true);
setError(null);
const response = await apiKeyService.listApiKeys();
if (response.success && response.apiKeys) {
setApiKeys(response.apiKeys);
} else {
setError(response.error || 'Failed to load API keys');
}
setLoading(false);
};
const loadApiKeys = async () => {
setLoading(true);
setError(null);
try {
const response = await apiKeyService.listApiKeys();
if (response.success && response.apiKeys) {
setApiKeys(response.apiKeys);
} else {
setError(response.error || 'Failed to load API keys');
}
} catch (err) {
setError('Failed to load API keys');
}
setLoading(false);
};
🤖 Prompt for AI Agents
In `@src/components/ApiKeys.tsx` around lines 50 - 63, The loadApiKeys function
currently calls apiKeyService.listApiKeys without error handling; wrap the async
API call in a try-catch (similar to handleCreateKey) so any thrown errors
(network, unexpected) are caught, setError with a useful message (e.g.,
error.message or a default), and ensure setLoading(false) runs in a finally
block; reference loadApiKeys, apiKeyService.listApiKeys, setLoading, setError so
you place the try/catch/finally around the call and update state in
catch/finally.

Comment on lines +147 to +151
const copyToClipboard = (key: string) => {
navigator.clipboard.writeText(key);
setCopiedKey(true);
setTimeout(() => setCopiedKey(false), 2000);
};
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Handle clipboard API errors.

navigator.clipboard.writeText() can fail (e.g., if the page doesn't have focus or clipboard permissions are denied). Consider wrapping in try-catch.

🐛 Proposed fix
-  const copyToClipboard = (key: string) => {
-    navigator.clipboard.writeText(key);
-    setCopiedKey(true);
-    setTimeout(() => setCopiedKey(false), 2000);
+  const copyToClipboard = async (key: string) => {
+    try {
+      await navigator.clipboard.writeText(key);
+      setCopiedKey(true);
+      setTimeout(() => setCopiedKey(false), 2000);
+    } catch (err) {
+      setError('Failed to copy to clipboard');
+    }
   };
🤖 Prompt for AI Agents
In `@src/components/ApiKeys.tsx` around lines 147 - 151, The copyToClipboard
function currently calls navigator.clipboard.writeText(key) without handling
failures; wrap the writeText call in a try-catch inside copyToClipboard,
setCopiedKey(true) only on success, and in the catch setCopiedKey(false) and
surface/log an error or show a user-friendly fallback (e.g., alert or fallback
copy approach). Ensure you reference the existing copyToClipboard function,
navigator.clipboard.writeText, and setCopiedKey so the clipboard failure is
handled gracefully and the UI state stays consistent.

Comment on lines +81 to +85
{user?.plan !== 'enterprise' && (
<button className="px-6 py-2 bg-white text-black rounded-lg font-medium hover:bg-neutral-200 transition-colors">
Upgrade Plan
</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 | 🟡 Minor

Plan comparison case sensitivity inconsistency.

Line 81 compares user?.plan directly with 'enterprise' (lowercase), but user?.plan may contain different casing (e.g., 'Enterprise'). Meanwhile, getPlanDetails normalizes with toLowerCase(). This inconsistency could show the Upgrade button to Enterprise users.

🐛 Suggested fix
-              {user?.plan !== 'enterprise' && (
+              {user?.plan?.toLowerCase() !== 'enterprise' && (
                 <button className="px-6 py-2 bg-white text-black rounded-lg font-medium hover:bg-neutral-200 transition-colors">
                   Upgrade Plan
                 </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
{user?.plan !== 'enterprise' && (
<button className="px-6 py-2 bg-white text-black rounded-lg font-medium hover:bg-neutral-200 transition-colors">
Upgrade Plan
</button>
)}
{user?.plan?.toLowerCase() !== 'enterprise' && (
<button className="px-6 py-2 bg-white text-black rounded-lg font-medium hover:bg-neutral-200 transition-colors">
Upgrade Plan
</button>
)}
🤖 Prompt for AI Agents
In `@src/components/Billing.tsx` around lines 81 - 85, The conditional rendering
for the Upgrade button compares user?.plan to the lowercase string 'enterprise'
but user?.plan may have different casing; update the check in the Billing
component to normalize the plan string (e.g., use user?.plan?.toLowerCase()) or
reuse getPlanDetails(user?.plan) to determine if the plan is enterprise before
rendering the button (so the comparison is case-insensitive and consistent with
getPlanDetails).

.replace(/[^a-z0-9]+/g, '-')
.replace(/^-+|-+$/g, '') + '-organization';

const API_BASE_URL = getApiBaseUrl() || 'http://localhost:3001';
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Avoid hardcoded localhost fallback in production code.

The fallback || 'http://localhost:3001' could cause production requests to fail silently or route to unintended destinations if REACT_APP_API_BASE_URL is not set.

🐛 Proposed fix
-  const API_BASE_URL = getApiBaseUrl() || 'http://localhost:3001';
+  const API_BASE_URL = getApiBaseUrl();
+  
+  // Early return or error if API base URL is not configured
+  if (!API_BASE_URL) {
+    return (
+      <DashboardLayout>
+        <div className="p-8 text-red-400">
+          API configuration error. Please contact support.
+        </div>
+      </DashboardLayout>
+    );
+  }

Alternatively, ensure the env var is always set and let it fail explicitly rather than silently routing to localhost.

🤖 Prompt for AI Agents
In `@src/components/OrganizationSettings.tsx` at line 36, Remove the hardcoded
localhost fallback for API_BASE_URL and ensure missing configuration fails
loudly: update the code using getApiBaseUrl() (and the constant API_BASE_URL) to
throw or log a clear error and abort initialization when the env var is
undefined rather than defaulting to 'http://localhost:3001'; alternatively,
validate getApiBaseUrl() at startup and surface a descriptive exception
(including guidance to set REACT_APP_API_BASE_URL) so production builds do not
silently call localhost.

Comment on lines +47 to +71
try {
const token = localStorage.getItem('accessToken');
const response = await fetch(`${API_BASE_URL}/api/auth/organization`, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({ organizationName: orgName }),
});

const data = await response.json();

if (data.success) {
updateUser(data.user);
setMessage({
type: 'success',
text: 'Organization updated successfully',
});
} else {
setMessage({
type: 'error',
text: data.message || 'Failed to update organization',
});
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Handle non-2xx responses before parsing JSON.

response.json() is called without checking response.ok. If the server returns an error status with a non-JSON body, this will throw an unhandled exception.

🐛 Proposed fix
       const response = await fetch(`${API_BASE_URL}/api/auth/organization`, {
         method: 'PATCH',
         headers: {
           'Content-Type': 'application/json',
           Authorization: `Bearer ${token}`,
         },
         body: JSON.stringify({ organizationName: orgName }),
       });

+      if (!response.ok) {
+        const errorText = await response.text();
+        throw new Error(errorText || `HTTP ${response.status}`);
+      }
+
       const data = await response.json();
📝 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
try {
const token = localStorage.getItem('accessToken');
const response = await fetch(`${API_BASE_URL}/api/auth/organization`, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({ organizationName: orgName }),
});
const data = await response.json();
if (data.success) {
updateUser(data.user);
setMessage({
type: 'success',
text: 'Organization updated successfully',
});
} else {
setMessage({
type: 'error',
text: data.message || 'Failed to update organization',
});
}
try {
const token = localStorage.getItem('accessToken');
const response = await fetch(`${API_BASE_URL}/api/auth/organization`, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({ organizationName: orgName }),
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(errorText || `HTTP ${response.status}`);
}
const data = await response.json();
if (data.success) {
updateUser(data.user);
setMessage({
type: 'success',
text: 'Organization updated successfully',
});
} else {
setMessage({
type: 'error',
text: data.message || 'Failed to update organization',
});
}
🤖 Prompt for AI Agents
In `@src/components/OrganizationSettings.tsx` around lines 47 - 71, The fetch
block in OrganizationSettings.tsx calls response.json() without checking
response.ok, which can throw on non-2xx responses or non-JSON bodies; update the
request handling in the function containing this code to first check response.ok
(or response.status) and handle error status paths before attempting to parse
JSON: if response.ok, parse JSON and proceed to call updateUser and setMessage
with success, otherwise attempt to safely parse error text/json (with a
try/catch or response.text fallback) and call setMessage({ type: 'error', text:
parsedError || response.statusText || 'Failed to update organization' }); ensure
Authorization and API_BASE_URL usage remain unchanged.

Comment on lines +244 to +276
{activeTab === 'danger' && (
<div className="bg-[#0a0a0a] border border-red-900/30 rounded-xl p-8">
<h3 className="text-red-400 font-semibold text-lg mb-2">
Delete Organization
</h3>
<p className="text-neutral-300 mb-2">
Permanently delete this organization and all associated data.
This action cannot be undone.
</p>
<p className="text-neutral-500 text-sm mb-6">
This will delete: all agents, sessions, events, API keys, and
member associations.
</p>

<div className="mb-6">
<label className="block text-white text-sm font-medium mb-3">
Type{' '}
<span className="font-mono bg-neutral-900 px-1.5 py-0.5 rounded text-neutral-300">
{formatOrgName(user?.organizationName)}
</span>{' '}
to confirm
</label>
<input
type="text"
placeholder={formatOrgName(user?.organizationName)}
className="w-full bg-transparent border border-neutral-800 rounded-lg px-4 py-3 text-white placeholder-neutral-600 focus:outline-none focus:border-red-900/50 transition-colors"
/>
</div>

<button className="px-6 py-3 bg-red-500/10 border border-red-500/20 text-red-400 rounded-lg font-medium hover:bg-red-500/20 transition-colors w-full sm:w-auto">
Delete Organization
</button>
</div>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Delete organization functionality is incomplete.

The Danger Zone tab renders a confirmation input and delete button, but:

  1. The confirmation input value is not tracked in state
  2. The delete button has no onClick handler
  3. No validation that the typed name matches before enabling delete

This could lead to user confusion when clicking the button does nothing.

Do you want me to generate the implementation for the delete organization functionality, including state management, validation, and the API call?

🤖 Prompt for AI Agents
In `@src/components/OrganizationSettings.tsx` around lines 244 - 276, The Danger
Zone UI in OrganizationSettings is missing state, validation, and a delete
handler: add a local state (e.g., confirmName) tied to the input, compute a
boolean match (compare confirmName === formatOrgName(user?.organizationName)),
and disable the Delete Organization button unless match is true; implement an
onClick handler (e.g., handleDeleteOrganization) that calls your delete
organization API (await deleteOrganization or similar), handles loading/error
states and shows feedback, and ensure you use existing symbols formatOrgName and
user?.organizationName for the comparison and confirmation text.

key={repo.id}
onClick={() => onSelect(repo)}
className="w-full p-4 bg-neutral-800/50 hover:bg-neutral-800 border border-neutral-700 hover:border-neutral-600 rounded-xl transition-all text-left group"
className="w-full p-4 bg-neutral-800 hover:bg-neutral-750 border border-neutral-700 hover:border-neutral-600 rounded-xl transition-all text-left group"
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -name "tailwind.config.*" -type f | head -5

Repository: Refactron-ai/Refactron_Website

Length of output: 94


🏁 Script executed:

cat ./tailwind.config.js

Repository: Refactron-ai/Refactron_Website

Length of output: 3228


🏁 Script executed:

sed -n '146p' ./src/components/RepositorySelector.tsx

Repository: Refactron-ai/Refactron_Website

Length of output: 243


🏁 Script executed:

rg "neutral-" ./src --count

Repository: Refactron-ai/Refactron_Website

Length of output: 1466


🏁 Script executed:

web_search

Repository: Refactron-ai/Refactron_Website

Length of output: 124


🌐 Web query:

Tailwind CSS neutral color palette standard values

💡 Result:

Tailwind CSS default neutral palette (shades → hex): (v3.tailwindcss.com)

  • 50: #fafafa
  • 100: #f5f5f5
  • 200: #e5e5e5
  • 300: #d4d4d4
  • 400: #a3a3a3
  • 500: #737373
  • 600: #525252
  • 700: #404040
  • 800: #262626
  • 900: #171717
  • 950: #0a0a0a

Citations:


Add neutral-750 to Tailwind config or replace with a standard color.

neutral-750 is not a standard Tailwind color. Update tailwind.config.js to add it under theme.extend.colors, or replace it with neutral-700 or neutral-800.

Relevant code
className="w-full p-4 bg-neutral-800 hover:bg-neutral-750 border border-neutral-700 hover:border-neutral-600 rounded-xl transition-all text-left group"
🤖 Prompt for AI Agents
In `@src/components/RepositorySelector.tsx` at line 146, The class string in
RepositorySelector (the className containing "bg-neutral-800
hover:bg-neutral-750 ...") uses a nonstandard Tailwind color neutral-750; either
add neutral-750 to your Tailwind config under theme.extend.colors with the
desired hex value, or change the class on RepositorySelector to use an existing
color (e.g., hover:bg-neutral-700 or hover:bg-neutral-800) so Tailwind can
generate the utility; update the className in the RepositorySelector component
accordingly.

Comment on lines +38 to +56
const getAuthToken = (): string | null => {
// Try localStorage first (primary storage used by useAuth)
const localToken = localStorage.getItem('accessToken');
if (localToken) {
console.log('Found token in localStorage');
return localToken;
}

// Fallback to cookie
const cookies = document.cookie.split(';');
const tokenCookie = cookies.find(c => c.trim().startsWith('accessToken='));
if (tokenCookie) {
console.log('Found token in cookies');
return tokenCookie.split('=')[1];
}

console.log('No token found in localStorage or cookies');
return null;
};
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Remove debug console.log statements before production.

These logging statements expose authentication state and could leak sensitive information in production. Remove or replace with a proper logging library that can be disabled in production.

🔒 Suggested fix
 const getAuthToken = (): string | null => {
   // Try localStorage first (primary storage used by useAuth)
   const localToken = localStorage.getItem('accessToken');
   if (localToken) {
-    console.log('Found token in localStorage');
     return localToken;
   }
 
   // Fallback to cookie
   const cookies = document.cookie.split(';');
   const tokenCookie = cookies.find(c => c.trim().startsWith('accessToken='));
   if (tokenCookie) {
-    console.log('Found token in cookies');
     return tokenCookie.split('=')[1];
   }
 
-  console.log('No token found in localStorage or cookies');
   return 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 getAuthToken = (): string | null => {
// Try localStorage first (primary storage used by useAuth)
const localToken = localStorage.getItem('accessToken');
if (localToken) {
console.log('Found token in localStorage');
return localToken;
}
// Fallback to cookie
const cookies = document.cookie.split(';');
const tokenCookie = cookies.find(c => c.trim().startsWith('accessToken='));
if (tokenCookie) {
console.log('Found token in cookies');
return tokenCookie.split('=')[1];
}
console.log('No token found in localStorage or cookies');
return null;
};
const getAuthToken = (): string | null => {
// Try localStorage first (primary storage used by useAuth)
const localToken = localStorage.getItem('accessToken');
if (localToken) {
return localToken;
}
// Fallback to cookie
const cookies = document.cookie.split(';');
const tokenCookie = cookies.find(c => c.trim().startsWith('accessToken='));
if (tokenCookie) {
return tokenCookie.split('=')[1];
}
return null;
};
🤖 Prompt for AI Agents
In `@src/services/apiKey.service.ts` around lines 38 - 56, In getAuthToken remove
the debug console.log calls that print token presence and absence (the three
console.log statements inside getAuthToken) to avoid leaking auth state; either
delete them or replace them with calls to a configured logger that respects
environment (e.g., a debug/info logger behind a feature flag) so production
builds do not emit these messages, ensuring you only reference the accessToken
value and return it as currently implemented.

Comment on lines +66 to +77
const token = getAuthToken();
console.log('Token for createApiKey:', token ? 'Present' : 'Missing');

const headers: HeadersInit = {
'Content-Type': 'application/json',
};

if (token) {
headers['Authorization'] = `Bearer ${token}`;
}

console.log('Request headers:', headers);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Remove sensitive debug logging in createApiKey.

Logging token presence and request headers can expose sensitive information. These should be removed for production code.

🔒 Suggested fix
   try {
     const token = getAuthToken();
-    console.log('Token for createApiKey:', token ? 'Present' : 'Missing');
 
     const headers: HeadersInit = {
       'Content-Type': 'application/json',
     };
 
     if (token) {
       headers['Authorization'] = `Bearer ${token}`;
     }
 
-    console.log('Request headers:', headers);
-
     const response = await fetch(`${API_BASE_URL}/api/keys`, {
📝 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 token = getAuthToken();
console.log('Token for createApiKey:', token ? 'Present' : 'Missing');
const headers: HeadersInit = {
'Content-Type': 'application/json',
};
if (token) {
headers['Authorization'] = `Bearer ${token}`;
}
console.log('Request headers:', headers);
const token = getAuthToken();
const headers: HeadersInit = {
'Content-Type': 'application/json',
};
if (token) {
headers['Authorization'] = `Bearer ${token}`;
}
🤖 Prompt for AI Agents
In `@src/services/apiKey.service.ts` around lines 66 - 77, In createApiKey, remove
the sensitive debug console.log statements that print token presence and request
headers (the lines referencing token, getAuthToken(), and headers) so no auth
token or headers are emitted to logs; if you need runtime visibility use a
secure/redacting logger or log only non-sensitive high-level states (e.g.,
"createApiKey called") without including token or header content.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This pull request implements authentication persistence improvements and adds comprehensive UI components for organization and API key management. The changes introduce slug-based routing for multi-tenant organization support and refactor the dashboard layout into a reusable component.

Changes:

  • Added new service layer for API key management with authentication token handling
  • Introduced slug-based routing system with redirect support for backward compatibility
  • Created organization and billing management UI components
  • Refactored Dashboard to use a shared DashboardLayout component, extracting ~250 lines of sidebar code

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 11 comments.

Show a summary per file
File Description
src/services/apiKey.service.ts New service for managing API keys with CRUD operations and authentication
src/hooks/useAuth.tsx Centralized API base URL utility usage for consistency
src/components/SlugRedirect.tsx Handles redirects from legacy routes to slug-based organization routes
src/components/Organizations.tsx Organization listing and management UI
src/components/OrganizationSettings.tsx Comprehensive organization settings with tabs for general, members, and danger zone
src/components/DashboardLayout.tsx Extracted shared layout component with sidebar, navigation, and dropdowns
src/components/Dashboard.tsx Simplified dashboard using DashboardLayout, removed ~250 lines of duplicated code
src/components/ApiKeys.tsx Full-featured API key management with create, revoke, and delete operations
src/components/Billing.tsx Billing and subscription plan display component
src/components/ConfirmationModal.tsx Reusable confirmation modal for dangerous actions
src/components/RepositorySelector.tsx Minor styling adjustment for hover states
src/components/AuthApp.tsx Added routes for new features with slug-based and legacy path support

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +47 to +51
const cookies = document.cookie.split(';');
const tokenCookie = cookies.find(c => c.trim().startsWith('accessToken='));
if (tokenCookie) {
console.log('Found token in cookies');
return tokenCookie.split('=')[1];
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

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

The cookie parsing logic is fragile and may fail if the cookie value contains special characters or equals signs. Additionally, there's no validation of the token format before returning it. Consider using a more robust cookie parsing approach or validating that the token has the expected format before using it.

Suggested change
const cookies = document.cookie.split(';');
const tokenCookie = cookies.find(c => c.trim().startsWith('accessToken='));
if (tokenCookie) {
console.log('Found token in cookies');
return tokenCookie.split('=')[1];
const cookieString = document.cookie;
if (!cookieString) {
console.log('No token found in localStorage or cookies');
return null;
}
const cookies = cookieString.split(';');
const name = 'accessToken=';
const tokenCookie = cookies.find(c => c.trim().startsWith(name));
if (tokenCookie) {
console.log('Found token in cookies');
const trimmed = tokenCookie.trim();
const rawValue = trimmed.substring(name.length);
let tokenFromCookie = rawValue;
try {
tokenFromCookie = decodeURIComponent(rawValue);
} catch {
// Ignore decoding errors and fall back to the raw value
}
// Basic validation: ensure non-empty and free of whitespace
const isValidToken =
typeof tokenFromCookie === 'string' &&
tokenFromCookie.length > 0 &&
!/\s/.test(tokenFromCookie);
if (isValidToken) {
return tokenFromCookie;
}

Copilot uses AI. Check for mistakes.
Comment on lines +65 to +84
try {
const token = getAuthToken();
console.log('Token for createApiKey:', token ? 'Present' : 'Missing');

const headers: HeadersInit = {
'Content-Type': 'application/json',
};

if (token) {
headers['Authorization'] = `Bearer ${token}`;
}

console.log('Request headers:', headers);

const response = await fetch(`${API_BASE_URL}/api/keys`, {
method: 'POST',
headers,
credentials: 'include',
body: JSON.stringify({ name, environment }),
});
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

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

The API call proceeds even when no authentication token is found. While the headers are conditionally set, the function should consider whether making an unauthenticated request is appropriate. Consider either requiring authentication and returning an error early if no token is found, or documenting why unauthenticated requests are acceptable for this endpoint.

Copilot uses AI. Check for mistakes.
key={repo.id}
onClick={() => onSelect(repo)}
className="w-full p-4 bg-neutral-800/50 hover:bg-neutral-800 border border-neutral-700 hover:border-neutral-600 rounded-xl transition-all text-left group"
className="w-full p-4 bg-neutral-800 hover:bg-neutral-750 border border-neutral-700 hover:border-neutral-600 rounded-xl transition-all text-left group"
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

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

The class 'bg-neutral-750' is not a standard Tailwind color. Tailwind's neutral color scale only includes values like 50, 100, 200, ..., 900, 950. This will not generate any styles unless explicitly defined in the Tailwind configuration. Consider using 'bg-neutral-700' or 'bg-neutral-800' instead, or define neutral-750 in tailwind.config.js if this specific shade is required.

Suggested change
className="w-full p-4 bg-neutral-800 hover:bg-neutral-750 border border-neutral-700 hover:border-neutral-600 rounded-xl transition-all text-left group"
className="w-full p-4 bg-neutral-800 hover:bg-neutral-700 border border-neutral-700 hover:border-neutral-600 rounded-xl transition-all text-left group"

Copilot uses AI. Check for mistakes.
Comment on lines +17 to +21
const orgSlug =
(user.organizationName || 'user')
.toLowerCase()
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-+|-+$/g, '') + '-organization';
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

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

The slug generation logic is duplicated across multiple components (SlugRedirect, DashboardLayout, Organizations, OrganizationSettings). This code should be extracted into a shared utility function in urlUtils.ts to ensure consistency and make it easier to maintain. If the slug generation logic needs to change in the future, it should only need to be updated in one place.

Copilot uses AI. Check for mistakes.
Comment on lines +11 to +17
const formatOrgName = (name: string | null | undefined) => {
if (!name) return "User's Organization";
const capitalized = name.charAt(0).toUpperCase() + name.slice(1);
return `${capitalized}'s Organization`;
};

const orgName = formatOrgName(user?.organizationName);
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

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

The formatOrgName helper function is duplicated across multiple components (Organizations, OrganizationSettings, and DashboardLayout). Consider extracting this into a shared utility function to maintain consistency and reduce code duplication.

Suggested change
const formatOrgName = (name: string | null | undefined) => {
if (!name) return "User's Organization";
const capitalized = name.charAt(0).toUpperCase() + name.slice(1);
return `${capitalized}'s Organization`;
};
const orgName = formatOrgName(user?.organizationName);
const orgName = !user?.organizationName
? "User's Organization"
: `${user.organizationName.charAt(0).toUpperCase()}${user.organizationName.slice(1)}'s Organization`;

Copilot uses AI. Check for mistakes.
text: data.message || 'Failed to update organization',
});
}
} catch (error) {
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

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

The error handling in the catch block loses the specific error message. While a generic message is user-friendly, consider at least logging the actual error to the console for debugging purposes. This would help diagnose issues in production.

Suggested change
} catch (error) {
} catch (error) {
console.error('Error updating organization settings:', error);

Copilot uses AI. Check for mistakes.

// Initialize auth state
useEffect(() => {
// ...
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

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

The comment "// ..." on line 45 is unclear and provides no useful information. Consider removing it or replacing it with a meaningful comment that explains what the useEffect is doing (e.g., "// Check authentication status on mount").

Suggested change
// ...
// Check authentication status and refresh token if needed on mount

Copilot uses AI. Check for mistakes.
Comment on lines +147 to +150
const copyToClipboard = (key: string) => {
navigator.clipboard.writeText(key);
setCopiedKey(true);
setTimeout(() => setCopiedKey(false), 2000);
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

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

The copyToClipboard function doesn't handle potential errors from navigator.clipboard.writeText(). This API can fail if the user denies clipboard permissions or if the browser doesn't support it. Consider adding error handling and providing user feedback if the copy operation fails.

Suggested change
const copyToClipboard = (key: string) => {
navigator.clipboard.writeText(key);
setCopiedKey(true);
setTimeout(() => setCopiedKey(false), 2000);
const copyToClipboard = async (key: string) => {
if (!navigator.clipboard || !navigator.clipboard.writeText) {
setError('Clipboard is not supported in this browser.');
return;
}
try {
await navigator.clipboard.writeText(key);
setCopiedKey(true);
setTimeout(() => setCopiedKey(false), 2000);
} catch (err) {
// Optional: log the error for debugging purposes
console.error('Failed to copy API key to clipboard:', err);
setError('Failed to copy API key to clipboard. Please try again.');
}

Copilot uses AI. Check for mistakes.
Comment on lines +42 to +77
console.log('Found token in localStorage');
return localToken;
}

// Fallback to cookie
const cookies = document.cookie.split(';');
const tokenCookie = cookies.find(c => c.trim().startsWith('accessToken='));
if (tokenCookie) {
console.log('Found token in cookies');
return tokenCookie.split('=')[1];
}

console.log('No token found in localStorage or cookies');
return null;
};

/**
* Create a new API key
*/
export const createApiKey = async (
name: string,
environment: 'test' | 'live'
): Promise<CreateKeyResponse> => {
try {
const token = getAuthToken();
console.log('Token for createApiKey:', token ? 'Present' : 'Missing');

const headers: HeadersInit = {
'Content-Type': 'application/json',
};

if (token) {
headers['Authorization'] = `Bearer ${token}`;
}

console.log('Request headers:', headers);
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

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

Debug console.log statements should be removed from production code. These logging statements expose implementation details and could clutter the console in production. Consider using a proper logging library or removing these debug logs before deployment.

Copilot uses AI. Check for mistakes.
@@ -0,0 +1,209 @@
import { getApiBaseUrl } from '../utils/urlUtils';

const API_BASE_URL = getApiBaseUrl() || 'http://localhost:3001';
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

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

When getApiBaseUrl returns an empty string, the fallback to 'http://localhost:3001' may not be the appropriate default in production. This pattern is inconsistent with how useAuth.tsx handles it, where getApiBaseUrl is used directly without a fallback. Consider whether this hardcoded fallback is necessary or if it should follow the same pattern as other parts of the codebase.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type:feature New feature type:refactor Code refactoring

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants