11import React , { useState , useEffect , useMemo } from 'react'
22import { CloudDownload , Upload , Package , Database , RefreshCw , Trash2 , Loader2 , AlertTriangle , HardDrive , Usb , ChevronDown , Globe } from 'lucide-react'
33import { QRCodeSVG } from 'qrcode.react'
4- import { cn , isPS5 , isSystemPayload } from '../../utils/helpers'
4+ import { cn , isPS5 , parsePayloadName } from '../../utils/helpers'
55import PayloadName from '../ui/PayloadName'
66
77const StorageHub = ( { payloads, payloadMeta, onInstall, onDelete, onUpload, onImportFromUsb, config, ip, scrollTarget, onClearScrollTarget } ) => {
@@ -30,13 +30,14 @@ const StorageHub = ({ payloads, payloadMeta, onInstall, onDelete, onUpload, onIm
3030
3131 // Legacy single-source: auto-refresh if older than 24h
3232 if ( ! force && data ?. last_update ) {
33+ // eslint-disable-next-line react-hooks/purity
3334 const now = Math . floor ( Date . now ( ) / 1000 )
3435 if ( now - Number ( data . last_update ) > 24 * 60 * 60 ) {
3536 await fetchRemote ( true )
3637 return
3738 }
3839 }
39- } catch ( e ) {
40+ } catch {
4041 setError ( true )
4142 } finally {
4243 setLoading ( false )
@@ -134,7 +135,7 @@ const StorageHub = ({ payloads, payloadMeta, onInstall, onDelete, onUpload, onIm
134135 </ span >
135136 </ div >
136137
137- < div className = { cn ( "grid gap-4" , isPS5 ? "grid-cols-2" : "grid-cols-1 md :grid-cols-2" ) } >
138+ < div className = { cn ( "grid gap-4" , isPS5 ? "grid-cols-2" : "grid-cols-1 lg :grid-cols-2" ) } >
138139 { internalPayloads . length === 0 ? (
139140 < div className = "col-span-full py-20 border-2 border-dashed border-white/5 rounded-ps-3xl flex flex-col items-center justify-center space-y-4 bg-white/[0.01]" >
140141 < Package className = "w-16 h-16 text-white/5" />
@@ -149,46 +150,43 @@ const StorageHub = ({ payloads, payloadMeta, onInstall, onDelete, onUpload, onIm
149150 ? enrichedSources . flatMap ( s => s . payloads )
150151 : remotePayloads
151152 const remoteMatch = allRemote . find ( rp => rp . filename === fileName || rp . installedFilename === fileName )
153+ const remoteVersion = remoteMatch ?. filename ? parsePayloadName ( remoteMatch . filename ) . version : null
152154 return (
153- < div key = { path } className = { cn (
154- "group flex justify-between p-4 md:p-6 glass-card rounded-ps-2xl border-white/10 hover:border-ps-blue/30 gap-4 relative overflow-hidden" ,
155- isPS5 ? "flex-row items-center" : "flex-col md:flex-row md:items-center"
156- ) } >
157- < div className = "flex items-center space-x-4 md:space-x-6 min-w-0" >
158- < div className = "p-3 md:p-4 bg-white/5 rounded-2xl group-hover:bg-ps-blue/10 transition-colors shrink-0" >
159- < Package className = "w-6 h-6 md:w-8 md:h-8 text-zinc-400 group-hover:text-ps-blue transition-colors" />
160- </ div >
161- < div className = "min-w-0 flex-1" >
162- < PayloadName path = { fileName } className = "text-xl md:text-2xl text-white" stacked />
163- { /* Source badge — floats in bottom-right, doesn't expand the row */ }
164- { sourceBadge && (
165- < div className = "absolute bottom-2 right-3 flex items-center gap-1 z-10 pointer-events-none" >
166- < Globe className = "w-3 h-3 text-zinc-500 shrink-0" />
167- < span className = "text-[11px] text-zinc-400 font-medium truncate max-w-[160px] select-none" >
168- { sourceBadge }
169- </ span >
170- </ div >
171- ) }
155+ < div key = { path } className = "group flex flex-col p-4 md:p-6 glass-card rounded-ps-2xl border-white/10 hover:border-ps-blue/30 gap-3 md:gap-4 relative overflow-hidden" >
156+ < div className = "flex flex-row items-center justify-between w-full gap-4" >
157+ < div className = "flex items-center space-x-4 md:space-x-6 min-w-0 flex-1" >
158+ < div className = "p-3 md:p-4 bg-white/5 rounded-2xl group-hover:bg-ps-blue/10 transition-colors shrink-0" >
159+ < Package className = "w-6 h-6 md:w-8 md:h-8 text-zinc-400 group-hover:text-ps-blue transition-colors" />
160+ </ div >
161+ < div className = "min-w-0 flex-1 space-y-1" >
162+ < PayloadName path = { fileName } className = "text-xl md:text-2xl text-white" stacked />
163+ { sourceBadge && (
164+ < div className = "flex items-center gap-1 text-zinc-500 text-[11px] select-none font-medium" >
165+ < Globe className = "w-3.5 h-3.5" />
166+ < span > { sourceBadge } </ span >
167+ </ div >
168+ ) }
169+ </ div >
172170 </ div >
173- </ div >
174- < div className = "flex items-center space-x-3 md:space-x-4 ml-auto md:ml-0" >
175- { remoteMatch ?. isUpdate && (
171+ < div className = "flex items-center shrink-0" >
176172 < button
177- onClick = { ( ) => onInstall ( remoteMatch , remoteMatch . source_id , legacyRepoUrl ) }
178- className = "flex items-center space-x-2 md:space-x-3 px-4 md:px-6 py-2 md:py-3 bg-emerald-600 hover:bg-emerald-500 text-white rounded-xl font-bold text-xs md:text-sm transition-all"
173+ onClick = { ( ) => onDelete ( fileName ) }
174+ className = "p-3 md:p-4 rounded-xl bg-red-950/20 text-red-500 border border-red-500/10 hover:bg-red-500 hover:text-white transition-all"
175+ title = "Remove Payload"
179176 >
180- < RefreshCw className = "w-4 h-4 md:w-5 md:h-5" />
181- < span > Update</ span >
177+ < Trash2 className = "w-5 h-5 md:w-6 md:h-6" />
182178 </ button >
183- ) }
179+ </ div >
180+ </ div >
181+ { remoteMatch ?. isUpdate && (
184182 < button
185- onClick = { ( ) => onDelete ( fileName ) }
186- className = "p-3 md:p-4 rounded-xl bg-red-950/20 text-red-500 border border-red-500/10 hover:bg-red-500 hover:text-white transition-all"
187- title = "Remove Payload"
183+ onClick = { ( ) => onInstall ( remoteMatch , remoteMatch . source_id , legacyRepoUrl ) }
184+ className = "w-full flex items-center justify-center space-x-2 py-2 bg-emerald-600 hover:bg-emerald-500 text-white rounded-xl font-bold text-xs md:text-sm transition-all"
188185 >
189- < Trash2 className = "w-5 h-5 md:w-6 md:h-6" />
186+ < RefreshCw className = "w-4 h-4 md:w-5 md:h-5" />
187+ < span > Update{ remoteVersion ? ` (to v${ remoteVersion . replace ( / ^ v / i, '' ) } )` : '' } </ span >
190188 </ button >
191- </ div >
189+ ) }
192190 </ div >
193191 )
194192 } )
@@ -363,7 +361,7 @@ const StorageHub = ({ payloads, payloadMeta, onInstall, onDelete, onUpload, onIm
363361 </ span >
364362 </ div >
365363
366- < div className = { cn ( "grid gap-4" , isPS5 ? "grid-cols-2" : "grid-cols-1 md :grid-cols-2" ) } >
364+ < div className = { cn ( "grid gap-4" , isPS5 ? "grid-cols-2" : "grid-cols-1 lg :grid-cols-2" ) } >
367365 { payloads . filter ( p => p . includes ( '/mnt/usb' ) ) . length === 0 ? (
368366 < div className = "col-span-full py-20 border-2 border-dashed border-white/5 rounded-ps-3xl flex flex-col items-center justify-center space-y-6 bg-white/[0.01]" >
369367 < div className = "relative" >
@@ -385,13 +383,9 @@ const StorageHub = ({ payloads, payloadMeta, onInstall, onDelete, onUpload, onIm
385383 </ div >
386384 </ div >
387385 ) : (
388- payloads . filter ( p => p . includes ( '/mnt/usb' ) ) . map ( ( path , i ) => {
389- const fileName = path . split ( '/' ) . pop ( )
386+ payloads . filter ( p => p . includes ( '/mnt/usb' ) ) . map ( ( path ) => {
390387 return (
391- < div key = { path } className = { cn (
392- "group flex justify-between p-4 md:p-6 glass-card rounded-ps-2xl border-white/10 hover:border-ps-blue/30 gap-4" ,
393- isPS5 ? "flex-row items-center" : "flex-col md:flex-row md:items-center"
394- ) } >
388+ < div key = { path } className = "group flex flex-row items-center justify-between p-4 md:p-6 glass-card rounded-ps-2xl border-white/10 hover:border-ps-blue/30 gap-4" >
395389 < div className = "flex items-center space-x-4 md:space-x-6 min-w-0" >
396390 < div className = "p-3 md:p-4 bg-white/5 rounded-2xl group-hover:bg-ps-blue/10 transition-colors shrink-0" >
397391 < Usb className = "w-6 h-6 md:w-8 md:h-8 text-zinc-400 group-hover:text-ps-blue transition-colors" />
@@ -401,7 +395,7 @@ const StorageHub = ({ payloads, payloadMeta, onInstall, onDelete, onUpload, onIm
401395 < p className = "text-[10px] text-zinc-600 font-medium font-mono uppercase tracking-tighter opacity-60 truncate" > { path } </ p >
402396 </ div >
403397 </ div >
404- < div className = "flex items-center ml-auto md:ml -0" >
398+ < div className = "flex items-center shrink -0" >
405399 < button
406400 onClick = { ( ) => onImportFromUsb ( path ) }
407401 className = "flex items-center space-x-2 md:space-x-3 px-4 md:px-6 py-3 md:py-4 bg-white/5 hover:bg-ps-blue text-white rounded-xl font-bold text-xs md:text-sm transition-all border border-white/10 hover:border-ps-blue group/btn"
0 commit comments