-
-
Notifications
You must be signed in to change notification settings - Fork 0
feature:auth persistence and UI refinements #93
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
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Dependency Review✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.Scanned FilesNone |
📝 WalkthroughWalkthroughThis 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
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
Suggested labels
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 inconclusive)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
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. Comment |
There was a problem hiding this 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
isLoadingis 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
handleRepositorySelectfunction 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 usingdecodeURIComponent.♻️ 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 typeCreateKeyResponseis misleading for revoke operation.
revokeApiKeyreturnsPromise<CreateKeyResponse>, but this type name suggests key creation. Consider defining a dedicatedRevokeKeyResponsetype for clarity, or use a genericApiKeyOperationResponse.
3-3: **Inconsistent fallback with other files usinggetApiBaseUrl().**This file has a hardcoded localhost fallback whileuseAuth.tsxusesgetApiBaseUrl()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()(matchinguseAuth.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
onClickhandler. 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
formatOrgNamehelper (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
onClickhandler. 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
loadingisfalsebutuserisnull(e.g., unauthenticated user somehow reaches this component), the spinner will display indefinitely since no redirect occurs and nothing else is rendered.While
ProtectedRouteshould 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: UseLinkinstead 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
pathproperty 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.
| 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); | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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.
| const copyToClipboard = (key: string) => { | ||
| navigator.clipboard.writeText(key); | ||
| setCopiedKey(true); | ||
| setTimeout(() => setCopiedKey(false), 2000); | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| {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> | ||
| )} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| {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'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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', | ||
| }); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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.
| {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> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Delete organization functionality is incomplete.
The Danger Zone tab renders a confirmation input and delete button, but:
- The confirmation input value is not tracked in state
- The delete button has no
onClickhandler - 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" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
find . -name "tailwind.config.*" -type f | head -5Repository: Refactron-ai/Refactron_Website
Length of output: 94
🏁 Script executed:
cat ./tailwind.config.jsRepository: Refactron-ai/Refactron_Website
Length of output: 3228
🏁 Script executed:
sed -n '146p' ./src/components/RepositorySelector.tsxRepository: Refactron-ai/Refactron_Website
Length of output: 243
🏁 Script executed:
rg "neutral-" ./src --countRepository: Refactron-ai/Refactron_Website
Length of output: 1466
🏁 Script executed:
web_searchRepository: 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.
| 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; | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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.
| 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); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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.
There was a problem hiding this 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.
| 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]; |
Copilot
AI
Jan 25, 2026
There was a problem hiding this comment.
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.
| 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; | |
| } |
| 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 }), | ||
| }); |
Copilot
AI
Jan 25, 2026
There was a problem hiding this comment.
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.
| 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" |
Copilot
AI
Jan 25, 2026
There was a problem hiding this comment.
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.
| 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" |
| const orgSlug = | ||
| (user.organizationName || 'user') | ||
| .toLowerCase() | ||
| .replace(/[^a-z0-9]+/g, '-') | ||
| .replace(/^-+|-+$/g, '') + '-organization'; |
Copilot
AI
Jan 25, 2026
There was a problem hiding this comment.
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.
| 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); |
Copilot
AI
Jan 25, 2026
There was a problem hiding this comment.
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.
| 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`; |
| text: data.message || 'Failed to update organization', | ||
| }); | ||
| } | ||
| } catch (error) { |
Copilot
AI
Jan 25, 2026
There was a problem hiding this comment.
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.
| } catch (error) { | |
| } catch (error) { | |
| console.error('Error updating organization settings:', error); |
|
|
||
| // Initialize auth state | ||
| useEffect(() => { | ||
| // ... |
Copilot
AI
Jan 25, 2026
There was a problem hiding this comment.
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").
| // ... | |
| // Check authentication status and refresh token if needed on mount |
| const copyToClipboard = (key: string) => { | ||
| navigator.clipboard.writeText(key); | ||
| setCopiedKey(true); | ||
| setTimeout(() => setCopiedKey(false), 2000); |
Copilot
AI
Jan 25, 2026
There was a problem hiding this comment.
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.
| 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.'); | |
| } |
| 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); |
Copilot
AI
Jan 25, 2026
There was a problem hiding this comment.
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.
| @@ -0,0 +1,209 @@ | |||
| import { getApiBaseUrl } from '../utils/urlUtils'; | |||
|
|
|||
| const API_BASE_URL = getApiBaseUrl() || 'http://localhost:3001'; | |||
Copilot
AI
Jan 25, 2026
There was a problem hiding this comment.
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.
Summary by CodeRabbit
Release Notes
New Features
Style
✏️ Tip: You can customize this high-level summary in your review settings.