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
48 changes: 42 additions & 6 deletions apps/postcards/src/features/backup/Backup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -221,12 +221,18 @@ export function Backup() {
{t("backup.reminder.suffix")}
</span>
<span className="backup-reminder-actions">
<button className="btn" type="button" onClick={() => void exportJson()}>
<button
className="btn"
type="button"
title={t("backup.reminder.backupNow")}
onClick={() => void exportJson()}
>
{t("backup.reminder.backupNow")}
</button>
<button
className="link"
type="button"
title={t("backup.reminder.later")}
onClick={() => {
snoozeReminder(Date.now());
setReminderDue(false);
Expand All @@ -241,24 +247,50 @@ export function Backup() {
<p className="muted">{t("backup.intro")}</p>

<div className="btn-row">
<button className="btn" type="button" onClick={() => void exportArchive()}>
<button
className="btn"
type="button"
title={t("backup.export.all")}
onClick={() => void exportArchive()}
>
{t("backup.export.all")}
</button>
<button className="btn-ghost" type="button" onClick={() => void exportJson()}>
<button
className="btn-ghost"
type="button"
title={t("backup.export.data")}
onClick={() => void exportJson()}
>
{t("backup.export.data")}
</button>
<button className="btn-ghost" type="button" onClick={() => void exportCsv()}>
<button
className="btn-ghost"
type="button"
title={t("backup.export.csv")}
onClick={() => void exportCsv()}
>
{t("backup.export.csv")}
</button>
<button className="btn-ghost" type="button" onClick={() => void exportMd()}>
<button
className="btn-ghost"
type="button"
title={t("backup.export.md")}
onClick={() => void exportMd()}
>
{t("backup.export.md")}
</button>
<button className="btn-ghost" type="button" onClick={() => fileInput.current?.click()}>
<button
className="btn-ghost"
type="button"
title={t("backup.import")}
onClick={() => fileInput.current?.click()}
>
{t("backup.import")}
</button>
<input
ref={fileInput}
type="file"
title={t("backup.import")}
accept="application/zip,.zip,application/json,.json,text/csv,.csv,.tsv,text/plain"
onChange={onImport}
style={{ display: "none" }}
Expand Down Expand Up @@ -294,6 +326,7 @@ export function Backup() {
<button
className="btn-danger"
type="button"
title={t("backup.reset.button")}
onClick={() => {
setResetText("");
setResetOpen(true);
Expand Down Expand Up @@ -326,11 +359,13 @@ export function Backup() {
autoCorrect="off"
spellCheck={false}
aria-label={t("backup.reset.inputAria", { word: RESET_WORD })}
title={t("backup.reset.inputAria", { word: RESET_WORD })}
/>
<div className="btn-row">
<button
className="btn-danger"
type="button"
title={t("backup.reset.erase")}
disabled={resetText.trim().toUpperCase() !== RESET_WORD}
onClick={() => void resetAll()}
>
Expand All @@ -339,6 +374,7 @@ export function Backup() {
<button
className="btn-ghost"
type="button"
title={t("common.cancel")}
onClick={() => {
setResetOpen(false);
setResetText("");
Expand Down
12 changes: 11 additions & 1 deletion apps/postcards/src/features/city/CityScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ function VisitDetails({ visitId, date, note }: { visitId: string; date: string |
<input
type="date"
className="select"
title={t("city.visitDate")}
value={date ?? ""}
onChange={(e) => void setDetails(visitId, { date: e.target.value || null })}
/>
Expand All @@ -66,6 +67,7 @@ function VisitDetails({ visitId, date, note }: { visitId: string; date: string |
<textarea
className="select journal-textarea"
rows={3}
title={t("city.note")}
placeholder={t("city.notePlaceholder")}
maxLength={2000}
value={draft}
Expand Down Expand Up @@ -195,7 +197,7 @@ export function CityScreen({ cityId, onBack }: { cityId: string; onBack: () => v

return (
<div className="screen city-page">
<button className="mini-btn back-btn" type="button" onClick={onBack}>
<button className="mini-btn back-btn" type="button" title={t("city.back")} onClick={onBack}>
← {t("city.back")}
</button>

Expand Down Expand Up @@ -238,6 +240,7 @@ export function CityScreen({ cityId, onBack }: { cityId: string; onBack: () => v
<button
className="mini-btn"
type="button"
title={t("city.showOnMap")}
onClick={() => {
// "Show on map" opens the place's marker card (been-there /
// Details), not just a silent re-centre — same as tapping its dot.
Expand Down Expand Up @@ -265,6 +268,7 @@ export function CityScreen({ cityId, onBack }: { cityId: string; onBack: () => v
<button
className="mini-btn"
type="button"
title={t("city.searchOnMap")}
onClick={() => {
useUi.getState().setTab("map");
useUi.getState().focusSearch();
Expand Down Expand Up @@ -305,6 +309,7 @@ export function CityScreen({ cityId, onBack }: { cityId: string; onBack: () => v
className="mini-btn"
type="button"
style={placeStories.length ? { marginTop: 6 } : undefined}
title={t("city.addStory")}
onClick={() => openJournalDraft(place)}
>
+ {t("city.addStory")}
Expand All @@ -321,6 +326,7 @@ export function CityScreen({ cityId, onBack }: { cityId: string; onBack: () => v
<a
className="mini-btn"
href={wikipediaHref}
title="Wikipedia"
target="_blank"
rel="noopener noreferrer"
>
Expand All @@ -329,6 +335,7 @@ export function CityScreen({ cityId, onBack }: { cityId: string; onBack: () => v
<a
className="mini-btn"
href={wikivoyageHref}
title="Wikivoyage"
target="_blank"
rel="noopener noreferrer"
>
Expand All @@ -338,6 +345,7 @@ export function CityScreen({ cityId, onBack }: { cityId: string; onBack: () => v
<a
className="mini-btn"
href={`https://www.openstreetmap.org/#map=12/${lat}/${lon}`}
title="OpenStreetMap"
target="_blank"
rel="noopener noreferrer"
>
Expand All @@ -362,6 +370,7 @@ export function CityScreen({ cityId, onBack }: { cityId: string; onBack: () => v
type="button"
onClick={() => useUi.getState().openCity(h.id)}
aria-label={t("places.row.openAria", { name: h.name })}
title={t("places.row.openAria", { name: h.name })}
>
<CityLine flag="🏛️" name={h.name} sub={<>· {t("city.away", { km: formatKm(h.km) })}</>} multiline />
</button>
Expand All @@ -385,6 +394,7 @@ export function CityScreen({ cityId, onBack }: { cityId: string; onBack: () => v
type="button"
onClick={() => useUi.getState().openCity(a.id)}
aria-label={t("places.row.openAria", { name: `${a.name} (${a.id})` })}
title={t("places.row.openAria", { name: `${a.name} (${a.id})` })}
>
<CityLine
flag="✈️"
Expand Down
13 changes: 10 additions & 3 deletions apps/postcards/src/features/country/CountryScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export function CountryScreen({ iso2, onBack }: { iso2: string; onBack: () => vo
if (!country) {
return (
<div className="screen city-page">
<button className="mini-btn back-btn" type="button" onClick={onBack}>
<button className="mini-btn back-btn" type="button" title={t("city.back")} onClick={onBack}>
← {t("city.back")}
</button>
<p className="notice">{t("country.unknownCode", { iso2 })}</p>
Expand All @@ -59,7 +59,7 @@ export function CountryScreen({ iso2, onBack }: { iso2: string; onBack: () => vo

return (
<div className="screen city-page">
<button className="mini-btn back-btn" type="button" onClick={onBack}>
<button className="mini-btn back-btn" type="button" title={t("city.back")} onClick={onBack}>
← {t("city.back")}
</button>

Expand Down Expand Up @@ -90,7 +90,12 @@ export function CountryScreen({ iso2, onBack }: { iso2: string; onBack: () => vo
</span>
)}
{cities.length > 0 && (
<button className="mini-btn" type="button" onClick={showOnMap}>
<button
className="mini-btn"
type="button"
title={t("city.showOnMap")}
onClick={showOnMap}
>
{t("city.showOnMap")}
</button>
)}
Expand Down Expand Up @@ -135,6 +140,7 @@ export function CountryScreen({ iso2, onBack }: { iso2: string; onBack: () => vo
<button
className="city-focus"
type="button"
title={t("places.row.openAria", { name: h.name })}
onClick={() => useUi.getState().openCity(h.id)}
>
<CityLine flag="🏛️" name={h.name} multiline />
Expand Down Expand Up @@ -166,6 +172,7 @@ export function CountryScreen({ iso2, onBack }: { iso2: string; onBack: () => vo
<button
className="city-focus"
type="button"
title={t("places.row.openAria", { name: c.name })}
onClick={() => useUi.getState().openCity(c.id)}
>
<CityLine
Expand Down
65 changes: 55 additions & 10 deletions apps/postcards/src/features/guides/GuideButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,12 @@ function GuideContent({ placeName, names }: { placeName: string; names: GuideNam
</p>
)}
{!overview && state === "idle" && !autoLoad && !offlineMode && (
<button type="button" className="btn-ghost guide-overview-btn" onClick={loadOverview}>
<button
type="button"
className="btn-ghost guide-overview-btn"
title={t("guide.loadOverview")}
onClick={loadOverview}
>
↧ {t("guide.loadOverview")}
</button>
)}
Expand All @@ -276,7 +281,12 @@ function GuideContent({ placeName, names }: { placeName: string; names: GuideNam
!navigator.onLine && (
<p className="muted small">
{t("guide.offlineWait")}{" "}
<button type="button" className="mini-btn" onClick={loadOverview}>
<button
type="button"
className="mini-btn"
title={t("guide.retry")}
onClick={loadOverview}
>
{t("guide.retry")}
</button>
</p>
Expand All @@ -286,7 +296,12 @@ function GuideContent({ placeName, names }: { placeName: string; names: GuideNam
{isOffline()
? t("guide.emptyOffline")
: t("guide.emptyOnline")}{" "}
<button type="button" className="mini-btn" onClick={loadOverview}>
<button
type="button"
className="mini-btn"
title={t("guide.retry")}
onClick={loadOverview}
>
{t("guide.retry")}
</button>
</p>
Expand All @@ -312,7 +327,12 @@ function GuideContent({ placeName, names }: { placeName: string; names: GuideNam
))}
</blockquote>
<figcaption className="muted small guide-cite">
<a href={cardUrl} target="_blank" rel="noopener noreferrer">
<a
href={cardUrl}
target="_blank"
rel="noopener noreferrer"
title={t("guide.readMore", { source: cardSource })}
>
{t("guide.readMore", { source: cardSource })}
</a>{" "}
· CC BY-SA · {t("guide.savedOffline")}
Expand All @@ -322,7 +342,12 @@ function GuideContent({ placeName, names }: { placeName: string; names: GuideNam

{/* The whole guide, readable right here — no trip to the website. */}
{(overview || full) && !full && fullRestored && fullState === "idle" && !offlineMode && (
<button type="button" className="btn-ghost guide-overview-btn" onClick={() => void loadFullGuide()}>
<button
type="button"
className="btn-ghost guide-overview-btn"
title={t("guide.readWhole")}
onClick={() => void loadFullGuide()}
>
📖 {t("guide.readWhole")}
</button>
)}
Expand All @@ -336,7 +361,12 @@ function GuideContent({ placeName, names }: { placeName: string; names: GuideNam
{isOffline()
? t("guide.fullOffline")
: t("guide.fullEmpty")}{" "}
<button type="button" className="mini-btn" onClick={() => void loadFullGuide()}>
<button
type="button"
className="mini-btn"
title={t("guide.retry")}
onClick={() => void loadFullGuide()}
>
{t("guide.retry")}
</button>
</p>
Expand All @@ -345,7 +375,7 @@ function GuideContent({ placeName, names }: { placeName: string; names: GuideNam
<div className="guide-full">
{fullSections.map((s) => (
<details key={s.heading} className="guide-full-section">
<summary>{s.heading}</summary>
<summary title={s.heading}>{s.heading}</summary>
{s.text.split(/\n{2,}/).map((p, i) => (
<p key={i}>{p}</p>
))}
Expand All @@ -354,7 +384,12 @@ function GuideContent({ placeName, names }: { placeName: string; names: GuideNam
{full && (
<p className="muted small guide-cite">
{t("guide.fullGuideLabel")} ·{" "}
<a href={full.url} target="_blank" rel="noopener noreferrer">
<a
href={full.url}
target="_blank"
rel="noopener noreferrer"
title={full.attribution}
>
{full.attribution}
</a>{" "}
· {t("guide.savedOffline")}
Expand All @@ -373,7 +408,12 @@ function GuideContent({ placeName, names }: { placeName: string; names: GuideNam
<ul className="guide-links">
{items.map((l) => (
<li key={l.id}>
<a href={l.url} target="_blank" rel="noopener noreferrer">
<a
href={l.url}
target="_blank"
rel="noopener noreferrer"
title={t(`guide.link.${l.kind}.label` as MessageKey, { name: l.name })}
>
<span className="guide-link-label">
{t(`guide.link.${l.kind}.label` as MessageKey, { name: l.name })}
</span>
Expand All @@ -391,7 +431,12 @@ function GuideContent({ placeName, names }: { placeName: string; names: GuideNam
{/* Honest fallback: a search link always works, even when an exact article
title doesn't match (name variants) or the overview fetch fails. */}
<p className="muted small guide-search">
<a href={searchUrl(searchQuery)} target="_blank" rel="noopener noreferrer">
<a
href={searchUrl(searchQuery)}
target="_blank"
rel="noopener noreferrer"
title={t("guide.searchFor", { query: searchQuery })}
>
{t("guide.searchFor", { query: searchQuery })}
</a>
</p>
Expand Down
Loading
Loading