Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,19 @@ trips/
└── README.md
```

## Navigation & languages

The main dashboard links directly to the flight price watcher from the desktop navigation, hero actions, Flights essentials card and mobile navigation.

The dashboard and flight-price page share `i18n.js` with these languages:

- English
- Tiếng Việt
- 中文 (Simplified Chinese)
- 日本語

The selected language is stored in `localStorage` as `travel-language`; otherwise the browser language is detected automatically.

## Local development

```bash
Expand Down
1 change: 1 addition & 0 deletions flights.html
Original file line number Diff line number Diff line change
Expand Up @@ -49,5 +49,6 @@
async function load(){try{const [data,history]=await Promise.all([fetch(`./data/flights.json?t=${Date.now()}`,{cache:'no-store'}).then(r=>{if(!r.ok)throw new Error('flights.json unavailable');return r.json()}),fetch(`./data/flight-history.json?t=${Date.now()}`,{cache:'no-store'}).then(r=>r.ok?r.json():[]).catch(()=>[])]);LIVE=data;HISTORY=Array.isArray(history)?history:[];$('#searchMeta').textContent=`${data.search?.route_label||'Flight search'} · ${data.search?.passengers?.label||''}`;if(data.status!=='ok'){ $('#cheapest').textContent=data.status==='no_results'?'No results':'Setup needed';$('#cheapestMeta').textContent=data.disclaimer||'Run the GitHub Action after adding the SerpApi key.';$('#freshness').textContent=data.generated_at?'Checked · no matching itinerary':'Waiting for first live check';$('#lastChecked').textContent=data.generated_at?`Last checked: ${dt(data.generated_at)}`:'Last checked: —';$('#comparison').innerHTML='<div class="empty" style="grid-column:1/-1">No comparable fares yet.</div>';$('#scenarios').innerHTML=data.status==='setup_required'?`<div class="empty"><strong>SerpApi key not configured yet.</strong><br><br>Add repository secret <code>SERPAPI_API_KEY</code>, merge this PR, then run <b>Update live flight prices</b> from GitHub Actions.</div>`:`<div class="empty">${escapeHtml(data.disclaimer||'No matching itinerary was returned.')}</div>`;return}const cheapest=data.cheapest?.offer;if(cheapest){$('#cheapest').textContent=currency(cheapest.total_amount,cheapest.total_currency);$('#cheapestMeta').textContent=`${data.cheapest.label} · ${cheapest.owner?.name||'Airline'} · selected 7 travellers`}$('#lastChecked').textContent=`Last checked: ${dt(data.generated_at)}`;$('#freshness').textContent=freshness(data.generated_at).label;populateAirlines();renderOffers();renderComparison();renderHistory(cheapest);renderSummary(cheapest);renderTarget(cheapest)}catch(e){$('#cheapest').textContent='Unavailable';$('#cheapestMeta').textContent=e.message;$('#freshness').textContent='Data unavailable';$('#scenarios').innerHTML=`<div class="empty">Could not load flight price data. ${escapeHtml(e.message)}</div>`}}
['sort','stops','airline'].forEach(id=>document.getElementById(id).addEventListener('change',renderOffers));$('#saveTarget').addEventListener('click',()=>{const v=$('#targetPrice').value.replace(/[^0-9.]/g,'');localStorage.setItem('flight-target',v);$('#targetPrice').value=v;renderTarget(LIVE?.cheapest?.offer)});load();
</script>
<script src="./i18n.js"></script>
</body>
</html>
Loading
Loading