@@ -5,6 +5,10 @@ export function TestPage() {
55 const [ rows , setRows ] = useState ( [ ] ) ;
66 const [ status , setStatus ] = useState ( "Loading test rows..." ) ;
77 const [ error , setError ] = useState ( "" ) ;
8+ const [ syncStatus , setSyncStatus ] = useState ( null ) ;
9+ const [ syncError , setSyncError ] = useState ( "" ) ;
10+ const [ syncMessage , setSyncMessage ] = useState ( "" ) ;
11+ const [ isSyncing , setIsSyncing ] = useState ( false ) ;
812
913 useEffect ( ( ) => {
1014 let isMounted = true ;
@@ -31,19 +35,113 @@ export function TestPage() {
3135 }
3236 }
3337
38+ async function loadSyncStatus ( ) {
39+ try {
40+ const response = await fetch ( "/api/airtable/status" ) ;
41+ const isJson = response . headers . get ( "content-type" ) ?. includes ( "application/json" ) ;
42+ const data = isJson ? await response . json ( ) : { } ;
43+
44+ if ( ! response . ok ) {
45+ throw new Error ( data . error || "Failed to load sync status." ) ;
46+ }
47+
48+ if ( isMounted ) {
49+ setSyncStatus ( data ) ;
50+ setSyncError ( "" ) ;
51+ }
52+ } catch ( err ) {
53+ if ( isMounted ) {
54+ setSyncError ( err . message ) ;
55+ }
56+ }
57+ }
58+
3459 loadRows ( ) ;
60+ loadSyncStatus ( ) ;
3561
3662 return ( ) => {
3763 isMounted = false ;
3864 } ;
3965 } , [ ] ) ;
4066
67+ async function loadRows ( ) {
68+ const response = await fetch ( "/api/test" ) ;
69+ const isJson = response . headers . get ( "content-type" ) ?. includes ( "application/json" ) ;
70+ const data = isJson ? await response . json ( ) : { } ;
71+
72+ if ( ! response . ok ) {
73+ throw new Error ( data . error || "Failed to load test rows." ) ;
74+ }
75+
76+ setRows ( data . rows || [ ] ) ;
77+ }
78+
79+ async function loadSyncStatus ( ) {
80+ const response = await fetch ( "/api/airtable/status" ) ;
81+ const isJson = response . headers . get ( "content-type" ) ?. includes ( "application/json" ) ;
82+ const data = isJson ? await response . json ( ) : { } ;
83+
84+ if ( ! response . ok ) {
85+ throw new Error ( data . error || "Failed to load sync status." ) ;
86+ }
87+
88+ setSyncStatus ( data ) ;
89+ setSyncError ( "" ) ;
90+ return data ;
91+ }
92+
93+ async function handleSyncNow ( ) {
94+ setIsSyncing ( true ) ;
95+ setSyncError ( "" ) ;
96+ setSyncMessage ( "" ) ;
97+
98+ try {
99+ const response = await fetch ( "/api/airtable/sync" , {
100+ method : "POST" ,
101+ } ) ;
102+ const isJson = response . headers . get ( "content-type" ) ?. includes ( "application/json" ) ;
103+ const data = isJson ? await response . json ( ) : { } ;
104+
105+ if ( ! response . ok ) {
106+ throw new Error ( data . error || data . message || "Failed to sync Airtable." ) ;
107+ }
108+
109+ setSyncMessage ( "Sync finished." ) ;
110+ setSyncStatus ( ( currentStatus ) => ( {
111+ ...currentStatus ,
112+ lastSync : data ,
113+ } ) ) ;
114+ await Promise . all ( [ loadRows ( ) , loadSyncStatus ( ) ] ) ;
115+ } catch ( err ) {
116+ setSyncError ( err . message ) ;
117+ } finally {
118+ setIsSyncing ( false ) ;
119+ }
120+ }
121+
122+ const lastSyncTime = syncStatus ?. lastSync ?. syncedAt
123+ ? new Date ( syncStatus . lastSync . syncedAt ) . toLocaleString ( )
124+ : "Never synced" ;
125+
41126 return (
42127 < main className = "test-page" >
43128 < section className = "test-page__card" >
44129 < h1 > Test Table</ h1 >
45130 < p > Rows from the production Postgres < code > test</ code > table.</ p >
46131
132+ < div className = "test-page__sync" >
133+ < div >
134+ < strong > Airtable sync</ strong >
135+ < span > Last update: { lastSyncTime } </ span >
136+ </ div >
137+ < button type = "button" onClick = { handleSyncNow } disabled = { isSyncing } >
138+ { isSyncing ? "Syncing..." : "Sync now" }
139+ </ button >
140+ </ div >
141+
142+ { syncMessage && < p className = "test-page__success" > { syncMessage } </ p > }
143+ { syncError && < p className = "test-page__error" > { syncError } </ p > }
144+
47145 { status && < p > { status } </ p > }
48146 { error && < p className = "test-page__error" > { error } </ p > }
49147
0 commit comments