Add translations for "Loading place details…" and improve place search functionality

- Integrate a loading spinner for "Name" input field during place search.
- Enhance OpenStreetMap place detail retrieval with Nominatim lookup.
- Update `authStore` to track Google Maps API key presence.
This commit is contained in:
Ben Haas
2026-04-13 08:28:34 -07:00
parent 7fca16d866
commit 1a51f8e3e1
17 changed files with 78 additions and 12 deletions
@@ -6,7 +6,7 @@ import { useAuthStore } from '../../store/authStore'
import { useCanDo } from '../../store/permissionsStore'
import { useTripStore } from '../../store/tripStore'
import { useToast } from '../shared/Toast'
import { Search, Paperclip, X, AlertTriangle } from 'lucide-react'
import { Search, Paperclip, X, AlertTriangle, Loader2 } from 'lucide-react'
import { useTranslation } from '../../i18n'
import CustomTimePicker from '../shared/CustomTimePicker'
import type { Place, Category, Assignment } from '../../types'
@@ -234,6 +234,7 @@ export default function PlaceFormModal({
setAcHighlight(-1)
const previousSearch = mapsSearch
setMapsSearch('')
setForm(prev => ({ ...prev, name: suggestion.mainText }))
setIsSearchingMaps(true)
try {
const result = await mapsApi.details(suggestion.placeId, language)
@@ -425,14 +426,21 @@ export default function PlaceFormModal({
{/* Name */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">{t('places.formName')} *</label>
<input
type="text"
value={form.name}
onChange={e => handleChange('name', e.target.value)}
required
placeholder={t('places.formNamePlaceholder')}
className="form-input"
/>
<div className="relative">
<input
type="text"
value={form.name}
onChange={e => handleChange('name', e.target.value)}
required
placeholder={t('places.formNamePlaceholder')}
className="form-input"
/>
{isSearchingMaps && (
<div className="absolute right-2.5 top-0 bottom-0 flex items-center">
<Loader2 className="w-4 h-4 animate-spin text-slate-400" />
</div>
)}
</div>
</div>
{/* Description */}
+1
View File
@@ -855,6 +855,7 @@ const ar: Record<string, string | { name: string; category: string }[]> = {
'places.reservationNotesPlaceholder': 'ملاحظات الحجز، رقم التأكيد...',
'places.mapsSearchPlaceholder': 'ابحث عن أماكن...',
'places.mapsSearchError': 'فشل البحث عن المكان.',
'places.loadingDetails': 'جارٍ تحميل تفاصيل المكان…',
'places.osmHint': 'يتم البحث عبر OpenStreetMap (بدون صور أو ساعات عمل أو تقييمات). أضف مفتاح Google API في الإعدادات للحصول على جميع التفاصيل.',
'places.osmActive': 'البحث عبر OpenStreetMap (بدون صور أو تقييمات أو ساعات عمل). أضف مفتاح Google API في الإعدادات لبيانات موسعة.',
'places.categoryCreateError': 'فشل إنشاء الفئة',
+1
View File
@@ -837,6 +837,7 @@ const br: Record<string, string | { name: string; category: string }[]> = {
'places.reservationNotesPlaceholder': 'Notas da reserva, código de confirmação...',
'places.mapsSearchPlaceholder': 'Buscar lugares...',
'places.mapsSearchError': 'Falha na busca de lugares.',
'places.loadingDetails': 'Carregando detalhes do lugar…',
'places.osmHint': 'Busca via OpenStreetMap (sem fotos, horários ou avaliações). Adicione uma chave Google nas configurações para detalhes completos.',
'places.osmActive': 'Busca via OpenStreetMap (sem fotos, avaliações ou horário de funcionamento). Adicione uma chave Google em Configurações para mais dados.',
'places.categoryCreateError': 'Falha ao criar categoria',
+1
View File
@@ -853,6 +853,7 @@ const cs: Record<string, string | { name: string; category: string }[]> = {
'places.reservationNotesPlaceholder': 'Poznámky k rezervaci, potvrzovací kód...',
'places.mapsSearchPlaceholder': 'Hledat místa...',
'places.mapsSearchError': 'Hledání místa se nezdařilo.',
'places.loadingDetails': 'Načítání podrobností místa…',
'places.osmHint': 'Používáte hledání přes OpenStreetMap (bez fotek a hodnocení). Pro plné detaily přidejte Google API klíč v nastavení.',
'places.osmActive': 'Hledání přes OpenStreetMap.',
'places.categoryCreateError': 'Nepodařilo se vytvořit kategorii',
+1
View File
@@ -853,6 +853,7 @@ const de: Record<string, string | { name: string; category: string }[]> = {
'places.reservationNotesPlaceholder': 'Reservierungsnotizen, Bestätigungsnummer...',
'places.mapsSearchPlaceholder': 'Ortssuche...',
'places.mapsSearchError': 'Ortssuche fehlgeschlagen.',
'places.loadingDetails': 'Ortsdetails werden geladen…',
'places.osmHint': 'OpenStreetMap-Suche aktiv (ohne Bilder, Öffnungszeiten, Bewertungen). Für erweiterte Daten Google API Key in den Einstellungen hinterlegen.',
'places.osmActive': 'Suche via OpenStreetMap (ohne Bilder, Bewertungen & Öffnungszeiten). Google API Key in den Einstellungen hinterlegen für erweiterte Daten.',
'places.categoryCreateError': 'Fehler beim Erstellen der Kategorie',
+1
View File
@@ -872,6 +872,7 @@ const en: Record<string, string | { name: string; category: string }[]> = {
'places.reservationNotesPlaceholder': 'Reservation notes, confirmation number...',
'places.mapsSearchPlaceholder': 'Search places...',
'places.mapsSearchError': 'Place search failed.',
'places.loadingDetails': 'Loading place details…',
'places.osmHint': 'Using OpenStreetMap search (no photos, opening hours, or ratings). Add a Google API key in settings for full details.',
'places.osmActive': 'Search via OpenStreetMap (no photos, ratings or opening hours). Add a Google API key in Settings for enhanced data.',
'places.categoryCreateError': 'Failed to create category',
+1
View File
@@ -828,6 +828,7 @@ const es: Record<string, string> = {
'places.reservationNotesPlaceholder': 'Notas de reserva, número de confirmación...',
'places.mapsSearchPlaceholder': 'Buscar lugares...',
'places.mapsSearchError': 'La búsqueda de lugares falló.',
'places.loadingDetails': 'Cargando detalles del lugar…',
'places.osmHint': 'Usando búsqueda con OpenStreetMap (sin fotos, horarios ni valoraciones). Añade una clave API de Google en Ajustes para obtener todos los detalles.',
'places.osmActive': 'Búsqueda mediante OpenStreetMap (sin fotos, valoraciones ni horarios). Añade una clave API de Google en Ajustes para datos ampliados.',
'places.categoryCreateError': 'No se pudo crear la categoría',
+1
View File
@@ -852,6 +852,7 @@ const fr: Record<string, string> = {
'places.reservationNotesPlaceholder': 'Notes de réservation, numéro de confirmation…',
'places.mapsSearchPlaceholder': 'Rechercher des lieux…',
'places.mapsSearchError': 'La recherche de lieu a échoué.',
'places.loadingDetails': 'Chargement des détails du lieu…',
'places.osmHint': 'Recherche via OpenStreetMap (pas de photos, horaires ni notes). Ajoutez une clé API Google dans les paramètres pour plus de détails.',
'places.osmActive': 'Recherche via OpenStreetMap (pas de photos, notes ni horaires). Ajoutez une clé API Google dans les paramètres pour des données enrichies.',
'places.categoryCreateError': 'Impossible de créer la catégorie',
+1
View File
@@ -853,6 +853,7 @@ const hu: Record<string, string | { name: string; category: string }[]> = {
'places.reservationNotesPlaceholder': 'Foglalási jegyzetek, visszaigazolási szám...',
'places.mapsSearchPlaceholder': 'Helyek keresése...',
'places.mapsSearchError': 'Helykeresés sikertelen.',
'places.loadingDetails': 'Hely adatainak betöltése…',
'places.osmHint': 'OpenStreetMap keresés aktív (képek, nyitvatartás és értékelések nélkül). Bővített adatokhoz add meg a Google API kulcsot a beállításokban.',
'places.osmActive': 'Keresés OpenStreetMap-en keresztül (képek, értékelések és nyitvatartás nélkül). Bővített adatokhoz add meg a Google API kulcsot a beállításokban.',
'places.categoryCreateError': 'Nem sikerült létrehozni a kategóriát',
+1
View File
@@ -853,6 +853,7 @@ const it: Record<string, string | { name: string; category: string }[]> = {
'places.reservationNotesPlaceholder': 'Note della prenotazione, numero di conferma...',
'places.mapsSearchPlaceholder': 'Cerca luoghi...',
'places.mapsSearchError': 'Impossibile cercare i luoghi.',
'places.loadingDetails': 'Caricamento dettagli del luogo…',
'places.osmHint': 'Uso della ricerca OpenStreetMap (senza foto, orari di apertura o valutazioni). Aggiungi una chiave API Google nelle impostazioni per i dettagli completi.',
'places.osmActive': 'Ricerca tramite OpenStreetMap (senza foto, valutazioni o orari di apertura). Aggiungi una chiave API Google nelle Impostazioni per dati avanzati.',
'places.categoryCreateError': 'Impossibile creare la categoria',
+1
View File
@@ -852,6 +852,7 @@ const nl: Record<string, string> = {
'places.reservationNotesPlaceholder': 'Reserveringsnotities, bevestigingsnummer...',
'places.mapsSearchPlaceholder': 'Plaatsen zoeken...',
'places.mapsSearchError': 'Zoeken naar plaatsen mislukt.',
'places.loadingDetails': 'Plaatsgegevens laden…',
'places.osmHint': 'Zoeken via OpenStreetMap (geen foto\'s, openingstijden of beoordelingen). Voeg een Google API-sleutel toe in instellingen voor volledige details.',
'places.osmActive': 'Zoeken via OpenStreetMap (geen foto\'s, beoordelingen of openingstijden). Voeg een Google API-sleutel toe in Instellingen voor uitgebreide gegevens.',
'places.categoryCreateError': 'Categorie aanmaken mislukt',
+1
View File
@@ -811,6 +811,7 @@ const pl: Record<string, string | { name: string; category: string }[]> = {
'places.reservationNotesPlaceholder': 'Notatki z rezerwacji, numer potwierdzenia...',
'places.mapsSearchPlaceholder': 'Szukaj miejsc...',
'places.mapsSearchError': 'Nie udało się wyszukać miejsca.',
'places.loadingDetails': 'Ładowanie szczegółów miejsca…',
'places.osmHint': 'Korzystając z OpenStreetMap (brak zdjęć, godzin otwarcia czy ocen). Dodaj klucz API Google w ustawieniach aby uzyskać pełne dane.',
'places.osmActive': 'Szukaj przez OpenStreetMap (brak zdjęć, ocen czy godzin otwarcia). Dodaj klucz API Google w ustawieniach aby uzyskać pełne dane.',
'places.categoryCreateError': 'Nie udało się utworzyć kategorii',
+1
View File
@@ -852,6 +852,7 @@ const ru: Record<string, string> = {
'places.reservationNotesPlaceholder': 'Заметки о бронировании, номер подтверждения...',
'places.mapsSearchPlaceholder': 'Поиск мест...',
'places.mapsSearchError': 'Ошибка поиска мест.',
'places.loadingDetails': 'Загрузка данных о месте…',
'places.osmHint': 'Поиск через OpenStreetMap (без фото, часов работы и рейтингов). Добавьте API-ключ Google в настройках для полной информации.',
'places.osmActive': 'Поиск через OpenStreetMap (без фото, рейтингов и часов работы). Добавьте API-ключ Google в настройках для расширенных данных.',
'places.categoryCreateError': 'Не удалось создать категорию',
+1
View File
@@ -852,6 +852,7 @@ const zh: Record<string, string> = {
'places.reservationNotesPlaceholder': '预订备注、确认号...',
'places.mapsSearchPlaceholder': '搜索地点...',
'places.mapsSearchError': '地点搜索失败。',
'places.loadingDetails': '正在加载地点详情…',
'places.osmHint': '使用 OpenStreetMap 搜索(无照片、营业时间或评分)。在设置中添加 Google API 密钥以获取完整信息。',
'places.osmActive': '通过 OpenStreetMap 搜索(无照片、评分或营业时间)。在设置中添加 Google API 密钥以获取增强数据。',
'places.categoryCreateError': '创建分类失败',
+1
View File
@@ -832,6 +832,7 @@ const zhTw: Record<string, string> = {
'places.reservationNotesPlaceholder': '預訂備註、確認號...',
'places.mapsSearchPlaceholder': '搜尋地點...',
'places.mapsSearchError': '地點搜尋失敗。',
'places.loadingDetails': '正在載入地點詳情…',
'places.osmHint': '使用 OpenStreetMap 搜尋(無照片、營業時間或評分)。在設定中新增 Google API 金鑰以獲取完整資訊。',
'places.osmActive': '透過 OpenStreetMap 搜尋(無照片、評分或營業時間)。在設定中新增 Google API 金鑰以獲取增強資料。',
'places.categoryCreateError': '建立分類失敗',
+4
View File
@@ -178,6 +178,7 @@ export const useAuthStore = create<AuthState>((set, get) => ({
await authApi.updateMapsKey(key)
set((state) => ({
user: state.user ? { ...state.user, maps_api_key: key || null } : null,
hasMapsKey: !!key,
}))
} catch (err: unknown) {
throw new Error(getApiErrorMessage(err, 'Error saving API key'))
@@ -188,6 +189,9 @@ export const useAuthStore = create<AuthState>((set, get) => ({
try {
const data = await authApi.updateApiKeys(keys)
set({ user: data.user })
if ('maps_api_key' in keys) {
set({ hasMapsKey: !!keys.maps_api_key })
}
} catch (err: unknown) {
throw new Error(getApiErrorMessage(err, 'Error saving API keys'))
}