@@ -26,12 +26,47 @@ import { StyleInsights } from '../StyleInsights'
2626import { ImpactAnalyzer } from '../ImpactAnalyzer'
2727import { DashboardStats } from './DashboardStats'
2828import { IndexingProgressModal } from '../IndexingProgressModal'
29+ import { UpgradeLimitModal } from '../UpgradeLimitModal'
2930import type { Repository } from '../../types'
3031import type { GitHubRepo } from '../../hooks/useGitHubRepos'
3132import { API_URL } from '../../config/api'
3233
3334const MAX_FREE_REPOS = 3
3435
36+ // Safe stringify that won't crash on circular refs
37+ function safeStringify ( obj : unknown , maxLen = 200 ) : string {
38+ try {
39+ return JSON . stringify ( obj ) . slice ( 0 , maxLen )
40+ } catch {
41+ return String ( obj ) . slice ( 0 , maxLen )
42+ }
43+ }
44+
45+ // Extract error message from API response (handles nested detail objects)
46+ function extractErrorMessage ( err : any , fallback : string ) : string {
47+ // FastAPI wraps in detail, but handle both cases
48+ const detail = err ?. detail || err
49+
50+ if ( typeof detail === 'string' ) return detail
51+ if ( typeof detail ?. message === 'string' ) return detail . message
52+ if ( typeof err ?. message === 'string' ) return err . message
53+
54+ // Last resort: stringify (but keep it short, safe from circular refs)
55+ if ( detail && typeof detail === 'object' ) {
56+ const msg = detail . message || detail . error
57+ if ( msg ) return String ( msg )
58+ return safeStringify ( detail )
59+ }
60+ return fallback
61+ }
62+
63+ // Check if error is a limit/upgrade error (handles both wrapped and unwrapped)
64+ function isUpgradeError ( err : any ) : boolean {
65+ const detail = err ?. detail || err
66+ const code = detail ?. error || detail ?. error_code
67+ return [ 'REPO_TOO_LARGE' , 'REPO_LIMIT_REACHED' ] . includes ( code )
68+ }
69+
3570type RepoTab = 'overview' | 'search' | 'dependencies' | 'insights' | 'impact'
3671
3772export function DashboardHome ( ) {
@@ -49,6 +84,14 @@ export function DashboardHome() {
4984 const [ indexingRepoId , setIndexingRepoId ] = useState < string | null > ( null )
5085 const [ indexingRepoName , setIndexingRepoName ] = useState < string > ( '' )
5186 const [ showIndexingModal , setShowIndexingModal ] = useState ( false )
87+
88+ // Upgrade prompt modal state
89+ const [ upgradeModal , setUpgradeModal ] = useState < { show : boolean ; message : string ; repoName ?: string } > ( { show : false , message : '' } )
90+
91+ // Helper to show upgrade modal with context
92+ const showUpgradeModal = ( err : any , repoName ?: string ) => {
93+ setUpgradeModal ( { show : true , message : extractErrorMessage ( err , 'Repository exceeds free tier limits' ) , repoName } )
94+ }
5295
5396 // Auto-open GitHub import modal if redirected from OAuth callback
5497 useEffect ( ( ) => {
@@ -96,7 +139,11 @@ export function DashboardHome() {
96139
97140 if ( ! response . ok ) {
98141 const err = await response . json ( ) . catch ( ( ) => ( { } ) )
99- throw new Error ( err . detail || 'Failed to add repository' )
142+ if ( isUpgradeError ( err ) ) {
143+ showUpgradeModal ( err , name )
144+ return
145+ }
146+ throw new Error ( extractErrorMessage ( err , 'Failed to add repository' ) )
100147 }
101148
102149 const data = await response . json ( )
@@ -110,7 +157,11 @@ export function DashboardHome() {
110157
111158 if ( ! indexResponse . ok ) {
112159 const err = await indexResponse . json ( ) . catch ( ( ) => ( { } ) )
113- throw new Error ( err . detail ?. message || err . detail || 'Failed to start indexing' )
160+ if ( isUpgradeError ( err ) ) {
161+ showUpgradeModal ( err , name )
162+ return
163+ }
164+ throw new Error ( extractErrorMessage ( err , 'Failed to start indexing' ) )
114165 }
115166
116167 // Show indexing progress modal
@@ -150,7 +201,11 @@ export function DashboardHome() {
150201
151202 if ( ! response . ok ) {
152203 const err = await response . json ( ) . catch ( ( ) => ( { } ) )
153- throw new Error ( err . detail || `Failed to add ${ repo . name } ` )
204+ if ( isUpgradeError ( err ) ) {
205+ showUpgradeModal ( err , repo . name )
206+ continue
207+ }
208+ throw new Error ( extractErrorMessage ( err , `Failed to add ${ repo . name } ` ) )
154209 }
155210
156211 const data = await response . json ( )
@@ -164,7 +219,11 @@ export function DashboardHome() {
164219
165220 if ( ! indexResponse . ok ) {
166221 const err = await indexResponse . json ( ) . catch ( ( ) => ( { } ) )
167- const errMsg = err . detail ?. message || err . detail || 'Indexing failed to start'
222+ if ( isUpgradeError ( err ) ) {
223+ showUpgradeModal ( err , repo . name )
224+ continue
225+ }
226+ const errMsg = extractErrorMessage ( err , 'Indexing failed to start' )
168227 console . error ( `Failed to start indexing for ${ repo . name } :` , err )
169228 toast . warning ( `${ repo . name } added but indexing failed` , {
170229 description : errMsg
@@ -439,6 +498,14 @@ export function DashboardHome() {
439498 maxSelectable = { MAX_FREE_REPOS }
440499 currentRepoCount = { repos . length }
441500 />
501+
502+ { /* Upgrade Limit Modal */ }
503+ < UpgradeLimitModal
504+ isOpen = { upgradeModal . show }
505+ onClose = { ( ) => setUpgradeModal ( { show : false , message : '' } ) }
506+ errorMessage = { upgradeModal . message }
507+ repoName = { upgradeModal . repoName }
508+ />
442509 </ div >
443510 )
444511}
0 commit comments