Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. WalkthroughAdds a new DESIGN_GUIDE and extensive UI refactors across the dashboard: collapsible animated sidebar and nav refactor, chart and widget abstractions (time-range, donut/line), simplified globe, apps store/app tile redesign, new helper components (copyable text, warning modal), and major settings/form primitives overhaul. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant Header as HeaderToggle
participant Layout as SidebarLayout
participant Sidebar as SidebarContent
participant Main as MainContent
Header->>Layout: click collapse toggle
Layout->>Layout: set isCollapsed state
Layout->>Sidebar: pass isCollapsed, toggleCollapsed
Sidebar-->>Layout: render collapsed/expanded UI
Layout->>Main: reflow main content (adjust margin/width)
Note over Sidebar,Main: animated width transition, tooltips/chevrons update
sequenceDiagram
autonumber
participant Page as MetricsPage
participant Storage as localStorage
participant Conductor as MetricsContent
participant Widget as WidgetComponent
participant Time as TimeRangeToggle
Page->>Storage: loadConfig()
Storage-->>Page: dashboardConfig
Page->>Conductor: render with dashboardConfig, timeRange
Conductor->>Widget: render enabled widgets in widgetOrder
Time->>Conductor: user toggles timeRange
Conductor->>Widget: re-render with new timeRange
Page->>Storage: saveConfig(updatedConfig)
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120+ minutes Focus review on:
Possibly related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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 |
|
Claude finished @Developing-Gamer's task —— View job PR Review: Sidebar RedesignReview SummaryThis is a comprehensive sidebar redesign PR that includes both a new design guide and significant improvements to the sidebar layout component. The changes are well-structured and demonstrate attention to both functionality and design consistency. Review Checklist:
🎯 Overall AssessmentStrengths:
📋 Detailed Code Review
|
Greptile OverviewGreptile SummaryThis PR implements a comprehensive sidebar redesign that significantly improves the visual hierarchy and user experience of the dashboard navigation. Key Changes:
Design Improvements:
Confidence Score: 5/5
Important Files ChangedFile Analysis
Sequence DiagramsequenceDiagram
participant User
participant SidebarLayout
participant NavItem
participant NavSubItem
participant Router
User->>SidebarLayout: Navigate to page
SidebarLayout->>SidebarContent: Render sidebar
SidebarContent->>SidebarContent: Get enabled apps from config
SidebarContent->>SidebarContent: getDefaultExpandedSections()
SidebarContent->>NavItem: Render overview item
loop For each enabled app
SidebarContent->>NavItem: Render app section
NavItem->>NavItem: Check if section is active
NavItem->>NavItem: Apply highlight styles if active
alt Section is expanded
NavItem->>NavSubItem: Render navigation items
NavSubItem->>NavSubItem: Check if item is active
NavSubItem->>NavSubItem: Apply highlight styles if active
end
end
SidebarContent->>NavItem: Render bottom items
User->>NavItem: Click navigation item
NavItem->>Router: Navigate to href
Router->>SidebarLayout: Update pathname
SidebarLayout->>NavItem: Re-render with new active state
NavItem->>NavItem: Update highlight based on new pathname
User->>NavItem: Click section toggle
NavItem->>SidebarContent: toggleSection(appId)
SidebarContent->>SidebarContent: Update expandedSections state
SidebarContent->>NavItem: Re-render with new expanded state
NavItem->>NavItem: Animate expansion/collapse
|
There was a problem hiding this comment.
Pull request overview
This is a comprehensive sidebar redesign PR that modernizes the dashboard UI with improved navigation, performance optimizations, and enhanced user experience features.
Key Changes
- Complete sidebar redesign with collapsible functionality and improved visual hierarchy
- New app management UI with filtering, search, and warning modals for alpha/beta apps
- Performance optimizations using React.memo, useCallback, and useMemo throughout components
- Enhanced metrics dashboard with configurable widgets, time range filtering, and improved chart visualizations
- Comprehensive design guide documentation for maintaining consistency across the dashboard
Reviewed changes
Copilot reviewed 17 out of 17 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
apps-frontend.tsx |
Updated logo styling and added AppId type export |
settings.tsx |
Added React.memo optimizations and new SettingCopyableText component |
link.tsx |
Enhanced onClick handler to receive event parameter |
copyable-text.tsx |
New reusable component for copyable text with visual feedback |
app-warning-modal.tsx |
New modal component for alpha/beta app warnings |
app-store-entry.tsx |
Redesigned app store entry with enhanced visual hierarchy |
app-square.tsx |
Major refactoring with new hover interactions and enable/disable functionality |
vercel/page-client.tsx |
Redesigned with confetti celebrations and improved step tracking |
sidebar-layout.tsx |
Implemented collapsible sidebar with tooltips and performance optimizations |
project-settings/page-client.tsx |
Added memoization and improved team member rendering |
apps/page-client.tsx |
New filtering and search functionality with category tabs |
apps/[appId]/page-client.tsx |
Added error handling for missing app frontends |
metrics-page.tsx |
Complete dashboard redesign with configurable widgets |
line-chart.tsx |
Enhanced chart components with tabbed metrics and improved tooltips |
globe.tsx |
Simplified globe visualization with better interaction patterns |
charts-section-with-data.tsx |
Updated layout with improved user activity displays |
DESIGN_GUIDE.md |
Comprehensive design system documentation with dark mode guidelines |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const performToggle = async () => { | ||
| setIsProcessing(true); | ||
|
|
||
| try { | ||
| await project.updateConfig({ | ||
| [`apps.installed.${appId}.enabled`]: !isEnabled, | ||
| }); | ||
| onToggleEnabled?.(!isEnabled); | ||
| } catch (error) { | ||
| console.error(`Failed to ${isEnabled ? 'disable' : 'enable'} app:`, error); | ||
| alert(`Failed to ${isEnabled ? 'disable' : 'enable'} ${app.displayName}. Please try again.`); | ||
| } finally { | ||
| setIsProcessing(false); | ||
| } | ||
| }; |
There was a problem hiding this comment.
The performToggle function is missing error handling. If project.updateConfig fails, the processing state will be set to false in the finally block, but the user won't receive proper feedback about the failure. Consider wrapping the entire operation in a try-catch block before the alert call on line 91.
| const handleGenerateKeys = async () => { | ||
| if (isGenerating) return; | ||
| setIsGenerating(true); | ||
| setError(null); | ||
|
|
||
| try { | ||
| const newKey = await adminApp.createInternalApiKey({ | ||
| hasPublishableClientKey: true, | ||
| hasSecretServerKey: true, | ||
| hasSuperSecretAdminKey: false, | ||
| expiresAt: new Date(Date.now() + TWO_HUNDRED_YEARS_IN_MS), | ||
| description: "Vercel Integration", | ||
| }); | ||
|
|
||
| setKeys({ | ||
| projectId: adminApp.projectId, | ||
| publishableClientKey: newKey.publishableClientKey!, | ||
| secretServerKey: newKey.secretServerKey!, | ||
| }); | ||
| } catch (err) { | ||
| console.error(err); | ||
| setError(err instanceof Error ? err.message : "Failed to generate API keys. Please try again."); | ||
| } finally { | ||
| setIsGenerating(false); | ||
| } | ||
| const newKey = await adminApp.createInternalApiKey({ | ||
| hasPublishableClientKey: true, | ||
| hasSecretServerKey: true, | ||
| hasSuperSecretAdminKey: false, | ||
| expiresAt: new Date(Date.now() + TWO_HUNDRED_YEARS_IN_MS), | ||
| description: "Vercel Integration", | ||
| }); | ||
|
|
||
| setKeys({ | ||
| projectId: adminApp.projectId, | ||
| publishableClientKey: newKey.publishableClientKey!, | ||
| secretServerKey: newKey.secretServerKey!, | ||
| }); | ||
| setIsGenerating(false); | ||
| }; |
There was a problem hiding this comment.
Missing error handling in handleGenerateKeys. The try-catch block was removed, so if createInternalApiKey fails, the error will propagate unhandled. The isGenerating state will remain true, leaving the UI in a broken state. Consider restoring the try-catch block or using runAsynchronouslyWithAlert to handle errors properly.
| defaultValuesRef.current = props.defaultValues; | ||
| form.reset(props.defaultValues as any); | ||
| } | ||
| }, [props.defaultValues, form]); |
There was a problem hiding this comment.
The dependency array includes form which is an object reference that changes on every render. This will cause unnecessary re-renders and potential infinite loops. Consider using useCallback dependencies more carefully or exclude form from the dependency array since it should be stable from useForm.
| }, [props.defaultValues, form]); | |
| }, [props.defaultValues]); |
| // TODO: This function will be used when widget configuration UI is implemented | ||
| function saveConfig(config: DashboardConfig) { | ||
| if (typeof window === 'undefined') return; | ||
| try { | ||
| localStorage.setItem(STORAGE_KEY, JSON.stringify(config)); | ||
| } catch (e) { | ||
| console.error('Failed to save dashboard config:', e); | ||
| } | ||
| } |
There was a problem hiding this comment.
[nitpick] The TODO comment indicates saveConfig function will be used in future implementation, but it's currently unused. Consider either implementing the feature now or removing the function to avoid dead code. If keeping it for future use, add an ESLint disable comment to prevent linting warnings.
There was a problem hiding this comment.
Actionable comments posted: 7
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
apps/dashboard/src/components/app-square.tsx (1)
16-40: Unused props:size,showBadge, andvariantare not utilized.The
AppIconcomponent declaressize,showBadge, andvariantprops but doesn't use them in the component body. Either implement the functionality or remove the unused props to avoid confusion.export function AppIcon({ appId, className, - size = "medium", - showBadge = false, - variant = "default" }: { appId: AppId, className?: string, - size?: "small" | "medium" | "large", - showBadge?: boolean, - variant?: AppSquareVariant, }) {Or pass these props to
SharedAppIconif it supports them.apps/dashboard/src/components/link.tsx (1)
26-37:onClickis invoked twice whenneedConfirmis trueBecause
onClick?.(e)is called inside theif (needConfirm)block and again unconditionally afterward, anyonClickhandler will run twice whenneedConfirmis true, which can cause duplicate side effects (analytics, state changes, etc.).You can fix this by returning early from the
needConfirmbranch:- onClick={(e: React.MouseEvent<HTMLAnchorElement>) => { - if (needConfirm) { - e.preventDefault(); - onClick?.(e); - router.push(href.toString()); - } - onClick?.(e); - }} + onClick={(e: React.MouseEvent<HTMLAnchorElement>) => { + if (needConfirm) { + e.preventDefault(); + onClick?.(e); + router.push(href.toString()); + return; + } + onClick?.(e); + }}
🧹 Nitpick comments (20)
apps/dashboard/src/components/app-warning-modal.tsx (2)
182-186: Consider using<Button>with async handler for loading state.Per coding guidelines, button components like
<Button>support async callbacks with built-in loading state. IfonConfirmis async (e.g., enabling an app), the current implementation won't show loading feedback and allows double-clicks.Consider importing and using the
Buttoncomponent from@stackframe/stack-uiwith asynconClickto leverage built-in loading state handling, similar to the Cancel button pattern.
87-92: Addaria-labelto close button for accessibility.The close button only has an
Xicon without accessible text for screen readers.<button onClick={onClose} className="p-1.5 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors" + aria-label="Close modal" > <X className="w-4 h-4 text-gray-500" /> </button>apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/charts-section-with-data.tsx (1)
93-93: Consider typing user data instead ofany.Both user mappings use
anytype annotation, losing type safety. If a user type exists in the codebase, applying it would catch potential property access errors at compile time.Also applies to: 157-157
apps/dashboard/src/components/app-store-entry.tsx (1)
23-28: Consider making features configurable per app.The comment suggests future customization, but consider defining this in
apps-config.tsorapps-frontend.tsxnow to avoid hardcoded marketing claims that may not apply to all apps (e.g., "Production Ready" for alpha apps).apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/page-client.tsx (2)
56-57: UseinstalledAppsSetfor O(1) lookup instead ofincludes().Line 57 uses
installedApps.includes(appId)whileinstalledAppsSetwas created specifically for O(1) lookups. This inconsistency also appears ingetCategoryCountat line 90.// Apply category filter if (selectedCategory === "installed") { - apps = apps.filter(appId => installedApps.includes(appId)); + apps = apps.filter(appId => installedAppsSet.has(appId)); } else if (selectedCategory !== "all") {Also update line 90:
const getCategoryCount = (categoryId: string) => { - if (categoryId === "installed") return installedApps.length; + if (categoryId === "installed") return installedAppsSet.size;
88-102: Consider memoizinggetCategoryCountwithuseCallback.
getCategoryCountis called for each category tab during every render. While the computation is lightweight, wrapping it inuseCallbackwould be consistent with the memoization strategy used elsewhere in this component.apps/dashboard/src/components/app-square.tsx (1)
201-277:AppListItemlooks good but consider consistent error handling.The
onEnablecallback at line 267 is fire-and-forget. If the caller passes an async function, errors won't be handled. Consider documenting this expectation or wrapping inrunAsynchronously.apps/dashboard/src/components/settings.tsx (1)
66-69: Wrap async callback withrunAsynchronouslyWithAlert.If
propOnCheckedChangethrows, the error will propagate but may not be handled by the Switch component's event system. Consider wrapping with error handling.const onCheckedChange = useCallback(async (checked: boolean) => { setCheckedState(checked); - await propOnCheckedChange(checked); + await propOnCheckedChange(checked); }, [propOnCheckedChange]);Since this is called directly (not via runAsynchronously), ensure callers handle errors, or wrap the call site:
<Switch id={id} checked={checked} - onCheckedChange={onCheckedChange} + onCheckedChange={(checked) => runAsynchronouslyWithAlert(onCheckedChange(checked))} disabled={props.disabled} />apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/metrics-page.tsx (4)
50-77: Avoid try-catch with console.error for error handling.The
loadConfigfunction uses try-catch withconsole.error, which violates the coding guidelines. Per project conventions, userunAsynchronouslyorrunAsynchronouslyWithAlertfor error handling instead of try-catch blocks.However, since this is a synchronous localStorage read during initialization, consider either:
- Letting the error propagate (localStorage errors are rare)
- Using a silent fallback without logging
function loadConfig(): DashboardConfig { if (typeof window === 'undefined') return DEFAULT_CONFIG; - try { - const stored = localStorage.getItem(STORAGE_KEY); - if (stored) { - const parsed = JSON.parse(stored); - // Validate and merge with defaults for any new widgets - const validWidgetIds = new Set(AVAILABLE_WIDGETS.map(w => w.id)); - const enabledWidgets = (parsed.enabledWidgets || []).filter((id: string) => validWidgetIds.has(id as WidgetId)); - const widgetOrder = (parsed.widgetOrder || []).filter((id: string) => validWidgetIds.has(id as WidgetId)); - - // Add any new widgets that aren't in the stored config - for (const widget of AVAILABLE_WIDGETS) { - if (!widgetOrder.includes(widget.id)) { - widgetOrder.push(widget.id); - if (widget.defaultEnabled) { - enabledWidgets.push(widget.id); - } + const stored = localStorage.getItem(STORAGE_KEY); + if (stored) { + const parsed = JSON.parse(stored); + // Validate and merge with defaults for any new widgets + const validWidgetIds = new Set(AVAILABLE_WIDGETS.map(w => w.id)); + const enabledWidgets = (parsed.enabledWidgets || []).filter((id: string) => validWidgetIds.has(id as WidgetId)); + const widgetOrder = (parsed.widgetOrder || []).filter((id: string) => validWidgetIds.has(id as WidgetId)); + + // Add any new widgets that aren't in the stored config + for (const widget of AVAILABLE_WIDGETS) { + if (!widgetOrder.includes(widget.id)) { + widgetOrder.push(widget.id); + if (widget.defaultEnabled) { + enabledWidgets.push(widget.id); } } - - return { enabledWidgets, widgetOrder }; } - } catch (e) { - console.error('Failed to load dashboard config:', e); + + return { enabledWidgets, widgetOrder }; } return DEFAULT_CONFIG; }
79-87: Remove unusedsaveConfigfunction.The
saveConfigfunction is defined but never called. The TODO comment indicates it will be used when widget configuration UI is implemented, but dead code should not be committed. Consider removing it and adding it back when the feature is implemented.
121-128: Replaceanytypes with proper typing.The reduce callback uses
anytypes which reduces type safety. SinceDataPointhas a defined structure, use proper types.- const filteredData = timeRange === '7d' ? dailyUsers.slice(-7) : dailyUsers.slice(-30); - return filteredData.reduce((sum: any, point: { activity: any; }) => sum + point.activity, 0); + const filteredData = timeRange === '7d' ? dailyUsers.slice(-7) : dailyUsers.slice(-30); + return filteredData.reduce((sum: number, point: { activity: number }) => sum + point.activity, 0);
194-204: Replaceanytype fordataprop with a proper interface.Both
DailyActiveUsersWidgetandDailySignUpsWidgetusedata: anywhich loses type safety. Define an interface for the metrics data structure.+interface MetricsData { + daily_active_users?: DataPoint[]; + recently_active?: UserListItem[]; + daily_users?: DataPoint[]; + recently_registered?: UserListItem[]; + total_users?: number; +} function DailyActiveUsersWidget({ data, projectId, router, timeRange }: { - data: any, + data: MetricsData, projectId: string, router: ReturnType<typeof useRouter>, timeRange: TimeRange }) {Also applies to: 220-230
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/vercel/page-client.tsx (1)
309-312: UnuseditemIdparameter inhandleItemClick.The
itemIdparameter is received but never used. Either remove the parameter or use it for more granular item-level completion tracking if intended.-const handleItemClick = (stepId: StepId, itemId: string) => { +const handleItemClick = (stepId: StepId, _itemId: string) => { // Toggle the step completion when clicking on an item toggleStepCompletion(stepId); };Or if item-level tracking is intended:
const handleItemClick = (stepId: StepId, itemId: string) => { - // Toggle the step completion when clicking on an item - toggleStepCompletion(stepId); + // TODO: Implement item-level completion tracking using itemId + toggleStepCompletion(stepId); };apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/project-settings/page-client.tsx (3)
21-45: Replaceanytype with proper interface forTeamMemberItem.The
memberprop usesanytype which loses type safety. Define a proper interface based on the expected shape.-function TeamMemberItem({ member }: { member: any }) { +interface TeamMember { + id: string; + teamProfile: { + displayName?: string | null; + profileImageUrl?: string | null; + }; +} + +function TeamMemberItem({ member }: { member: TeamMember }) { const displayName = member.teamProfile.displayName?.trim() || "Name not set";
139-141: Replaceanytype with proper team type.The
handleTeamSwitcherChangecallback usesanyfor the team parameter. Use the proper team type from the SDK.-const handleTeamSwitcherChange = useCallback(async (team: any) => { +const handleTeamSwitcherChange = useCallback(async (team: { id: string }) => { setSelectedTeamId(team.id); }, []);
230-245: Inconsistent memoization for logo handlers.
handleLogoChangeandhandleFullLogoChangeare memoized (lines 125-131), but the dark mode logo handlers (lines 230-241) are inline async functions. This inconsistency could cause unnecessary re-renders.Consider memoizing all logo handlers for consistency:
+const handleLogoDarkModeChange = useCallback(async (logoDarkModeUrl: string | null) => { + await project.update({ logoDarkModeUrl }); +}, [project]); + +const handleLogoFullDarkModeChange = useCallback(async (logoFullDarkModeUrl: string | null) => { + await project.update({ logoFullDarkModeUrl }); +}, [project]); <LogoUpload label="Logo (Dark Mode)" value={project.logoDarkModeUrl} - onValueChange={async (logoDarkModeUrl) => { - await project.update({ logoDarkModeUrl }); - }} + onValueChange={handleLogoDarkModeChange} ... />apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/globe.tsx (2)
77-78: Remove unuseddisplayNamevariable.The
displayNamevariable is defined but never used in the component. This appears to be leftover from the previous implementation.const user = useUser({ or: "redirect" }); -const displayName = user.displayName ?? user.primaryEmail;
39-40: Remove unused refs and size hooks.
sectionContainerRefandsectionContainerSizeare defined but never used meaningfully. The ref is attached to an element (line 138) but the size is never consumed.const globeContainerRef = useRef<HTMLDivElement>(null); const globeContainerSize = useSize(globeContainerRef); -const sectionContainerRef = useRef<HTMLDivElement>(null); -const sectionContainerSize = useSize(sectionContainerRef);And update the JSX:
<div - ref={sectionContainerRef} className='relative flex items-center justify-center' >apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/line-chart.tsx (2)
34-39: ImportisWeekendfrom shared utilities instead of duplicating.This
isWeekendfunction duplicates the one inpackages/stack-shared/src/utils/dates.tsx. Import from the shared package to avoid duplication.+import { isWeekend as isWeekendDate } from '@stackframe/stack-shared/dist/utils/dates'; -const isWeekend = (dateString: string): boolean => { - const date = new Date(dateString); - if (isNaN(date.getTime())) return false; - const dayOfWeek = date.getDay(); - return dayOfWeek === 0 || dayOfWeek === 6; // Sunday (0) or Saturday (6) -} +const isWeekend = (dateString: string): boolean => { + const date = new Date(dateString); + if (isNaN(date.getTime())) return false; + return isWeekendDate(date); +};
548-550: Inconsistent access patterns for BRAND_CONFIG.The code uses both direct object access (
BRAND_CONFIG[key as keyof typeof BRAND_CONFIG]) and Map access (BRAND_CONFIG_MAP.get(item.method)). Use the Map consistently since it was created for efficient lookups.-const brandConfig = key ? BRAND_CONFIG[key as keyof typeof BRAND_CONFIG] : undefined; +const brandConfig = key ? BRAND_CONFIG_MAP.get(key) : undefined; -style={{ backgroundColor: BRAND_CONFIG[item.method as keyof typeof BRAND_CONFIG]?.color ?? "var(--color-other)" }} +style={{ backgroundColor: BRAND_CONFIG_MAP.get(item.method)?.color ?? "var(--color-other)" }}Also applies to: 602-605
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (16)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/charts-section-with-data.tsx(3 hunks)apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/globe.tsx(3 hunks)apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/line-chart.tsx(2 hunks)apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/metrics-page.tsx(1 hunks)apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/[appId]/page-client.tsx(1 hunks)apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/page-client.tsx(1 hunks)apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/project-settings/page-client.tsx(10 hunks)apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx(11 hunks)apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/vercel/page-client.tsx(5 hunks)apps/dashboard/src/components/app-square.tsx(2 hunks)apps/dashboard/src/components/app-store-entry.tsx(2 hunks)apps/dashboard/src/components/app-warning-modal.tsx(1 hunks)apps/dashboard/src/components/copyable-text.tsx(1 hunks)apps/dashboard/src/components/link.tsx(2 hunks)apps/dashboard/src/components/settings.tsx(5 hunks)apps/dashboard/src/lib/apps-frontend.tsx(3 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx}: Ensure code passespnpm typecheckfor TypeScript type validation
Never usetoastfor blocking alerts and errors; use alerts instead as they are more visible to users
Keep hover/click transitions snappy and fast; apply transitions after the action (e.g., smooth fade-out on hover end) rather than delaying actions with pre-transitions to avoid sluggish UI
Never use try-catch-all, never void a promise, never use .catch(console.error) or similar; userunAsynchronouslyorrunAsynchronouslyWithAlertfor error handling instead of try-catch blocks
Use loading indicators instead of try-catch blocks for asynchronous UI operations; button components like support async callbacks with built-in loading state
Use ES6 maps instead of records wherever possible
Files:
apps/dashboard/src/components/app-warning-modal.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/[appId]/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/metrics-page.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/charts-section-with-data.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/vercel/page-client.tsxapps/dashboard/src/components/app-square.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/globe.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsxapps/dashboard/src/components/copyable-text.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/line-chart.tsxapps/dashboard/src/components/link.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/project-settings/page-client.tsxapps/dashboard/src/components/app-store-entry.tsxapps/dashboard/src/lib/apps-frontend.tsxapps/dashboard/src/components/settings.tsx
apps/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Prefer client components and dynamic functions like
usePathnameover Next.js dynamic functions to keep pages static; avoid usingawait paramsor similar dynamic patterns
Files:
apps/dashboard/src/components/app-warning-modal.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/[appId]/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/metrics-page.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/charts-section-with-data.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/vercel/page-client.tsxapps/dashboard/src/components/app-square.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/globe.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsxapps/dashboard/src/components/copyable-text.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/line-chart.tsxapps/dashboard/src/components/link.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/project-settings/page-client.tsxapps/dashboard/src/components/app-store-entry.tsxapps/dashboard/src/lib/apps-frontend.tsxapps/dashboard/src/components/settings.tsx
**/apps-{frontend,config}.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
To update available apps, edit
apps-frontend.tsxandapps-config.ts
Files:
apps/dashboard/src/lib/apps-frontend.tsx
🧠 Learnings (7)
📚 Learning: 2025-11-25T02:09:03.086Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T02:09:03.086Z
Learning: Applies to **/apps-{frontend,config}.{ts,tsx} : To update available apps, edit `apps-frontend.tsx` and `apps-config.ts`
Applied to files:
apps/dashboard/src/components/app-warning-modal.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/[appId]/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/page-client.tsxapps/dashboard/src/components/app-square.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsxapps/dashboard/src/components/app-store-entry.tsxapps/dashboard/src/lib/apps-frontend.tsxapps/dashboard/src/components/settings.tsx
📚 Learning: 2025-10-11T04:13:19.308Z
Learnt from: N2D4
Repo: stack-auth/stack-auth PR: 943
File: examples/convex/app/action/page.tsx:23-28
Timestamp: 2025-10-11T04:13:19.308Z
Learning: In the stack-auth codebase, use `runAsynchronouslyWithAlert` from `stackframe/stack-shared/dist/utils/promises` for async button click handlers and form submissions instead of manual try/catch blocks. This utility automatically handles errors and shows alerts to users.
Applied to files:
apps/dashboard/src/components/app-warning-modal.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/vercel/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/project-settings/page-client.tsxapps/dashboard/src/components/settings.tsx
📚 Learning: 2025-11-25T02:09:03.086Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T02:09:03.086Z
Learning: Applies to **/*.{ts,tsx} : Never use `toast` for blocking alerts and errors; use alerts instead as they are more visible to users
Applied to files:
apps/dashboard/src/components/app-warning-modal.tsx
📚 Learning: 2025-11-25T02:09:03.086Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T02:09:03.086Z
Learning: Applies to apps/**/*.{ts,tsx} : Prefer client components and dynamic functions like `usePathname` over Next.js dynamic functions to keep pages static; avoid using `await params` or similar dynamic patterns
Applied to files:
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/[appId]/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsxapps/dashboard/src/components/app-store-entry.tsx
📚 Learning: 2025-11-25T02:09:03.086Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T02:09:03.086Z
Learning: Applies to apps/backend/src/app/api/latest/**/*.{ts,tsx} : In the Stack Auth monorepo backend API, organize routes by resource type (auth, user management, team management, OAuth providers) in `/apps/backend/src/app/api/latest/*`
Applied to files:
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx
📚 Learning: 2025-11-25T02:09:03.086Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T02:09:03.086Z
Learning: When making dashboard changes, provide deep links to the dashboard page in the format `http://localhost:<$NEXT_PUBLIC_STACK_PORT_PREFIX>01/projects/-selector-/...` (or use a.localhost, b.localhost, c.localhost for port prefixes 91, 92, 93)
Applied to files:
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/metrics-page.tsx
📚 Learning: 2025-11-25T02:09:03.086Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T02:09:03.086Z
Learning: Applies to **/*.{ts,tsx} : Never use try-catch-all, never void a promise, never use .catch(console.error) or similar; use `runAsynchronously` or `runAsynchronouslyWithAlert` for error handling instead of try-catch blocks
Applied to files:
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/vercel/page-client.tsx
🧬 Code graph analysis (6)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/[appId]/page-client.tsx (5)
apps/dashboard/src/lib/apps-frontend.tsx (3)
AppId(13-13)ALL_APPS_FRONTEND(67-228)getAppPath(40-43)apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/use-admin-app.tsx (1)
useAdminApp(29-44)packages/stack-shared/src/utils/errors.tsx (1)
StackAssertionError(69-85)apps/dashboard/src/components/app-store-entry.tsx (1)
AppStoreEntry(11-197)packages/stack-shared/src/utils/promises.tsx (1)
runAsynchronouslyWithAlert(312-328)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/page-client.tsx (4)
apps/dashboard/src/lib/apps-frontend.tsx (1)
AppId(13-13)packages/stack-shared/src/apps/apps-config.ts (2)
AppId(48-48)ALL_APPS(50-141)packages/stack-shared/src/utils/strings.tsx (1)
stringCompare(61-65)docs/src/components/icons.tsx (1)
Search(93-96)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/metrics-page.tsx (3)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/line-chart.tsx (5)
LineChartDisplayConfig(14-18)TimeRange(12-12)ChartCard(177-208)TabbedMetricsCard(244-405)TimeRangeToggle(210-242)apps/dashboard/src/components/app-square.tsx (1)
AppIcon(16-40)apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/globe-section-with-data.tsx (1)
GlobeSectionWithData(9-18)
apps/dashboard/src/components/copyable-text.tsx (1)
docs/src/components/icons.tsx (2)
Check(163-165)Copy(185-205)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/line-chart.tsx (2)
packages/stack-shared/src/utils/dates.tsx (2)
isWeekend(5-7)fromNow(28-30)apps/dashboard/src/components/router.tsx (1)
useRouter(15-33)
apps/dashboard/src/components/app-store-entry.tsx (3)
docs/src/components/icons.tsx (2)
Zap(345-347)Check(163-165)apps/dashboard/src/components/app-square.tsx (1)
AppIcon(16-40)packages/stack-shared/src/apps/apps-config.ts (1)
ALL_APP_TAGS(5-39)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: CodeQL analysis (javascript-typescript)
- GitHub Check: Vercel Agent Review
- GitHub Check: all-good
🔇 Additional comments (20)
apps/dashboard/src/lib/apps-frontend.tsx (2)
3-3: LGTM! Good type-safety improvement.Deriving
AppIdfromALL_APPSensures the type stays in sync with the configuration source of truth, eliminating potential drift between frontend and config definitions.Also applies to: 13-14
218-220: LGTM! Improved logo centering and styling.The flex container properly centers the Vercel logo, and the
object-containwith padding ensures consistent rendering across different viewport sizes.apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/[appId]/page-client.tsx (1)
14-14: Non-null assertion onuseAdminApp()may mask errors.Based on the
useAdminAppimplementation, it can returnundefinedwhenprojectIdis not provided and noAdminInterfaceProviderexists. The non-null assertion (!) suppresses TypeScript's null check but won't prevent runtime errors if the hook returnsundefined. Consider using optional chaining or an early return/redirect pattern if the app could legitimately be undefined.apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/charts-section-with-data.tsx (2)
25-28: LGTM! Good theme-aware chart configuration.Using theme objects with explicit light/dark mode colors ensures proper visibility and contrast in both modes.
Also applies to: 39-42
54-65: LGTM! Well-structured responsive grid layout.The layout effectively uses CSS Grid with responsive breakpoints (
lg:grid-cols-2,lg:col-span-2, etc.) and the button-based user items provide consistent interaction patterns with proper hover states.Also applies to: 68-131, 133-189
apps/dashboard/src/components/app-store-entry.tsx (1)
11-196: LGTM! Clean UI refactor with good structure.The component is well-organized with clear sections (hero, features, screenshots, description). Good use of conditional rendering for stage warnings and empty states.
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/page-client.tsx (1)
28-219: Good use of client-side state and memoization.The component correctly uses
usePathname-style client patterns per the coding guidelines. The filtering/sorting logic withuseMemois well-structured.apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx (2)
516-525:getDefaultExpandedSectionsrecreates URL on every call.The function is memoized with
useCallback, butpathnamein the dependency array means it gets recreated on every pathname change, which then could trigger state updates. This is likely fine, but the initial state function passed touseStateat line 527 will only be called once.Verify that changing routes doesn't cause unexpected collapse/expand behavior. The
expandedSectionsstate is initialized once and won't update whenpathnamechanges (which is likely the intended behavior).
722-820: Clean implementation of collapsible sidebar layout.Good use of
TooltipProviderat the root, proper fixed positioning for the sidebar, and smooth transitions withtransition-[width]andtransition-[margin]. The collapsed state management withuseCallbackis well done.apps/dashboard/src/components/settings.tsx (2)
302-309: Good pattern for detecting defaultValues changes with ref.Using a ref to track previous
defaultValuesand only resetting when they actually change is a good pattern to avoid unnecessary form resets. This correctly handles the case whereformobject reference changes but values don't.
12-49: Good memoization of settings components.The use of
React.memofor these components anduseCallbackfor handlers is appropriate for preventing unnecessary re-renders in forms with many settings.Also applies to: 51-100, 114-148, 150-171, 173-192
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/metrics-page.tsx (1)
322-350: Widget'total-users'is in AVAILABLE_WIDGETS but not handled inrenderWidget.The
renderWidgetfunction handles'apps','daily-active-users', and'daily-sign-ups', but'total-users'(defined inAVAILABLE_WIDGETSat line 32) is not handled—it returnsnull. WhileTotalUsersDisplayis rendered separately in the globe section, this could cause confusion if someone enables only'total-users'without the globe.Verify this is intentional—if users disable the globe but enable
'total-users', they won't see any total users display sincerenderWidgetreturnsnullfor it.apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/vercel/page-client.tsx (1)
518-528: LGTM! Good keyboard accessibility implementation.The
CardHeadercorrectly implements keyboard accessibility withrole="button",tabIndex={0}, and properonKeyDownhandling for Enter and Space keys. This aligns with the PR's accessibility improvements.apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/project-settings/page-client.tsx (1)
103-122: Guard clause forisTransferringshould come before async operation.The
isTransferringcheck on line 105 is good for preventing double-clicks, but the function is wrapped withrunAsynchronouslyWithAlertat the call site (line 328). This is the correct pattern per coding guidelines.apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/globe.tsx (2)
270-302: LGTM! Clean tooltip implementation.The tooltip now uses fixed positioning with cursor tracking via
onMouseMove, which provides smooth tooltip behavior. The transform calculation properly centers and offsets the tooltip above the cursor.
310-324: LGTM! Simplified loader.The
PlanetLoaderhas been simplified from a complex animation to a clean, minimal spinner. This aligns with the PR's design improvements and reduces complexity.apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/line-chart.tsx (3)
489-490: Good use of ES6 Map for efficient lookups.Using
MapforBRAND_CONFIG_MAPfollows the coding guidelines preference for ES6 maps over records for lookups.
89-175: LGTM! Well-structured shared chart component.The
ActivityBarChartcomponent is a good refactoring that extracts common chart logic for reuse acrossLineChartDisplayandTabbedMetricsCard. The component properly handles weekend styling, animations, and responsive tick sizing.
210-242: LGTM! Clean TimeRangeToggle implementation.The toggle component is well-structured with proper button semantics, clear visual states, and good accessibility (using
type="button"to prevent form submission).apps/dashboard/src/components/link.tsx (1)
10-20: LinkProps surface changes (eventfulonClick,title) look consistentThe updated
onClicksignature and added optionaltitleprop align with how the component uses them (passing the event intoonClickand forwarding...resttoNextLink). No issues from this change in this file.
| <AppStoreEntry | ||
| appId={appId} | ||
| onEnable={handleEnable} | ||
| onEnable={async () => runAsynchronouslyWithAlert(handleEnable())} |
There was a problem hiding this comment.
Incorrect runAsynchronouslyWithAlert usage — pass the function, not the invocation.
handleEnable() is already invoked, returning a Promise. runAsynchronouslyWithAlert expects a function that returns a Promise, not a Promise itself. This will cause the async to execute immediately rather than being managed by the wrapper.
- onEnable={async () => runAsynchronouslyWithAlert(handleEnable())}
+ onEnable={() => runAsynchronouslyWithAlert(handleEnable)}📝 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.
| onEnable={async () => runAsynchronouslyWithAlert(handleEnable())} | |
| onEnable={() => runAsynchronouslyWithAlert(handleEnable)} |
🤖 Prompt for AI Agents
In
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/[appId]/page-client.tsx
around line 33, the code invokes handleEnable() immediately and passes its
Promise into runAsynchronouslyWithAlert; instead pass the function itself so the
wrapper can call it when needed. Change the prop to pass a function reference
(e.g., wrap runAsynchronouslyWithAlert with an arrow that supplies handleEnable
without calling it) so runAsynchronouslyWithAlert receives a () => Promise and
not an already-started Promise.
| const caretClasses = cn( | ||
| "h-4 w-4 flex-shrink-0 transition-colors transition-transform", | ||
| isHighlighted | ||
| ? "text-blue-600 dark:text-blue-400" | ||
| : "text-muted-foreground group-hover:text-foreground", | ||
| isSection && isExpanded && "rotate-180" | ||
| ); |
There was a problem hiding this comment.
Duplicate transition- class in caretClasses.
Line 283 has both transition-colors and transition-transform which may conflict. Consider using a single transition-all or specifying transition with multiple properties.
const caretClasses = cn(
- "h-4 w-4 flex-shrink-0 transition-colors transition-transform",
+ "h-4 w-4 flex-shrink-0 transition-all",
isHighlighted
? "text-blue-600 dark:text-blue-400"
: "text-muted-foreground group-hover:text-foreground",
isSection && isExpanded && "rotate-180"
);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const caretClasses = cn( | |
| "h-4 w-4 flex-shrink-0 transition-colors transition-transform", | |
| isHighlighted | |
| ? "text-blue-600 dark:text-blue-400" | |
| : "text-muted-foreground group-hover:text-foreground", | |
| isSection && isExpanded && "rotate-180" | |
| ); | |
| const caretClasses = cn( | |
| "h-4 w-4 flex-shrink-0 transition-all", | |
| isHighlighted | |
| ? "text-blue-600 dark:text-blue-400" | |
| : "text-muted-foreground group-hover:text-foreground", | |
| isSection && isExpanded && "rotate-180" | |
| ); |
🤖 Prompt for AI Agents
In
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx
around lines 282 to 288, the caretClasses include both "transition-colors" and
"transition-transform" which the reviewer flagged as redundant; replace those
two classes with a single "transition-all" (or a single explicit transition
class you prefer) so the caret uses one transition utility, e.g., remove
"transition-colors transition-transform" and add "transition-all" to the cn
call.
| const handleGenerateKeys = async () => { | ||
| if (isGenerating) return; | ||
| setIsGenerating(true); | ||
| setError(null); | ||
|
|
||
| try { | ||
| const newKey = await adminApp.createInternalApiKey({ | ||
| hasPublishableClientKey: true, | ||
| hasSecretServerKey: true, | ||
| hasSuperSecretAdminKey: false, | ||
| expiresAt: new Date(Date.now() + TWO_HUNDRED_YEARS_IN_MS), | ||
| description: "Vercel Integration", | ||
| }); | ||
|
|
||
| setKeys({ | ||
| projectId: adminApp.projectId, | ||
| publishableClientKey: newKey.publishableClientKey!, | ||
| secretServerKey: newKey.secretServerKey!, | ||
| }); | ||
| } catch (err) { | ||
| console.error(err); | ||
| setError(err instanceof Error ? err.message : "Failed to generate API keys. Please try again."); | ||
| } finally { | ||
| setIsGenerating(false); | ||
| } | ||
| const newKey = await adminApp.createInternalApiKey({ | ||
| hasPublishableClientKey: true, | ||
| hasSecretServerKey: true, | ||
| hasSuperSecretAdminKey: false, | ||
| expiresAt: new Date(Date.now() + TWO_HUNDRED_YEARS_IN_MS), | ||
| description: "Vercel Integration", | ||
| }); | ||
|
|
||
| setKeys({ | ||
| projectId: adminApp.projectId, | ||
| publishableClientKey: newKey.publishableClientKey!, | ||
| secretServerKey: newKey.secretServerKey!, | ||
| }); | ||
| setIsGenerating(false); | ||
| }; |
There was a problem hiding this comment.
Wrap async key generation with error handling.
handleGenerateKeys is an async function that can throw errors from adminApp.createInternalApiKey, but it's not wrapped with runAsynchronouslyWithAlert. Per coding guidelines, async operations should use proper error handling utilities.
+import { runAsynchronouslyWithAlert } from "@stackframe/stack-shared/dist/utils/promises";
const handleGenerateKeys = async () => {
if (isGenerating) return;
setIsGenerating(true);
setError(null);
- const newKey = await adminApp.createInternalApiKey({
- hasPublishableClientKey: true,
- hasSecretServerKey: true,
- hasSuperSecretAdminKey: false,
- expiresAt: new Date(Date.now() + TWO_HUNDRED_YEARS_IN_MS),
- description: "Vercel Integration",
- });
-
- setKeys({
- projectId: adminApp.projectId,
- publishableClientKey: newKey.publishableClientKey!,
- secretServerKey: newKey.secretServerKey!,
- });
- setIsGenerating(false);
+ await runAsynchronouslyWithAlert(async () => {
+ const newKey = await adminApp.createInternalApiKey({
+ hasPublishableClientKey: true,
+ hasSecretServerKey: true,
+ hasSuperSecretAdminKey: false,
+ expiresAt: new Date(Date.now() + TWO_HUNDRED_YEARS_IN_MS),
+ description: "Vercel Integration",
+ });
+
+ setKeys({
+ projectId: adminApp.projectId,
+ publishableClientKey: newKey.publishableClientKey!,
+ secretServerKey: newKey.secretServerKey!,
+ });
+ });
+ setIsGenerating(false);
};Note: runAsynchronouslyWithAlert is already imported but not used here.
🤖 Prompt for AI Agents
In
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/vercel/page-client.tsx
around lines 75 to 94, the async handler handleGenerateKeys calls
adminApp.createInternalApiKey directly and can throw — wrap the operation with
the existing runAsynchronouslyWithAlert helper: call runAsynchronouslyWithAlert
with an async callback that performs the createInternalApiKey call, sets keys on
success, and sets setError on failure; ensure you still short-circuit when
isGenerating is true, setIsGenerating(true) before the wrapped call and
setIsGenerating(false) in a finally-equivalent cleanup so loading state is
cleared even if an error occurs.
| const performToggle = async () => { | ||
| setIsProcessing(true); | ||
|
|
||
| try { | ||
| await project.updateConfig({ | ||
| [`apps.installed.${appId}.enabled`]: !isEnabled, | ||
| }); | ||
| onToggleEnabled?.(!isEnabled); | ||
| } catch (error) { | ||
| console.error(`Failed to ${isEnabled ? 'disable' : 'enable'} app:`, error); | ||
| alert(`Failed to ${isEnabled ? 'disable' : 'enable'} ${app.displayName}. Please try again.`); | ||
| } finally { | ||
| setIsProcessing(false); | ||
| } | ||
| }; |
There was a problem hiding this comment.
Replace try-catch with runAsynchronouslyWithAlert per coding guidelines.
The coding guidelines state: "never use try-catch-all" and recommend using runAsynchronouslyWithAlert for error handling. The current implementation uses a try-catch-all pattern with console.error and alert().
+import { runAsynchronouslyWithAlert } from "@stackframe/stack-shared/dist/utils/promises";
- const performToggle = async () => {
- setIsProcessing(true);
-
- try {
- await project.updateConfig({
- [`apps.installed.${appId}.enabled`]: !isEnabled,
- });
- onToggleEnabled?.(!isEnabled);
- } catch (error) {
- console.error(`Failed to ${isEnabled ? 'disable' : 'enable'} app:`, error);
- alert(`Failed to ${isEnabled ? 'disable' : 'enable'} ${app.displayName}. Please try again.`);
- } finally {
- setIsProcessing(false);
- }
+ const performToggle = async () => {
+ setIsProcessing(true);
+ await project.updateConfig({
+ [`apps.installed.${appId}.enabled`]: !isEnabled,
+ });
+ onToggleEnabled?.(!isEnabled);
+ setIsProcessing(false);
};Then wrap the call sites with runAsynchronouslyWithAlert:
- runAsynchronously(performToggle());
+ runAsynchronouslyWithAlert(performToggle());Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In apps/dashboard/src/components/app-square.tsx around lines 81–95, replace the
try-catch-all in performToggle with runAsynchronouslyWithAlert:
setIsProcessing(true) before invoking runAsynchronouslyWithAlert, call
runAsynchronouslyWithAlert with an async callback that performs
project.updateConfig({[`apps.installed.${appId}.enabled`]: !isEnabled}) and then
calls onToggleEnabled?.(!isEnabled) on success, provide a user-facing error
message that includes app.displayName (the same text currently used in alert),
and ensure setIsProcessing(false) runs in the finally/onComplete handler
provided to runAsynchronouslyWithAlert so processing state is cleared whether it
succeeds or fails.
| useEffect(() => { | ||
| if (isOpen) { | ||
| setTimeout(() => setIsAnimating(true), 10); | ||
| } else { | ||
| setIsAnimating(false); | ||
| } | ||
| }, [isOpen]); |
There was a problem hiding this comment.
Missing cleanup for setTimeout — potential memory leak.
When isOpen changes to true, a timeout is scheduled but never cleared if the component unmounts before the timeout fires. This can cause a state update on an unmounted component.
useEffect(() => {
if (isOpen) {
- setTimeout(() => setIsAnimating(true), 10);
+ const timer = setTimeout(() => setIsAnimating(true), 10);
+ return () => clearTimeout(timer);
} else {
setIsAnimating(false);
}
}, [isOpen]);📝 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.
| useEffect(() => { | |
| if (isOpen) { | |
| setTimeout(() => setIsAnimating(true), 10); | |
| } else { | |
| setIsAnimating(false); | |
| } | |
| }, [isOpen]); | |
| useEffect(() => { | |
| if (isOpen) { | |
| const timer = setTimeout(() => setIsAnimating(true), 10); | |
| return () => clearTimeout(timer); | |
| } else { | |
| setIsAnimating(false); | |
| } | |
| }, [isOpen]); |
🤖 Prompt for AI Agents
In apps/dashboard/src/components/app-warning-modal.tsx around lines 24 to 30,
the useEffect schedules a setTimeout when isOpen becomes true but never clears
it, risking state updates after unmount; fix by storing the timeout id (use a
ref or local variable compatible with the environment) and return a cleanup
function from the effect that calls clearTimeout on that id, and also clear the
timeout whenever isOpen changes before scheduling a new one so no stray timers
remain.
| const handleCopy = useCallback(async () => { | ||
| try { | ||
| await navigator.clipboard.writeText(props.value); | ||
| setCopied(true); | ||
| } catch (err) { | ||
| console.error("Failed to copy:", err); | ||
| } | ||
| }, [props.value]); |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Replace try-catch with runAsynchronouslyWithAlert per coding guidelines.
The coding guidelines prohibit try-catch-all with console.error. For clipboard operations that can fail (e.g., permissions denied), use runAsynchronouslyWithAlert to properly alert the user.
+import { runAsynchronouslyWithAlert } from "@stackframe/stack-shared/dist/utils/promises";
+
- const handleCopy = useCallback(async () => {
- try {
- await navigator.clipboard.writeText(props.value);
- setCopied(true);
- } catch (err) {
- console.error("Failed to copy:", err);
- }
- }, [props.value]);
+ const handleCopy = useCallback(() => {
+ runAsynchronouslyWithAlert(async () => {
+ await navigator.clipboard.writeText(props.value);
+ setCopied(true);
+ });
+ }, [props.value]);📝 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 handleCopy = useCallback(async () => { | |
| try { | |
| await navigator.clipboard.writeText(props.value); | |
| setCopied(true); | |
| } catch (err) { | |
| console.error("Failed to copy:", err); | |
| } | |
| }, [props.value]); | |
| import { runAsynchronouslyWithAlert } from "@stackframe/stack-shared/dist/utils/promises"; | |
| const handleCopy = useCallback(() => { | |
| runAsynchronouslyWithAlert(async () => { | |
| await navigator.clipboard.writeText(props.value); | |
| setCopied(true); | |
| }); | |
| }, [props.value]); |
🤖 Prompt for AI Agents
In apps/dashboard/src/components/copyable-text.tsx around lines 22 to 29,
replace the manual try-catch that calls navigator.clipboard.writeText and
console.error with a call to the existing runAsynchronouslyWithAlert helper:
wrap the clipboard write in runAsynchronouslyWithAlert so any rejection shows a
user alert, and on success setCopied(true); keep the useCallback and dependency
on props.value unchanged and ensure the helper receives an async function that
awaits navigator.clipboard.writeText(props.value).
| const handleFormSubmit = useCallback((e: React.FormEvent<HTMLFormElement>) => { | ||
| runAsynchronously(async () => { | ||
| setSubmitting(true); | ||
| try { | ||
| await form.handleSubmit(onSubmit)(e); | ||
| } catch (error) { | ||
| toast({ | ||
| title: 'Failed to save changes', | ||
| description: error instanceof Error ? error.message : 'An error occurred', | ||
| variant: 'destructive' | ||
| }); | ||
| } finally { | ||
| setSubmitting(false); | ||
| } | ||
| }); | ||
| }, [form, onSubmit, toast]); |
There was a problem hiding this comment.
Replace try-catch with runAsynchronouslyWithAlert per coding guidelines.
The coding guidelines prohibit try-catch-all patterns. Use runAsynchronouslyWithAlert which handles errors automatically.
const handleFormSubmit = useCallback((e: React.FormEvent<HTMLFormElement>) => {
- runAsynchronously(async () => {
- setSubmitting(true);
- try {
- await form.handleSubmit(onSubmit)(e);
- } catch (error) {
- toast({
- title: 'Failed to save changes',
- description: error instanceof Error ? error.message : 'An error occurred',
- variant: 'destructive'
- });
- } finally {
- setSubmitting(false);
- }
- });
+ e.preventDefault();
+ setSubmitting(true);
+ runAsynchronouslyWithAlert(
+ form.handleSubmit(onSubmit)(e).finally(() => setSubmitting(false))
+ );
}, [form, onSubmit, toast]);Note: The guidelines state to "Never use toast for blocking alerts and errors; use alerts instead as they are more visible to users." runAsynchronouslyWithAlert uses alerts for errors.
📝 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 handleFormSubmit = useCallback((e: React.FormEvent<HTMLFormElement>) => { | |
| runAsynchronously(async () => { | |
| setSubmitting(true); | |
| try { | |
| await form.handleSubmit(onSubmit)(e); | |
| } catch (error) { | |
| toast({ | |
| title: 'Failed to save changes', | |
| description: error instanceof Error ? error.message : 'An error occurred', | |
| variant: 'destructive' | |
| }); | |
| } finally { | |
| setSubmitting(false); | |
| } | |
| }); | |
| }, [form, onSubmit, toast]); | |
| const handleFormSubmit = useCallback((e: React.FormEvent<HTMLFormElement>) => { | |
| e.preventDefault(); | |
| setSubmitting(true); | |
| runAsynchronouslyWithAlert( | |
| form.handleSubmit(onSubmit)(e).finally(() => setSubmitting(false)) | |
| ); | |
| }, [form, onSubmit]); |
🤖 Prompt for AI Agents
In apps/dashboard/src/components/settings.tsx around lines 281 to 296, the block
uses runAsynchronously with an explicit try-catch and toast error handling which
violates guidelines; replace this with runAsynchronouslyWithAlert so errors are
handled via alert automatically and remove the toast usage, while preserving the
submitting state semantics (setSubmitting(true) before the awaited call and
setSubmitting(false) in a finally-equivalent path after the call). Call
runAsynchronouslyWithAlert with an async function that awaits
form.handleSubmit(onSubmit)(e) and ensure any error path does not use toast but
relies on the wrapper to show alerts.
| }, [project]); | ||
|
|
||
| const handleFullLogoChange = useCallback(async (fullLogoUrl: string | null) => { | ||
| await project.update({ fullLogoUrl }); |
There was a problem hiding this comment.
Property name mismatch: the code uses fullLogoUrl in the update call, but the correct property name is logoFullUrl according to the API and type definitions throughout the codebase.
View Details
📝 Patch Details
diff --git a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/project-settings/page-client.tsx b/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/project-settings/page-client.tsx
index c0e8f56b..0f462b5e 100644
--- a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/project-settings/page-client.tsx
+++ b/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/project-settings/page-client.tsx
@@ -127,7 +127,7 @@ export default function PageClient() {
}, [project]);
const handleFullLogoChange = useCallback(async (fullLogoUrl: string | null) => {
- await project.update({ fullLogoUrl });
+ await project.update({ logoFullUrl: fullLogoUrl });
}, [project]);
// Memoize production mode change callback
@@ -218,7 +218,7 @@ export default function PageClient() {
<LogoUpload
label="Full Logo"
- value={project.fullLogoUrl}
+ value={project.logoFullUrl}
onValueChange={handleFullLogoChange}
description="Upload a full logo with text. Recommended size: At least 100px tall, landscape format"
type="full-logo"
Analysis
Property name mismatch: fullLogoUrl vs logoFullUrl in project settings
What fails: Full logo upload fails silently. The handleFullLogoChange() callback and logo value reading use the wrong property name fullLogoUrl instead of the correct logoFullUrl defined in the type system.
How to reproduce:
- Navigate to project settings page
- Upload a full logo using the "Full Logo" upload field
- The upload callback is called but fails to update the project because it attempts to set non-existent property
fullLogoUrlinstead oflogoFullUrl
Result: Full logo is uploaded but not saved to the project. Property access project.fullLogoUrl is undefined because the actual property on the AdminProject type is logoFullUrl.
Expected: Should use logoFullUrl throughout, consistent with:
- Type definition in
packages/template/src/lib/stack-app/projects/index.ts(line 58) - API mapping in
packages/template/src/lib/stack-app/apps/implementations/admin-app-impl.ts(line 130) - Backend schema in
packages/stack-shared/src/interface/crud/projects.ts - Related properties pattern:
logoUrl,logoDarkModeUrl,logoFullDarkModeUrl
Files affected:
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/project-settings/page-client.tsx- line 130 and line 221
| } | ||
| onClick?.(); | ||
| onClick?.(e); |
There was a problem hiding this comment.
| } | |
| onClick?.(); | |
| onClick?.(e); | |
| } else { | |
| onClick?.(e); | |
| } |
The onClick callback is being called twice when needConfirm is true, causing unintended duplicate invocations of click handlers.
View Details
Analysis
Duplicate onClick callback invocation in Link component with confirmation dialog
What fails: The Link component in apps/dashboard/src/components/link.tsx invokes the onClick callback twice when needConfirm is true, causing duplicate execution of click handlers.
How to reproduce:
- Render the Link component with
needConfirm=trueand anonClickhandler - Click the link
- The
onClickhandler executes twice - once on line 33 (inside the if block) and again on line 36 (unconditionally after the if block)
Expected behavior: The onClick callback should execute exactly once per click:
- When
needConfirmis true: execute inside the if block (before routing) - When
needConfirmis false: execute in the else block (during normal link handling)
Root cause: Line 36 had an unconditional onClick?.(e) call outside the if-else structure, causing it to execute regardless of the needConfirm condition. This resulted in duplicate invocations when needConfirm was true.
Impact: Any code using the Link component with an onClick handler experiences duplicate callback execution when needConfirm is true, potentially causing unintended side effects or duplicate operations.
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (2)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/globe.tsx (2)
133-133: Consider throttling tooltip position updates for better performance.Updating
tooltipPoson everymousemoveevent (line 157) triggers a re-render for each movement, which can be expensive. Consider throttling or using a ref-based approach for smoother performance.Example with throttling:
const throttledSetTooltipPos = useRef( throttle((x: number, y: number) => setTooltipPos({ x, y }), 16) // ~60fps ).current;Then in the
onMouseMovehandler:onMouseMove={(e) => { resumeRender(); - setTooltipPos({ x: e.clientX, y: e.clientY }); + throttledSetTooltipPos(e.clientX, e.clientY); }}
185-185: Clarify the purpose of the 100ms delay.The
setTimeoutdelay before settingisGlobeReadyappears arbitrary. Document why this delay is necessary or consider using the globe's built-in ready state if available.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/globe.tsx(3 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx}: Ensure code passespnpm typecheckfor TypeScript type validation
Never usetoastfor blocking alerts and errors; use alerts instead as they are more visible to users
Keep hover/click transitions snappy and fast; apply transitions after the action (e.g., smooth fade-out on hover end) rather than delaying actions with pre-transitions to avoid sluggish UI
Never use try-catch-all, never void a promise, never use .catch(console.error) or similar; userunAsynchronouslyorrunAsynchronouslyWithAlertfor error handling instead of try-catch blocks
Use loading indicators instead of try-catch blocks for asynchronous UI operations; button components like support async callbacks with built-in loading state
Use ES6 maps instead of records wherever possible
Files:
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/globe.tsx
apps/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Prefer client components and dynamic functions like
usePathnameover Next.js dynamic functions to keep pages static; avoid usingawait paramsor similar dynamic patterns
Files:
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/globe.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (12)
- GitHub Check: Vercel Agent Review
- GitHub Check: Cursor Bugbot
- GitHub Check: restart-dev-and-test-with-custom-base-port
- GitHub Check: check_prisma_migrations (22.x)
- GitHub Check: setup-tests
- GitHub Check: build (22.x)
- GitHub Check: restart-dev-and-test
- GitHub Check: all-good
- GitHub Check: build (22.x)
- GitHub Check: docker
- GitHub Check: build (22.x)
- GitHub Check: lint_and_build (latest)
🔇 Additional comments (3)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/globe.tsx (3)
42-46: Good simplification of sizing logic.The streamlined approach using container size with a sensible fallback improves readability.
310-325: Nice simplification of the loading indicator.The new loader is cleaner, more maintainable, and uses standard Tailwind animations effectively.
272-272: The z-[100] value is appropriate and justified; no changes needed.Verification shows the codebase maintains an intentional z-index hierarchy: z-[200] for dropdowns, z-[100] for the globe tooltip, and z-[9999] for critical overlays in stack-companion.tsx. The z-[100] value is neither "extremely high" nor causing stacking conflicts. As a non-interactive tooltip (
pointer-events-none), it appropriately sits above standard UI elements while below critical system overlays. This follows standard tooltip positioning practices.
| onGlobeReady={() => { | ||
| setTimeout(() => setIsGlobeReady(true), 100); | ||
| const current = globeRef.current; | ||
| if (!current) return; | ||
|
|
||
| const controls = current.controls(); | ||
| controls.autoRotate = true; | ||
| controls.autoRotateSpeed = 0.5; | ||
| controls.maxDistance = 1000; | ||
| controls.minDistance = 200; | ||
| controls.dampingFactor = 0.15; | ||
| controls.enableZoom = false; | ||
| controls.enableRotate = true; | ||
| current.camera().position.z = 420; | ||
| resumeRender(); | ||
| }} |
There was a problem hiding this comment.
🧩 Analysis chain
Verify controls.dampingFactor is a valid property.
The code sets controls.dampingFactor = 0.15 on line 194, but this property may not exist on the OrbitControls type. Verify this doesn't cause issues or silent failures.
🌐 Web query:
Does three.js OrbitControls support a dampingFactor property, or should it be enableDamping and dampingFactor used together?
💡 Result:
Yes — OrbitControls has a dampingFactor property, but it only takes effect when enableDamping is true. Set controls.enableDamping = true and then adjust controls.dampingFactor (and call controls.update() each frame). [1][2]
Sources:
[1] three.js OrbitControls docs.
[2] three.js examples / docs page (OrbitControls properties).
---
🏁 Script executed:
```shell
#!/bin/bash
# Check the globe.tsx file for enableDamping usage
rg -n "enableDamping" apps/dashboard/src/app/\(main\)/\(protected\)/projects/\[projectId\]/\(overview\)/globe.tsx
Length of output: 0
---
🏁 Script executed:
```shell
#!/bin/bash
# Find the globe.tsx file and check its contents around line 194
fd -e tsx globe.tsx | head -5
Length of output: 83
---
🏁 Script executed:
```shell
#!/bin/bash
# View the context around lines 184-199 in globe.tsx
sed -n '180,205p' 'apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/globe.tsx'
Length of output: 1115
Add controls.enableDamping = true for dampingFactor to take effect.
The dampingFactor property only takes effect when enableDamping is true. Line 194 sets controls.dampingFactor = 0.15, but controls.enableDamping is never enabled, causing the damping effect to be silently inactive. Add controls.enableDamping = true; before setting dampingFactor.
🤖 Prompt for AI Agents
In
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/globe.tsx
around lines 184 to 199, the code sets controls.dampingFactor but never enables
damping; add controls.enableDamping = true before setting controls.dampingFactor
so the dampingFactor takes effect, leaving the rest of the control configuration
unchanged.
| onPolygonHover={(d: any) => { | ||
| resumeRender(); | ||
| if (d) { | ||
| setPolygonSelectedCountry({ code: d.properties.ISO_A2_EH, name: d.properties.NAME }); | ||
| } else { | ||
| if (value === null && maxColorValue < 0.0001) { | ||
| // if there are no users at all, in dark mode, show the globe in a slightly lighter color | ||
| return `hsl(240, 84%, ${highlight ? '30%' : '20%'})`; | ||
| } else { | ||
| return `hsl(240, 84%, ${highlight ? '25%' : '15%'})`; | ||
| } | ||
| setPolygonSelectedCountry(null); | ||
| } | ||
| } | ||
| const scaled = value / maxColorValue; | ||
| if (theme === 'light') { | ||
| return `hsl(${175 * (1 - scaled)}, 100%, ${20 + 40 * scaled + (highlight ? 10 : 0)}%)`; | ||
| } else { | ||
| return `hsl(240, 84%, ${24 + 60 * scaled + (highlight ? 10 : 0)}%)`; | ||
| } | ||
| }; | ||
| const color = createColor(colorValues.get(country.properties.ISO_A2_EH) ?? null); | ||
| return color; | ||
| }} | ||
| onHexPolygonHover={(d: any) => { | ||
| resumeRender(); | ||
| if (d) { | ||
| setHexSelectedCountry({ code: d.properties.ISO_A2_EH, name: d.properties.NAME }); | ||
| } else { | ||
| setHexSelectedCountry(null); | ||
| } | ||
| }} | ||
| }} |
There was a problem hiding this comment.
🧩 Analysis chain
Replace any types with proper TypeScript types.
Multiple callback handlers use any for polygon/country data (lines 207, 219, 223, 240, 251), which bypasses type checking and could lead to runtime errors.
Check if react-globe.gl exports proper types for these parameters:
If types are available, update the callbacks:
- onPolygonHover={(d: any) => {
+ onPolygonHover={(d: GeoJsonFeature | null) => {Also applies to: 219-247, 251-257
🏁 Script executed:
#!/bin/bash
# Description: Check for type definitions in react-globe.gl
# Search for type exports in node_modules
fd -t f -e d.ts . node_modules/react-globe.gl --exec cat {} \; | rg -A 5 "onPolygonHover|onHexPolygonHover|onHexPolygonClick"Length of output: 114
🏁 Script executed:
#!/bin/bash
# Description: Check repository structure and package.json for react-globe.gl
# Check if we're in the right directory and look for package.json
echo "=== Checking for package.json files ==="
find . -maxdepth 3 -name "package.json" -type f | head -10
echo -e "\n=== Checking repository root structure ==="
ls -la | head -20
echo -e "\n=== Checking if node_modules exists ==="
if [ -d "node_modules" ]; then
echo "node_modules exists"
echo "react-globe.gl location:"
ls -la node_modules/ | grep -i globe || echo "Not found in direct listing"
else
echo "node_modules does not exist"
fi
echo -e "\n=== Checking the actual globe.tsx file ==="
if [ -f "apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/globe.tsx" ]; then
echo "File found. Showing lines 200-220:"
sed -n '200,220p' "apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/globe.tsx"
else
echo "File not found at expected path"
fi
echo -e "\n=== Checking package.json for react-globe.gl ==="
rg -A 2 -B 2 "react-globe" package.json --no-heading 2>/dev/null || echo "Not found in root package.json"Length of output: 2701
---
🏁 Script executed:
```shell
#!/bin/bash
# Description: Check dashboard package.json and examine globe.tsx for type info
echo "=== Dashboard package.json react-globe.gl entry ==="
rg -A 2 -B 2 "react-globe" apps/dashboard/package.json || echo "Check failed"
echo -e "\n=== Checking for import statements and types in globe.tsx ==="
head -50 "apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/globe.tsx" | rg -A 3 "import|type |interface"
echo -e "\n=== Checking full callback signatures in globe.tsx ==="
sed -n '1,50p' "apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/globe.tsx"
Length of output: 3966
🌐 Web query:
react-globe.gl TypeScript type definitions onPolygonHover callback
💡 Result:
Signature (JS): onPolygonHover(polygon, prevPolygon)
TypeScript: type OnPolygonHover = (polygon: T | null, prevPolygon: T | null) => void
Usage example (assuming your polygon item type is MyPolygon or a GeoJSON Feature):
- interface MyPolygon { id: string; geometry: any; name?: string }
- const handlePolygonHover: OnPolygonHover = (polygon, prev) => { /* ... */ }
References: react-globe.gl docs (onPolygonHover signature). [1][2]
Sources
[1] react-globe.gl README (onPolygonHover description). [2] globe.gl / react-globe.gl props docs (polygons layer).
🏁 Script executed:
#!/bin/bash
# Description: Extract and verify all callback signatures with `any` types
echo "=== Extracting callback lines mentioned in review ==="
echo "Line 207 (onPolygonHover):"
sed -n '207p' "apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/globe.tsx"
echo -e "\nLine 219 area (hexPolygonAltitude - context 216-222):"
sed -n '216,222p' "apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/globe.tsx"
echo -e "\nLine 223 area (likely onHexPolygonHover - context 223-230):"
sed -n '223,230p' "apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/globe.tsx"
echo -e "\nLine 240 area (context 238-245):"
sed -n '238,245p' "apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/globe.tsx"
echo -e "\nLine 251 area (context 249-257):"
sed -n '249,257p' "apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/globe.tsx"
echo -e "\n=== Summary: All lines with 'any' type in callbacks ==="
rg -n "(: any)" "apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/globe.tsx"Length of output: 2467
Replace any types with proper TypeScript types in callback handlers.
The library exports generic type definitions for these callbacks. Five instances use any which bypasses type safety:
- Line 207:
onPolygonHover - Line 219:
hexPolygonAltitude - Line 223:
hexPolygonColor - Line 240:
onHexPolygonHover - Line 251:
onHexPolygonClick
All callbacks access GeoJSON Feature properties (.properties.ISO_A2_EH, .properties.NAME), so import and use proper types from react-globe.gl or define a GeoJSON Feature interface. This aligns with the coding guideline requiring TypeScript type validation via pnpm typecheck.
🤖 Prompt for AI Agents
In
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/globe.tsx
around lines 207–214 and the other mentioned callbacks (lines ~219, ~223, ~240,
~251), replace the use of `any` in the callback parameter types with a proper
GeoJSON/feature type: import the appropriate types from react-globe.gl if
available (e.g., the library's Feature/Polygon/HexPolygon generic types) or
define a local GeoJSON Feature<T> interface that exposes properties.ISO_A2_EH
and properties.NAME, then annotate each handler parameter with that type and
adjust any null checks accordingly so TypeScript can validate access to
.properties.ISO_A2_EH and .properties.NAME (ensure corresponding imports/exports
are added at the top of the file).
There was a problem hiding this comment.
This PR is being reviewed by Cursor Bugbot
Details
You are on the Bugbot Free tier. On this plan, Bugbot will review limited PRs each billing cycle.
To receive Bugbot reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.
| await project.update({ logoFullUrl }); | ||
| }} | ||
| value={project.fullLogoUrl} | ||
| onValueChange={handleFullLogoChange} |
There was a problem hiding this comment.
Bug: Incorrect property name for full logo URL
The property name was changed from logoFullUrl to fullLogoUrl, but this doesn't match the actual project property naming convention. Other logo properties in this file follow the pattern logoUrl, logoDarkModeUrl, logoFullDarkModeUrl (all starting with "logo"). The code-editor.tsx type definition also shows the correct name is logoFullUrl. Reading project.fullLogoUrl will return undefined and updating with { fullLogoUrl } won't affect the correct field, breaking the Full Logo upload functionality.
Additional Locations (1)
| publishableClientKey: newKey.publishableClientKey!, | ||
| secretServerKey: newKey.secretServerKey!, | ||
| }); | ||
| setIsGenerating(false); |
There was a problem hiding this comment.
Bug: Missing error handling leaves button permanently disabled
The handleGenerateKeys function had its try/catch/finally block removed. If createInternalApiKey throws an error, setIsGenerating(false) won't be called, leaving the "Generate keys" button permanently disabled. Additionally, setError is never called so the error alert UI (which still exists in StepCard) will never display feedback to the user. The old code properly handled errors and always reset the loading state in a finally block.
| } finally { | ||
| setIsGenerating(false); | ||
| } | ||
| const newKey = await adminApp.createInternalApiKey({ |
There was a problem hiding this comment.
Missing error handling: The try-catch-finally block was removed from handleGenerateKeys, causing the isGenerating flag to never be reset if an error occurs during API key generation.
View Details
📝 Patch Details
diff --git a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/vercel/page-client.tsx b/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/vercel/page-client.tsx
index ad7e5bfc..08c6122d 100644
--- a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/vercel/page-client.tsx
+++ b/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/vercel/page-client.tsx
@@ -77,20 +77,26 @@ export default function PageClient() {
setIsGenerating(true);
setError(null);
- const newKey = await adminApp.createInternalApiKey({
- hasPublishableClientKey: true,
- hasSecretServerKey: true,
- hasSuperSecretAdminKey: false,
- expiresAt: new Date(Date.now() + TWO_HUNDRED_YEARS_IN_MS),
- description: "Vercel Integration",
- });
-
- setKeys({
- projectId: adminApp.projectId,
- publishableClientKey: newKey.publishableClientKey!,
- secretServerKey: newKey.secretServerKey!,
- });
- setIsGenerating(false);
+ try {
+ const newKey = await adminApp.createInternalApiKey({
+ hasPublishableClientKey: true,
+ hasSecretServerKey: true,
+ hasSuperSecretAdminKey: false,
+ expiresAt: new Date(Date.now() + TWO_HUNDRED_YEARS_IN_MS),
+ description: "Vercel Integration",
+ });
+
+ setKeys({
+ projectId: adminApp.projectId,
+ publishableClientKey: newKey.publishableClientKey!,
+ secretServerKey: newKey.secretServerKey!,
+ });
+ } catch (err) {
+ console.error(err);
+ setError(err instanceof Error ? err.message : "Failed to generate API keys. Please try again.");
+ } finally {
+ setIsGenerating(false);
+ }
};
const toggleStepCompletion = (id: StepId) => {
Analysis
Missing error handling in handleGenerateKeys leaves UI in loading state on API failure
What fails: The handleGenerateKeys function in apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/vercel/page-client.tsx lacks a try-catch block. When adminApp.createInternalApiKey() throws an error, the promise rejection is unhandled, causing setIsGenerating(false) to never execute.
How to reproduce:
- Navigate to the Vercel integration setup page
- Click "Generate keys" button
- If the API call fails (network error, server error, permissions issue, etc.), the button stays in the "Generating..." disabled state permanently
- The error is never displayed to the user
- User cannot retry the operation
Result: The "Generate keys" button becomes permanently disabled and displays "Generating..." indefinitely when an error occurs. The component is stuck and unusable.
Expected: When the API call fails, the error should be caught, displayed to the user via the error Alert component (line 590), and isGenerating should be reset to false to re-enable the button for retry.
Why it's a bug: The original implementation (commit 0540f39) included try-catch-finally error handling. This was removed in a recent change, breaking error recovery. Without the try-catch, execution stops at the failed await, and the finally block (which resets setIsGenerating(false)) never runs, leaving the UI in an incomplete state.
Fix applied: Restored the try-catch-finally block to properly handle errors and ensure setIsGenerating(false) always runs via the finally clause.
197c365 to
812814d
Compare
https://www.loom.com/share/c725fbfe9dd84e649d6d55d7a148348b
Summary by CodeRabbit
Documentation
New Features
Improvements
✏️ Tip: You can customize this high-level summary in your review settings.
Note
Redesigns the sidebar and dashboard with widgetized metrics and improved charts, adds a guided Vercel setup flow, enhances the apps store/enablement and project settings, and introduces a design guide.
apps/dashboard/DESIGN_GUIDE.mdfor patterns, dark mode, accessibility, and component conventions.TabbedMetricsCard,LineChartDisplay,DonutChartDisplay, custom tooltips, weekend styling, lists with user actions.AppStoreEntry.AppSquareenable/disable with alpha/beta warning modal; newAppWarningModalcomponent.SettingCopyableTextandCopyableText; form/UX improvements; transfer flow tweaks.CopyableText;LinkonClick signature; apps-frontend typing/asset tweaks; assorted UI polish and accessibility.Written by Cursor Bugbot for commit 197c365. This will update automatically on new commits. Configure here.