@@ -65,7 +65,29 @@ export default function InvoiceDetailPage({ params }: Props) {
6565 const [ showDuplicateModal , setShowDuplicateModal ] = useState ( false ) ;
6666 const [ paymentError , setPaymentError ] = useState < string | null > ( null ) ;
6767 const [ paymentMethod , setPaymentMethod ] = useState < "freighter" | "walletconnect" > ( "freighter" ) ;
68- const [ payerNonce , setPayerNonce ] = useState < bigint | null > ( null ) ;
68+ const [ amountLocked , setAmountLocked ] = useState ( false ) ;
69+ const prevPayAmountRef = useRef ( "" ) ;
70+ const [ cooldownExpiresAt , setCooldownExpiresAt ] = useState < number | null > ( null ) ;
71+ const [ activeDetailsTab , setActiveDetailsTab ] = useState < "audit" | "history" | "notes" > ( "audit" ) ;
72+
73+ const prevStatusRef = useRef < string | null > ( null ) ;
74+ const timelineRef = useRef < HTMLDivElement > ( null ) ;
75+ const [ exportingTimeline , setExportingTimeline ] = useState ( false ) ;
76+
77+ useEffect ( ( ) => {
78+ setNotifySubscribed ( isSubscribedToInvoice ( id ) ) ;
79+ } , [ id ] ) ;
80+
81+ const handleNotifyMe = async ( ) => {
82+ const permission = await requestNotificationPermission ( ) ;
83+ if ( permission !== "granted" ) {
84+ setNotifyDenied ( true ) ;
85+ return ;
86+ }
87+ subscribeToInvoice ( id ) ;
88+ setNotifySubscribed ( true ) ;
89+ setNotifyDenied ( false ) ;
90+ } ;
6991
7092 const load = async ( ) => {
7193 const inv = await splitClient . getInvoice ( id ) ;
@@ -581,50 +603,36 @@ export default function InvoiceDetailPage({ params }: Props) {
581603 />
582604 ) }
583605
584- { /* Token & Contract Info */ }
606+ { /* Tabbed detail section: Audit Log / History / Notes */ }
585607 < section className = "mb-8" >
586- < h2 className = "text-lg font-semibold text-white mb-3" > Details</ h2 >
587- < div className = "bg-gray-800/40 border border-gray-700 rounded-xl divide-y divide-gray-700" >
588- < div className = "flex justify-between items-center px-4 py-3" >
589- < span className = "text-sm text-gray-400" > Invoice ID</ span >
590- < span className = "text-sm font-mono text-gray-200" > #{ id } </ span >
591- </ div >
592- < div className = "flex justify-between items-center px-4 py-3" >
593- < span className = "text-sm text-gray-400" > Token</ span >
594- < span className = "text-sm font-mono text-gray-200 truncate ml-4" title = { invoice . token } >
595- { truncateAddress ( invoice . token , 8 ) }
596- </ span >
597- </ div >
598- < div className = "flex justify-between items-center px-4 py-3" >
599- < span className = "text-sm text-gray-400" > Deadline</ span >
600- < span className = "text-sm text-gray-200" >
601- { invoice . deadline > 0
602- ? new Date ( invoice . deadline * 1000 ) . toLocaleString ( )
603- : "No deadline" }
604- </ span >
605- </ div >
606- < div className = "flex justify-between items-center px-4 py-3" >
607- < span className = "text-sm text-gray-400" > Total Amount</ span >
608- < span className = "text-sm font-semibold text-indigo-300" > { formatAmount ( total ) } USDC</ span >
609- </ div >
610- < div className = "flex justify-between items-center px-4 py-3" >
611- < span className = "text-sm text-gray-400" > Funded</ span >
612- < span className = "text-sm font-medium text-green-400" > { formatAmount ( invoice . funded ) } USDC</ span >
613- </ div >
614- < div className = "flex justify-between items-center px-4 py-3" >
615- < span className = "text-sm text-gray-400" > Status</ span >
616- < span className = { `text-sm font-medium ${ status . color . replace ( "bg-" , "text-" ) } ` } >
617- { invoice . status }
618- </ span >
619- </ div >
620- { payerNonce !== null && (
621- < div className = "flex justify-between items-center px-4 py-3" >
622- < span className = "text-sm text-gray-400" > Your Nonce</ span >
623- < span className = "text-sm font-mono text-gray-200" > { payerNonce . toString ( ) } </ span >
624- </ div >
625- ) }
608+ < div className = "flex gap-1 border-b border-gray-700 mb-4" role = "tablist" aria-label = "Invoice details" >
609+ { ( [ "audit" , "history" , "notes" ] as const ) . map ( ( tab ) => {
610+ const labels : Record < string , string > = { audit : "Audit Log" , history : "History" , notes : "Notes" } ;
611+ return (
612+ < button
613+ key = { tab }
614+ type = "button"
615+ role = "tab"
616+ aria-selected = { activeDetailsTab === tab }
617+ onClick = { ( ) => setActiveDetailsTab ( tab ) }
618+ className = { `px-4 py-2 text-sm font-medium transition-colors rounded-t-lg -mb-px border-b-2 ${
619+ activeDetailsTab === tab
620+ ? "border-indigo-500 text-indigo-300"
621+ : "border-transparent text-gray-400 hover:text-gray-200"
622+ } `}
623+ >
624+ { labels [ tab ] }
625+ </ button >
626+ ) ;
627+ } ) }
626628 </ div >
629+ { activeDetailsTab === "audit" && < AuditLogTable invoiceId = { id } invoice = { invoice ?? undefined } /> }
630+ { activeDetailsTab === "history" && < VersionHistory invoiceId = { id } /> }
631+ { activeDetailsTab === "notes" && publicKey && (
632+ < CommentSection invoiceId = { id } walletAddress = { publicKey } />
633+ ) }
627634 </ section >
635+
628636
629637 { showCancelModal && (
630638 < CancelModal
0 commit comments