@@ -37,6 +37,12 @@ import { formatDateShort } from "@/lib/format";
3737import { parseRange , filterByDateRange } from "@/lib/analytics-range" ;
3838import { usePersistedRange } from "@/lib/hooks/use-persisted-range" ;
3939import type { AnalyticsData } from "@/lib/asc/analytics" ;
40+ import {
41+ PLATFORM_LABELS ,
42+ STATE_DOT_COLORS ,
43+ stateLabel ,
44+ type AscVersion ,
45+ } from "@/lib/asc/version-types" ;
4046import { ReportInitiatedBanner } from "@/components/report-initiated-banner" ;
4147
4248const CHART_COLORS = [
@@ -61,6 +67,7 @@ export default function DashboardPage() {
6167 const { apps, loading, truncated, needsAppSelection, refresh : refreshApps } = useApps ( ) ;
6268 const devSimulate = searchParams . get ( "analyticsState" ) === "initiated" ;
6369 const [ analytics , setAnalytics ] = useState < Record < string , AppAnalytics > > ( { } ) ;
70+ const [ appVersions , setAppVersions ] = useState < Record < string , AscVersion [ ] > > ( { } ) ;
6471 const [ range , setRange ] = usePersistedRange ( "range:portfolio-proceeds" ) ;
6572
6673 // entry=1 means proxy redirected here on app launch – restore last URL
@@ -147,6 +154,16 @@ export default function DashboardPage() {
147154 }
148155 } , [ ] ) ;
149156
157+ const fetchVersions = useCallback ( async ( appId : string ) => {
158+ try {
159+ const res = await fetch ( `/api/apps/${ appId } /versions` ) ;
160+ const json = await res . json ( ) ;
161+ setAppVersions ( ( prev ) => ( { ...prev , [ appId ] : json . versions ?? [ ] } ) ) ;
162+ } catch {
163+ // Non-critical
164+ }
165+ } , [ ] ) ;
166+
150167 useEffect ( ( ) => {
151168 if ( loading || apps . length === 0 || needsAppSelection ) return ;
152169
@@ -158,8 +175,9 @@ export default function DashboardPage() {
158175
159176 for ( const app of apps ) {
160177 fetchAnalytics ( app . id ) ;
178+ fetchVersions ( app . id ) ;
161179 }
162- } , [ apps , loading , needsAppSelection , fetchAnalytics ] ) ;
180+ } , [ apps , loading , needsAppSelection , fetchAnalytics , fetchVersions ] ) ;
163181
164182 // Poll while any app is pending
165183 const hasPending = Object . values ( analytics ) . some ( ( a ) => a . pending ) ;
@@ -187,11 +205,13 @@ export default function DashboardPage() {
187205 fetch ( `/api/apps/${ app . id } /analytics/refresh` , { method : "POST" } ) ,
188206 ) ,
189207 ) ;
190- await Promise . all ( currentApps . map ( ( app ) => fetchAnalytics ( app . id ) ) ) ;
208+ await Promise . all (
209+ currentApps . flatMap ( ( app ) => [ fetchAnalytics ( app . id ) , fetchVersions ( app . id ) ] ) ,
210+ ) ;
191211 } finally {
192212 setRefreshing ( false ) ;
193213 }
194- } , [ fetchAnalytics ] ) ;
214+ } , [ fetchAnalytics , fetchVersions ] ) ;
195215
196216 useRegisterRefresh ( {
197217 onRefresh : handleRefresh ,
@@ -496,6 +516,7 @@ export default function DashboardPage() {
496516 < div className = "grid gap-4 sm:grid-cols-2 lg:grid-cols-3" >
497517 { apps . map ( ( app ) => {
498518 const entry = analytics [ app . id ] ;
519+ const versions = pickPendingVersions ( appVersions [ app . id ] ?? [ ] ) ;
499520 return (
500521 < Link key = { app . id } href = { `/dashboard/apps/${ app . id } ` } >
501522 < Card className = "h-full transition-colors hover:bg-muted/50" >
@@ -517,6 +538,20 @@ export default function DashboardPage() {
517538 ) : (
518539 < p className = "text-xs text-muted-foreground" > No data</ p >
519540 ) }
541+ { versions . length > 0 && (
542+ < div className = "mt-3 space-y-0.5 border-t pt-3" >
543+ { versions . map ( ( v ) => (
544+ < div key = { v . id } className = "flex items-center gap-1.5 text-xs text-muted-foreground" >
545+ < span className = { `size-1.5 shrink-0 rounded-full ${ STATE_DOT_COLORS [ v . attributes . appVersionState ] ?? "bg-muted-foreground" } ` } />
546+ < span className = "truncate" >
547+ { PLATFORM_LABELS [ v . attributes . platform ] ?? v . attributes . platform } { " " }
548+ { v . attributes . versionString } { " " }
549+ { stateLabel ( v . attributes . appVersionState ) }
550+ </ span >
551+ </ div >
552+ ) ) }
553+ </ div >
554+ ) }
520555 </ CardContent >
521556 </ Card >
522557 </ Link >
@@ -537,6 +572,22 @@ export default function DashboardPage() {
537572 ) ;
538573}
539574
575+ const LIVE_STATES = new Set ( [ "READY_FOR_SALE" , "READY_FOR_DISTRIBUTION" ] ) ;
576+
577+ /** Pick non-live versions to show in portfolio cards (newest per platform). */
578+ function pickPendingVersions ( versions : AscVersion [ ] ) : AscVersion [ ] {
579+ const seen = new Set < string > ( ) ;
580+ const result : AscVersion [ ] = [ ] ;
581+ for ( const v of versions ) {
582+ if ( LIVE_STATES . has ( v . attributes . appVersionState ) ) continue ;
583+ const p = v . attributes . platform ;
584+ if ( seen . has ( p ) ) continue ;
585+ seen . add ( p ) ;
586+ result . push ( v ) ;
587+ }
588+ return result ;
589+ }
590+
540591function AppCardStats ( { data } : { data : AnalyticsData } ) {
541592 const downloads = data . dailyDownloads . reduce (
542593 ( sum , d ) => sum + d . firstTime + d . redownload ,
0 commit comments