fix(login): address PR review feedback

- Use apiClient instead of raw fetch() in configApi.getPublicConfig
- Validate DEFAULT_LANGUAGE against supported codes on server startup
- Log warning instead of silently swallowing fetch errors in LoginPage
- Case-insensitive browser language matching in detectBrowserLanguage
- Guard against undefined navigator in detectBrowserLanguage
- Validate language code in setLanguageTransient before applying
- Import directly from TranslationContext instead of barrel index
This commit is contained in:
Isaias Tavares
2026-04-12 18:46:03 -03:00
parent 57503a6a10
commit abed22661a
5 changed files with 23 additions and 11 deletions
+1 -1
View File
@@ -380,7 +380,7 @@ export const weatherApi = {
export const configApi = {
getPublicConfig: (): Promise<{ defaultLanguage: string }> =>
fetch('/api/config').then(r => r.json()),
apiClient.get('/config').then(r => r.data),
}
export const settingsApi = {
+12 -7
View File
@@ -54,19 +54,24 @@ export function isRtlLanguage(language: string): boolean {
// Detects the user's preferred language from the browser/OS settings and maps
// it to one of the supported language codes. Returns null if no match is found.
export function detectBrowserLanguage(): string | null {
const browserLangs = navigator.languages?.length ? navigator.languages : [navigator.language]
if (typeof navigator === 'undefined') return null
const browserLangs = navigator.languages?.length
? navigator.languages
: navigator.language ? [navigator.language] : []
const supported = SUPPORTED_LANGUAGES.map(l => l.value)
for (const lang of browserLangs) {
// Exact match (e.g. 'de', 'zh-TW')
if (supported.includes(lang)) return lang
// Exact match (e.g. 'de', 'zh-TW') — case-insensitive
const exactMatch = supported.find(s => s.toLowerCase() === lang.toLowerCase())
if (exactMatch) return exactMatch
// Portuguese variants → our code is 'br' (pt-BR)
if (lang.startsWith('pt')) return 'br'
if (lang.toLowerCase().startsWith('pt')) return 'br'
// Prefix match (e.g. 'de-AT' → 'de', 'zh-CN' → 'zh')
const prefix = lang.split('-')[0]
if (supported.includes(prefix)) return prefix
// Prefix match (e.g. 'de-AT' → 'de', 'zh-CN' → 'zh') — case-insensitive
const prefix = lang.split('-')[0].toLowerCase()
const prefixMatch = supported.find(s => s.toLowerCase() === prefix)
if (prefixMatch) return prefixMatch
}
return null
+2 -2
View File
@@ -2,7 +2,7 @@ import React, { useState, useEffect, useMemo, useRef } from 'react'
import { useNavigate, useLocation } from 'react-router-dom'
import { useAuthStore } from '../store/authStore'
import { useSettingsStore } from '../store/settingsStore'
import { SUPPORTED_LANGUAGES, useTranslation, detectBrowserLanguage } from '../i18n'
import { SUPPORTED_LANGUAGES, useTranslation, detectBrowserLanguage } from '../i18n/TranslationContext'
import { authApi, configApi } from '../api/client'
import { getApiErrorMessage } from '../types'
import { Plane, Eye, EyeOff, Mail, Lock, MapPin, Calendar, Package, User, Globe, Zap, Users, Wallet, Map, CheckSquare, BookMarked, FolderOpen, Route, Shield, KeyRound, ChevronDown } from 'lucide-react'
@@ -134,7 +134,7 @@ export default function LoginPage(): React.ReactElement {
configApi.getPublicConfig()
.then(({ defaultLanguage }) => { if (defaultLanguage) setLanguageTransient(defaultLanguage) })
.catch(() => {})
.catch((err) => console.warn('Failed to fetch default language config:', err))
}, [setLanguageTransient])
useEffect(() => {
+2
View File
@@ -64,6 +64,8 @@ export const useSettingsStore = create<SettingsState>((set, get) => ({
// Used for automatic detection (browser/server default) — only explicit user
// choices via the UI should be persisted.
setLanguageTransient: (lang: string) => {
const supported = ['de', 'en', 'es', 'fr', 'hu', 'nl', 'br', 'cs', 'pl', 'ru', 'zh', 'zh-TW', 'it', 'ar']
if (!supported.includes(lang)) return
set((state) => ({ settings: { ...state.settings, language: lang } }))
},
+6 -1
View File
@@ -102,4 +102,9 @@ export const ENCRYPTION_KEY = _encryptionKey;
// DEFAULT_LANGUAGE sets the language shown on the login page before the user
// selects one. Only applies when the user has no saved language preference.
// Supported values: de, en, es, fr, hu, nl, br, cs, pl, ru, zh, zh-TW, it, ar
export const DEFAULT_LANGUAGE = process.env.DEFAULT_LANGUAGE || 'en';
const SUPPORTED_LANG_CODES = ['de', 'en', 'es', 'fr', 'hu', 'nl', 'br', 'cs', 'pl', 'ru', 'zh', 'zh-TW', 'it', 'ar'];
const rawDefaultLang = process.env.DEFAULT_LANGUAGE || 'en';
if (!SUPPORTED_LANG_CODES.includes(rawDefaultLang)) {
console.warn(`DEFAULT_LANGUAGE="${rawDefaultLang}" is not supported. Falling back to "en". Supported: ${SUPPORTED_LANG_CODES.join(', ')}`);
}
export const DEFAULT_LANGUAGE = SUPPORTED_LANG_CODES.includes(rawDefaultLang) ? rawDefaultLang : 'en';