diff --git a/API/index.js b/API/index.js index 126d467..0ef4324 100644 --- a/API/index.js +++ b/API/index.js @@ -1,5 +1,5 @@ /** - * FarmaFinder external pharmacy sources (OpenStreetMap, Google Places, open-data URLs). + * FarmaClic external pharmacy sources (OpenStreetMap, Google Places, open-data URLs). * Used by the backend admin import; OSM does not require n8n. */ diff --git a/API/open-data.js b/API/open-data.js index ca87821..c7fcbf3 100644 --- a/API/open-data.js +++ b/API/open-data.js @@ -83,7 +83,7 @@ export async function fetchPharmaciesFromOpenDataUrl(openDataUrl) { const res = await fetch(u, { headers: { Accept: 'application/json', - 'User-Agent': 'FarmaFinder/1.0 (open-data pharmacy import)', + 'User-Agent': 'FarmaClic/1.0 (open-data pharmacy import)', }, }); const text = await res.text(); diff --git a/API/opening-hours-osm.js b/API/opening-hours-osm.js index e721dd1..d56389d 100644 --- a/API/opening-hours-osm.js +++ b/API/opening-hours-osm.js @@ -1,5 +1,5 @@ /** - * Parse a subset of the OSM opening_hours format into FarmaFinder's internal + * Parse a subset of the OSM opening_hours format into FarmaClic's internal * shape: { mon: ["09:00", "21:00"], ..., sun: null }. * * Supports the patterns commonly seen on amenity=pharmacy nodes: diff --git a/API/osm-overpass.js b/API/osm-overpass.js index de48c76..02f44f2 100644 --- a/API/osm-overpass.js +++ b/API/osm-overpass.js @@ -35,7 +35,7 @@ async function fetchOverpass(endpoint, body) { headers: { 'Content-Type': 'application/x-www-form-urlencoded', Accept: 'application/json', - 'User-Agent': 'FarmaFinder/1.0 (OSM pharmacy import; local admin)', + 'User-Agent': 'FarmaClic/1.0 (OSM pharmacy import; local admin)', }, body, }); diff --git a/API/package.json b/API/package.json index db28e6a..c9a6f64 100644 --- a/API/package.json +++ b/API/package.json @@ -1,5 +1,5 @@ { - "name": "farma-finder-api-sources", + "name": "farma-clic-api-sources", "private": true, "type": "module" } diff --git a/backend/.env.example b/backend/.env.example index bf65dcd..8cbdf78 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -7,7 +7,7 @@ REDIS_PORT=6379 REDIS_PASSWORD= # PostgreSQL for user accounts (leave unset to fallback to SQLite — dev/test only) -PG_URL=postgresql://farmafinder:change-me@localhost:5432/farmafinder +PG_URL=postgresql://farmaclic:change-me@localhost:5432/farmaclic PG_PASSWORD=change-me # Web Push (VAPID). Generate with: diff --git a/backend/farmacias-webhook-import.js b/backend/farmacias-webhook-import.js index e4bbb17..3979f56 100644 --- a/backend/farmacias-webhook-import.js +++ b/backend/farmacias-webhook-import.js @@ -1,5 +1,5 @@ /** - * Fetch pharmacy lists from an n8n (or any) HTTP webhook and map into FarmaFinder rows. + * Fetch pharmacy lists from an n8n (or any) HTTP webhook and map into FarmaClic rows. * Default URL: FARMACIAS_WEBHOOK_URL env or the project webhook. */ diff --git a/backend/package.json b/backend/package.json index 9f00bb2..43c6ef2 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,7 +1,7 @@ { - "name": "farma-finder-backend", + "name": "farma-clic-backend", "version": "1.0.0", - "description": "Backend API for FarmaFinder", + "description": "Backend API for FarmaClic", "main": "server.js", "type": "module", "scripts": { diff --git a/backend/reset-db.sh b/backend/reset-db.sh index 14e40c0..711dab7 100644 --- a/backend/reset-db.sh +++ b/backend/reset-db.sh @@ -1,6 +1,6 @@ #!/bin/bash -echo "🔄 FarmaFinder - Quick Database Reset" +echo "🔄 FarmaClic - Quick Database Reset" echo "====================================" echo "" echo "Este script eliminará la base de datos actual y creará una nueva." diff --git a/backend/server.js b/backend/server.js index 20daef9..1f52395 100644 --- a/backend/server.js +++ b/backend/server.js @@ -30,7 +30,7 @@ const PORT = process.env.PORT || 3001; // span_id to every log line so they can be correlated in Grafana. const logger = pino({ level: process.env.LOG_LEVEL || 'info', - base: { service: process.env.OTEL_SERVICE_NAME || 'farmafinder-backend' }, + base: { service: process.env.OTEL_SERVICE_NAME || 'farmaclic-backend' }, }); // Trust the reverse proxy in front of us (Docker/Traefik/nginx/Cloudflare) so @@ -61,7 +61,7 @@ if (PG_URL) { } const sessionConfig = { - secret: process.env.SESSION_SECRET || 'farma-finder-secret-key-change-in-production', + secret: process.env.SESSION_SECRET || 'farma-clic-secret-key-change-in-production', resave: false, saveUninitialized: false, cookie: { @@ -703,7 +703,7 @@ async function nominatimSearchFirstHit(originalQuery) { Accept: 'application/json', 'Accept-Language': 'es,en', 'User-Agent': - 'FarmaFinder/1.0 (pharmacy admin; geocoding; https://github.com/)', + 'FarmaClic/1.0 (pharmacy admin; geocoding; https://github.com/)', }, }); const text = await nomRes.text(); diff --git a/backend/src/tracing.js b/backend/src/tracing.js index 7bb567c..f07a24e 100644 --- a/backend/src/tracing.js +++ b/backend/src/tracing.js @@ -1,8 +1,8 @@ -// OpenTelemetry Node SDK bootstrap for FarmaFinder backend. +// OpenTelemetry Node SDK bootstrap for FarmaClic backend. // Started as a side-effect import from server.js (ESM). // // Env vars (set by docker-compose): -// OTEL_SERVICE_NAME — default: farmafinder-backend +// OTEL_SERVICE_NAME — default: farmaclic-backend // OTEL_EXPORTER_OTLP_ENDPOINT — OTLP gRPC endpoint (e.g. http://alloy:4317) // // Exports traces to the shared Grafana Alloy collector, where they are @@ -15,17 +15,17 @@ import * as resources from '@opentelemetry/resources'; import { ATTR_SERVICE_NAME, ATTR_SERVICE_NAMESPACE } from '@opentelemetry/semantic-conventions'; import { PinoInstrumentation } from '@opentelemetry/instrumentation-pino'; -const serviceName = process.env.OTEL_SERVICE_NAME || 'farmafinder-backend'; +const serviceName = process.env.OTEL_SERVICE_NAME || 'farmaclic-backend'; const otlpEndpoint = process.env.OTEL_EXPORTER_OTLP_ENDPOINT || 'http://localhost:4317'; const resource = typeof resources.resourceFromAttributes === 'function' ? resources.resourceFromAttributes({ [ATTR_SERVICE_NAME]: serviceName, - [ATTR_SERVICE_NAMESPACE]: 'farmafinder', + [ATTR_SERVICE_NAMESPACE]: 'farmaclic', }) : new resources.Resource({ [ATTR_SERVICE_NAME]: serviceName, - [ATTR_SERVICE_NAMESPACE]: 'farmafinder', + [ATTR_SERVICE_NAMESPACE]: 'farmaclic', }); const sdk = new NodeSDK({ diff --git a/capacitor.config.json b/capacitor.config.json index 81b52aa..de77975 100644 --- a/capacitor.config.json +++ b/capacitor.config.json @@ -1,6 +1,6 @@ { - "appId": "net.hacecalor.farmafinder", - "appName": "FarmaFinder", + "appId": "net.hacecalor.farmaclic", + "appName": "FarmaClic", "webDir": "frontend/dist", "server": { "androidScheme": "https" diff --git a/docker-compose.yml b/docker-compose.yml index d47bca8..bc5e24e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,14 +7,14 @@ services: image: postgres:16-alpine restart: unless-stopped environment: - POSTGRES_DB: farmafinder - POSTGRES_USER: farmafinder + POSTGRES_DB: farmaclic + POSTGRES_USER: farmaclic POSTGRES_PASSWORD: ${PG_PASSWORD:-change-me-in-production} volumes: - postgres_data:/var/lib/postgresql/data backend: - image: git.hacecalor.net/ichitux/farmafinder-backend:latest + image: git.hacecalor.net/ichitux/farmaclic-backend:latest build: context: . dockerfile: backend/Dockerfile @@ -25,19 +25,19 @@ services: PORT: "3001" NODE_ENV: production SESSION_SECRET: ${SESSION_SECRET:-change-me-in-production} - CORS_ORIGIN: http://localhost:3000 + CORS_ORIGIN: http://localhost:4000 REDIS_HOST: redis REDIS_PORT: "6379" REDIS_PASSWORD: ${REDIS_PASSWORD:-} DATABASE_PATH: /app/data/database.sqlite FARMACIAS_WEBHOOK_URL: ${FARMACIAS_WEBHOOK_URL:-} - PG_URL: postgresql://farmafinder:${PG_PASSWORD:-change-me-in-production}@postgres:5432/farmafinder + PG_URL: postgresql://farmaclic:${PG_PASSWORD:-change-me-in-production}@postgres:5432/farmaclic # OpenTelemetry — exported via OTLP gRPC to the shared Alloy collector - OTEL_SERVICE_NAME: farmafinder-backend + OTEL_SERVICE_NAME: farmaclic-backend OTEL_EXPORTER_OTLP_ENDPOINT: http://host.docker.internal:4317 OTEL_TRACES_EXPORTER: otlp OTEL_LOGS_EXPORTER: otlp - OTEL_RESOURCE_ATTRIBUTES: service.namespace=farmafinder + OTEL_RESOURCE_ATTRIBUTES: service.namespace=farmaclic volumes: - backend_data:/app/data depends_on: @@ -45,17 +45,17 @@ services: - postgres frontend: - image: git.hacecalor.net/ichitux/farmafinder-frontend:latest + image: git.hacecalor.net/ichitux/farmaclic-frontend:latest build: context: ./frontend args: VITE_FARO_ENDPOINT: ${VITE_FARO_ENDPOINT:-http://localhost:4318} - VITE_FARO_APP_NAME: ${VITE_FARO_APP_NAME:-farmafinder-frontend} + VITE_FARO_APP_NAME: ${VITE_FARO_APP_NAME:-farmaclic-frontend} VITE_FARO_ENV: ${VITE_FARO_ENV:-production} VITE_FARO_APP_VERSION: ${VITE_FARO_APP_VERSION:-1.0.0} restart: unless-stopped ports: - - "3000:80" + - "4000:80" depends_on: - backend diff --git a/frontend/.env.example b/frontend/.env.example index a62779f..d6309f0 100644 --- a/frontend/.env.example +++ b/frontend/.env.example @@ -9,7 +9,7 @@ VITE_FARO_ENDPOINT=http://localhost:4318 # App name shown in Grafana Explore / Faro data source. -VITE_FARO_APP_NAME=farmafinder-frontend +VITE_FARO_APP_NAME=farmaclic-frontend # Environment label (production | staging | development). VITE_FARO_ENV=production diff --git a/frontend/Dockerfile b/frontend/Dockerfile index 4dfa479..5cf0ec9 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -1,6 +1,6 @@ FROM node:20-alpine AS build ARG VITE_FARO_ENDPOINT -ARG VITE_FARO_APP_NAME=farmafinder-frontend +ARG VITE_FARO_APP_NAME=farmaclic-frontend ARG VITE_FARO_ENV=production ARG VITE_FARO_APP_VERSION=1.0.0 ENV VITE_FARO_ENDPOINT=$VITE_FARO_ENDPOINT diff --git a/frontend/index.html b/frontend/index.html index 2ecce58..f3a88e2 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -4,7 +4,7 @@ - FarmaFinder + FarmaClic diff --git a/frontend/package.json b/frontend/package.json index d96dfb0..143fbea 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,5 +1,5 @@ { - "name": "farma-finder-frontend", + "name": "farma-clic-frontend", "version": "1.0.0", "type": "module", "scripts": { diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 0f241a8..a5d80be 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -64,7 +64,7 @@ function App() { } let activeView; - let topBarTitle = 'FarmaFinder'; + let topBarTitle = 'FarmaClic'; let showBack = false; switch (screen) { diff --git a/frontend/src/components/LoginModal.jsx b/frontend/src/components/LoginModal.jsx index b41367e..6182a7a 100644 --- a/frontend/src/components/LoginModal.jsx +++ b/frontend/src/components/LoginModal.jsx @@ -25,7 +25,7 @@ function LoginModal({ onLogin, onClose, initialMode = 'login' }) { const u = username.trim(); if (!u || !password) return; if (mode === 'register' && password.length < 8) { - setError('Password must be at least 8 characters'); + setError('La contraseña debe tener al menos 8 caracteres'); return; } setLoading(true); @@ -40,12 +40,12 @@ function LoginModal({ onLogin, onClose, initialMode = 'login' }) { }); const data = await res.json().catch(() => ({})); if (!res.ok) { - setError(data.error || (mode === 'register' ? 'Could not create account' : 'Login failed')); + setError(data.error || (mode === 'register' ? 'No se pudo crear la cuenta' : 'Inicio de sesión fallido')); } else { onLogin(data.user); } } catch { - setError('Network error — try again'); + setError('Error de red — inténtalo de nuevo'); } finally { setLoading(false); } @@ -71,7 +71,7 @@ function LoginModal({ onLogin, onClose, initialMode = 'login' }) { onClick={() => setMode('login')} disabled={loading} > - Sign in + Iniciar sesión - +

{isRegister - ? 'Save your address and get notified when medicines arrive.' - : 'Sign in to manage your profile and notifications.'} + ? 'Guarda tu dirección y recibe notificaciones cuando lleguen medicamentos.' + : 'Inicia sesión para gestionar tu perfil y notificaciones.'}

- + {isRegister && ( -

3–32 characters: letters, digits, or underscore.

+

3–32 caracteres: letras, dígitos o guión bajo.

)}
- + {isRegister && ( -

At least 8 characters.

+

Al menos 8 caracteres.

)}
{error &&

{error}

} @@ -133,7 +133,7 @@ function LoginModal({ onLogin, onClose, initialMode = 'login' }) { onClick={onClose} disabled={loading} > - Cancel + Cancelar
diff --git a/frontend/src/components/MedicineResults.jsx b/frontend/src/components/MedicineResults.jsx index 6efacc5..5c28627 100644 --- a/frontend/src/components/MedicineResults.jsx +++ b/frontend/src/components/MedicineResults.jsx @@ -11,7 +11,7 @@ function MedicineResults({ medicines, onSelect, query, currentUser, onLoginReque if (medicines.length === 0 && query.length >= 2) { return (
-

No medicines found matching "{query}"

+

No se encontraron medicamentos para "{query}"

); } @@ -49,7 +49,7 @@ function MedicineCard({ medicine, onSelect, currentUser, onLoginRequest }) { return; } if (!supported) { - setError('Notifications need iOS 16.4+ and this site installed as an app (Share → Add to Home Screen).'); + setError('Las notificaciones requieren iOS 16.4+ y este sitio instalado como app (Compartir → Añadir a Pantalla de Inicio).'); return; } if (busy) return; @@ -65,7 +65,7 @@ function MedicineCard({ medicine, onSelect, currentUser, onLoginRequest }) { } } catch (err) { console.error('[notify] toggle failed:', err); - setError(err.message || 'Could not update subscription'); + setError(err.message || 'No se pudo actualizar la suscripción'); } finally { setBusy(false); } @@ -83,29 +83,29 @@ function MedicineCard({ medicine, onSelect, currentUser, onLoginRequest }) { aria-pressed={subscribed && !!currentUser} aria-label={ !currentUser - ? 'Login to enable notifications' + ? 'Inicia sesión para activar notificaciones' : subscribed - ? 'Stop notifications for this medicine' - : 'Notify me when this medicine becomes available' + ? 'Desactivar notificaciones para este medicamento' + : 'Notificarme cuando esté disponible' } title={ !currentUser - ? 'Login to enable notifications' + ? 'Inicia sesión para activar notificaciones' : subscribed - ? 'Notifications on — click to turn off' - : 'Notify me when this medicine is added to a pharmacy' + ? 'Notificaciones activadas — clic para desactivar' + : 'Notificarme cuando este medicamento esté en una farmacia' } > {subscribed && currentUser ? '🔔' : '🔕'}
-

Active Ingredient: {medicine.active_ingredient}

-

Dosage: {medicine.dosage} • Form: {medicine.form}

+

Principio Activo: {medicine.active_ingredient}

+

Dosis: {medicine.dosage} • Forma: {medicine.form}

{error &&

e.stopPropagation()}>{error}

}
- View pharmacies → + Ver farmacias →
); diff --git a/frontend/src/components/PharmacyList.jsx b/frontend/src/components/PharmacyList.jsx index 14d08cd..a59e7aa 100644 --- a/frontend/src/components/PharmacyList.jsx +++ b/frontend/src/components/PharmacyList.jsx @@ -13,7 +13,7 @@ function PharmacyList({ pharmacies, loading, userPosition, medicine, currentUser if (loading) { return (
-

Loading pharmacies...

+

Cargando farmacias...

); } @@ -21,7 +21,7 @@ function PharmacyList({ pharmacies, loading, userPosition, medicine, currentUser if (pharmacies.length === 0) { return (
-

No pharmacies found selling this medicine

+

No se encontraron farmacias con este medicamento

); } @@ -29,7 +29,7 @@ function PharmacyList({ pharmacies, loading, userPosition, medicine, currentUser return (

- Available at {pharmacies.length} {pharmacies.length === 1 ? 'pharmacy' : 'pharmacies'} + Disponible en {pharmacies.length} {pharmacies.length === 1 ? 'farmacia' : 'farmacias'}

{pharmacies.map((pharmacy) => { @@ -76,7 +76,7 @@ function PharmacyCard({ pharmacy, distanceKm, medicine, currentUser, onLoginRequ return; } if (!supported) { - setError('Notifications need iOS 16.4+ and this site installed as an app (Share → Add to Home Screen).'); + setError('Las notificaciones requieren iOS 16.4+ y este sitio instalado como app (Compartir → Añadir a Pantalla de Inicio).'); return; } if (busy) return; @@ -92,7 +92,7 @@ function PharmacyCard({ pharmacy, distanceKm, medicine, currentUser, onLoginRequ } } catch (err) { console.error('[notify] pharmacy toggle failed:', err); - setError(err.message || 'Could not update subscription'); + setError(err.message || 'No se pudo actualizar la suscripción'); } finally { setBusy(false); } @@ -116,18 +116,18 @@ function PharmacyCard({ pharmacy, distanceKm, medicine, currentUser, onLoginRequ disabled={busy} aria-pressed={subscribed && !!currentUser} aria-label={ - !currentUser - ? 'Login to enable notifications' - : subscribed - ? 'Stop notifications for this pharmacy' - : 'Notify me when this medicine arrives at this pharmacy' - } - title={ - !currentUser - ? 'Login to enable notifications' - : subscribed - ? 'Notifications on for this pharmacy — click to turn off' - : 'Notify me when this medicine arrives at this pharmacy' + !currentUser + ? 'Inicia sesión para activar notificaciones' + : subscribed + ? 'Desactivar notificaciones para esta farmacia' + : 'Notificarme cuando llegue a esta farmacia' + } + title={ + !currentUser + ? 'Inicia sesión para activar notificaciones' + : subscribed + ? 'Notificaciones activadas para esta farmacia — clic para desactivar' + : 'Notificarme cuando llegue a esta farmacia' } > {subscribed && currentUser ? '🔔' : '🔕'} @@ -152,7 +152,7 @@ function PharmacyCard({ pharmacy, distanceKm, medicine, currentUser, onLoginRequ )} {pharmacy.stock !== undefined && ( 20 ? 'in-stock' : pharmacy.stock > 0 ? 'low-stock' : 'out-of-stock'}`}> - {pharmacy.stock > 20 ? '✓ In Stock' : pharmacy.stock > 0 ? `⚠ Low Stock (${pharmacy.stock})` : '✗ Out of Stock'} + {pharmacy.stock > 20 ? '✓ En Stock' : pharmacy.stock > 0 ? `⚠ Stock Bajo (${pharmacy.stock})` : '✗ Sin Stock'} )}
diff --git a/frontend/src/components/SavedNotifications.jsx b/frontend/src/components/SavedNotifications.jsx index 74f6547..ef1edca 100644 --- a/frontend/src/components/SavedNotifications.jsx +++ b/frontend/src/components/SavedNotifications.jsx @@ -21,7 +21,7 @@ function SavedNotifications({ onClose }) { ].sort((a, b) => (b.created_at || '').localeCompare(a.created_at || '')); setItems(merged); } catch (err) { - if (!cancelled) setError(err.message || 'Could not load saved notifications'); + if (!cancelled) setError(err.message || 'No se pudieron cargar las notificaciones guardadas'); } finally { if (!cancelled) setLoading(false); } @@ -42,7 +42,7 @@ function SavedNotifications({ onClose }) { if (!res.ok && res.status !== 204) throw new Error(`HTTP ${res.status}`); setItems(prev => prev.filter(i => !(i.scope === item.scope && i.id === item.id))); } catch (err) { - setError(err.message || 'Could not remove notification'); + setError(err.message || 'No se pudo eliminar la notificación'); } finally { setBusyId(null); } @@ -52,15 +52,15 @@ function SavedNotifications({ onClose }) {
e.stopPropagation()}>
-

🔔 Saved Notifications

- +

🔔 Notificaciones Guardadas

+
- {loading &&

Loading…

} + {loading &&

Cargando…

} {!loading && error &&

{error}

} {!loading && !error && items.length === 0 && (

- No saved notifications yet. Tap the 🔕 bell on an out-of-stock pharmacy to get notified when it's restocked. + Aún no hay notificaciones guardadas. Toca la campana 🔕 en una farmacia sin stock para recibir notificaciones cuando se reponga.

)} {!loading && !error && items.length > 0 && ( @@ -77,7 +77,7 @@ function SavedNotifications({ onClose }) { {item.scope === 'pharmacy' ? ( <> - 🏥 {item.pharmacy_name || `Pharmacy #${item.pharmacy_id}`} + 🏥 {item.pharmacy_name || `Farmacia #${item.pharmacy_id}`} {item.pharmacy_address && ( {item.pharmacy_address} @@ -85,7 +85,7 @@ function SavedNotifications({ onClose }) { ) : ( - Any pharmacy + Cualquier farmacia )}
@@ -95,9 +95,9 @@ function SavedNotifications({ onClose }) { className="saved-notifications-remove" onClick={() => handleDelete(item)} disabled={busyId === key} - aria-label="Remove notification" + aria-label="Eliminar notificación" > - {busyId === key ? '…' : 'Remove'} + {busyId === key ? '…' : 'Eliminar'} ); diff --git a/frontend/src/components/TopBar.jsx b/frontend/src/components/TopBar.jsx index 63a802e..899d83b 100644 --- a/frontend/src/components/TopBar.jsx +++ b/frontend/src/components/TopBar.jsx @@ -17,7 +17,7 @@ function TopBar({ title, showBack, onBack, rightAction }) { {showBack ? (

{title}

) : ( - FarmaFinder + FarmaClic )}
{rightAction ||
}
diff --git a/frontend/src/components/admin/LoginForm.jsx b/frontend/src/components/admin/LoginForm.jsx index 4318269..db937e3 100644 --- a/frontend/src/components/admin/LoginForm.jsx +++ b/frontend/src/components/admin/LoginForm.jsx @@ -25,14 +25,14 @@ function LoginForm({ onLogin }) { const data = await response.json(); if (!response.ok) { - throw new Error(data.error || 'Login failed'); + throw new Error(data.error || 'Inicio de sesión fallido'); } // Success - notify parent component onLogin(data.user); } catch (error) { console.error('Login error:', error); - setError(error.message || 'Invalid username or password'); + setError(error.message || 'Usuario o contraseña inválidos'); } finally { setLoading(false); } @@ -42,8 +42,8 @@ function LoginForm({ onLogin }) {
-

🔐 Admin Login

-

Please enter your credentials to access the admin panel

+

🔐 Acceso Administración

+

Introduce tus credenciales para acceder al panel de administración

@@ -54,13 +54,13 @@ function LoginForm({ onLogin }) { )}
- + setUsername(e.target.value)} - placeholder="Enter username" + placeholder="Introduce usuario" required autoFocus disabled={loading} @@ -68,13 +68,13 @@ function LoginForm({ onLogin }) {
- + setPassword(e.target.value)} - placeholder="Enter password" + placeholder="Introduce contraseña" required disabled={loading} /> @@ -85,16 +85,16 @@ function LoginForm({ onLogin }) { className="login-button" disabled={loading || !username || !password} > - {loading ? 'Logging in...' : 'Login'} + {loading ? 'Iniciando sesión...' : 'Iniciar sesión'}

- Default credentials: admin / admin123 + Credenciales por defecto: admin / admin123

- ⚠️ Change the default password after first login! + ⚠️ ¡Cambia la contraseña por defecto tras el primer inicio de sesión!

diff --git a/frontend/src/components/admin/MedicineManagement.jsx b/frontend/src/components/admin/MedicineManagement.jsx index 4497564..bda5c9e 100644 --- a/frontend/src/components/admin/MedicineManagement.jsx +++ b/frontend/src/components/admin/MedicineManagement.jsx @@ -29,7 +29,7 @@ function MedicineManagement() { } catch (error) { if (error.name === 'AbortError') return; console.error('Error searching medicines:', error); - alert('Error searching medicines from CIMA API'); + alert('Error al buscar medicamentos en la API CIMA'); } finally { if (!controller.signal.aborted) setLoading(false); } @@ -44,7 +44,7 @@ function MedicineManagement() { return (
-

Search Medicines (CIMA API)

+

Buscar Medicamentos (API CIMA)

@@ -54,7 +54,7 @@ function MedicineManagement() {
- +
- {loading &&
Searching CIMA API...
} + {loading &&
Buscando en API CIMA...
} {!loading && medicines.length > 0 && (
-

Found {medicines.length} medicines

+

Se encontraron {medicines.length} medicamentos

{medicines.map((medicine) => (
diff --git a/frontend/src/components/admin/PharmacyManagement.jsx b/frontend/src/components/admin/PharmacyManagement.jsx index 98524ce..317b984 100644 --- a/frontend/src/components/admin/PharmacyManagement.jsx +++ b/frontend/src/components/admin/PharmacyManagement.jsx @@ -56,10 +56,10 @@ function haversineMeters(lat1, lon1, lat2, lon2) { } const REGION_PRESETS = [ - { id: 'custom', label: 'Custom coordinates', lat: '', lon: '', radio: '' }, + { id: 'custom', label: 'Coordenadas personalizadas', lat: '', lon: '', radio: '' }, { id: 'rubi', - label: 'Example: Rubí area (1.5 km)', + label: 'Ejemplo: Área de Rubí (1.5 km)', lat: '41.5631', lon: '2.0038', radio: '1500', @@ -76,16 +76,16 @@ async function geocodeErrorMessage(response) { } if (typeof body.error === 'string' && body.error.trim()) return body.error; if (response.status === 401) { - return 'Session expired or not logged in. Sign in again on Admin, then retry.'; + return 'Sesión expirada o no has iniciado sesión. Inicia sesión de nuevo en el panel Admin y reintenta.'; } if (response.status === 404) { const looksLikeHtml = /]/i.test(text || ''); if (looksLikeHtml) { - return 'The app could not reach the API (404). Use http://localhost:3000 with both frontend and backend running, or configure your server to proxy /api to the backend.'; + return 'La app no pudo conectar con la API (404). Usa http://localhost:3000 con frontend y backend activos.'; } - return 'Geocode service not found. Update the backend and restart it.'; + return 'Servicio de geocodificación no encontrado. Actualiza el backend y reinícialo.'; } - return `Lookup failed (HTTP ${response.status}).`; + return `Búsqueda fallida (HTTP ${response.status}).`; } function PharmacyManagement() { @@ -132,7 +132,7 @@ function PharmacyManagement() { setPharmacies(data); } catch (error) { console.error('Error fetching pharmacies:', error); - alert('Error loading pharmacies'); + alert('Error al cargar farmacias'); } finally { setLoading(false); } @@ -165,7 +165,7 @@ function PharmacyManagement() { e?.preventDefault(); const q = cityQuery.trim(); if (!q) { - setCityLookupMessage({ type: 'err', text: 'Enter a city or place name.' }); + setCityLookupMessage({ type: 'err', text: 'Introduce una ciudad o lugar.' }); return; } setCityLookupLoading(true); @@ -253,7 +253,7 @@ function PharmacyManagement() { const lon = parseFloat(regionLon); const radio = parseFloat(regionRadio); if (!Number.isFinite(lat) || !Number.isFinite(lon) || !Number.isFinite(radio)) { - throw new Error('Set latitude, longitude and radius (use Find city or a preset).'); + throw new Error('Establece latitud, longitud y radio (usa Buscar ciudad o un preset).'); } const response = await fetch('/api/admin/pharmacies/import-external', { method: 'POST', @@ -307,7 +307,7 @@ function PharmacyManagement() { if (!response.ok) { const error = await response.json(); - throw new Error(error.error || 'Failed to update pharmacy'); + throw new Error(error.error || 'Error al actualizar farmacia'); } } else { const response = await fetch('/api/admin/pharmacies', { @@ -319,16 +319,16 @@ function PharmacyManagement() { if (!response.ok) { const error = await response.json(); - throw new Error(error.error || 'Failed to create pharmacy'); + throw new Error(error.error || 'Error al crear farmacia'); } } resetForm(); fetchPharmacies(); - alert(editingPharmacy ? 'Pharmacy updated successfully!' : 'Pharmacy added successfully!'); + alert(editingPharmacy ? '¡Farmacia actualizada!' : '¡Farmacia añadida!'); } catch (error) { console.error('Error saving pharmacy:', error); - alert(`Error saving pharmacy: ${error.message}`); + alert(`Error al guardar farmacia: ${error.message}`); } finally { setSaving(false); } @@ -348,7 +348,7 @@ function PharmacyManagement() { }; const handleDelete = async (id) => { - if (!confirm('Are you sure you want to delete this pharmacy?')) return; + if (!confirm('¿Estás seguro de que quieres eliminar esta farmacia?')) return; try { const response = await fetch(`/api/admin/pharmacies/${id}`, { @@ -356,13 +356,13 @@ function PharmacyManagement() { credentials: 'include', }); - if (!response.ok) throw new Error('Failed to delete pharmacy'); + if (!response.ok) throw new Error('Error al eliminar farmacia'); fetchPharmacies(); - alert('Pharmacy deleted successfully!'); + alert('¡Farmacia eliminada!'); } catch (error) { console.error('Error deleting pharmacy:', error); - alert('Error deleting pharmacy'); + alert('Error al eliminar farmacia'); } }; @@ -391,7 +391,7 @@ function PharmacyManagement() { return (
-

Manage Pharmacies

+

Gestionar Farmacias

-

City, region & import

+

Ciudad, región e importación

- Find city sets latitude, longitude and radius for the map filter and for imports. - Choose a data source below: OpenStreetMap is free (no key);{' '} - Open data URL loads JSON you host (array or GeoJSON). Geocoding uses{' '} + Buscar ciudad establece latitud, longitud y radio para el filtro de mapa y las importaciones. + Elige una fuente de datos: OpenStreetMap es gratuita (sin clave);{' '} + URL de datos abiertos carga JSON alojado por ti. Geocodificación usa{' '} OpenStreetMap {' '} @@ -418,11 +418,11 @@ function PharmacyManagement() {

- + { setCityQuery(e.target.value); @@ -436,7 +436,7 @@ function PharmacyManagement() { className="btn-secondary city-lookup-submit" disabled={cityLookupLoading} > - {cityLookupLoading ? 'Looking up…' : 'Look up city'} + {cityLookupLoading ? 'Buscando…' : 'Buscar ciudad'} {cityLookupMessage && ( @@ -449,7 +449,7 @@ function PharmacyManagement() { )}
- +
- +
- +
- +
{importMode === 'openData' && (
- + {importing - ? 'Importing…' + ? 'Importando…' : importMode === 'webhook' - ? 'Import from webhook' + ? 'Importar desde webhook' : importMode === 'openData' - ? 'Import from URL' - : `Import from ${importMode === 'osm' ? 'Overpass' : 'OpenStreetMap'}`} + ? 'Importar desde URL' + : `Importar desde ${importMode === 'osm' ? 'Overpass' : 'OpenStreetMap'}`}
@@ -568,10 +568,10 @@ function PharmacyManagement() { {showForm && (
-

{editingPharmacy ? 'Edit Pharmacy' : 'Add New Pharmacy'}

+

{editingPharmacy ? 'Editar Farmacia' : 'Añadir Nueva Farmacia'}

- +
- +
- +
- +
- +
- Opening hours -

Leave a day checked as Closed if the pharmacy doesn't open that day.

+ Horario de apertura +

Marca un día como Cerrado si la farmacia no abre ese día.

{DAY_KEYS.map((day) => { const d = hoursDraft[day]; return ( @@ -635,14 +635,14 @@ function PharmacyManagement() { checked={d.closed} onChange={(e) => updateDay(day, { closed: e.target.checked })} /> - Closed + Cerrado updateDay(day, { open: e.target.value })} - aria-label={`${DAY_LABEL[day]} opens at`} + aria-label={`${DAY_LABEL[day]} abre a las`} /> updateDay(day, { close: e.target.value })} - aria-label={`${DAY_LABEL[day]} closes at`} + aria-label={`${DAY_LABEL[day]} cierra a las`} />
); @@ -659,28 +659,28 @@ function PharmacyManagement() {
)} {loading ? ( -
Loading pharmacies...
+
Cargando farmacias...
) : (

- Showing {displayedPharmacies.length} of {pharmacies.length} pharmacies - {filterByRegion && ' (inside radius)'} + Mostrando {displayedPharmacies.length} de {pharmacies.length} farmacias + {filterByRegion && ' (dentro del radio)'}

{displayedPharmacies.length === 0 ? (

{pharmacies.length === 0 - ? 'No pharmacies yet. Import from webhook or add one manually.' - : 'No pharmacies in this radius with coordinates. Widen the radius, look up a different city, or turn off the region filter.'} + ? 'Aún no hay farmacias. Importa desde webhook o añade una manualmente.' + : 'No hay farmacias en este radio con coordenadas. Amplía el radio, busca otra ciudad o desactiva el filtro de región.'}

) : ( displayedPharmacies.map((pharmacy) => ( @@ -697,10 +697,10 @@ function PharmacyManagement() {
diff --git a/frontend/src/components/admin/PharmacyMedicineLink.jsx b/frontend/src/components/admin/PharmacyMedicineLink.jsx index d770eaf..664cb1b 100644 --- a/frontend/src/components/admin/PharmacyMedicineLink.jsx +++ b/frontend/src/components/admin/PharmacyMedicineLink.jsx @@ -111,7 +111,7 @@ function PharmacyMedicineLink() { e.preventDefault(); if (!selectedMedicine) { - alert('Please select a medicine first'); + alert('Por favor, selecciona un medicamento primero'); return; } @@ -131,16 +131,16 @@ function PharmacyMedicineLink() { body: JSON.stringify(payload) }); - if (!response.ok) throw new Error('Failed to link medicine to pharmacy'); + if (!response.ok) throw new Error('Error al vincular medicamento a farmacia'); resetForm(); if (selectedPharmacy) { fetchPharmacyMedicines(selectedPharmacy.id); } - alert('Medicine linked to pharmacy successfully!'); + alert('¡Medicamento vinculado a la farmacia correctamente!'); } catch (error) { console.error('Error linking medicine:', error); - alert('Error linking medicine to pharmacy'); + alert('Error al vincular medicamento a farmacia'); } }; @@ -153,18 +153,18 @@ function PharmacyMedicineLink() { body: JSON.stringify({ price, stock }) }); - if (!response.ok) throw new Error('Failed to update'); + if (!response.ok) throw new Error('Error al actualizar'); fetchPharmacyMedicines(selectedPharmacy.id); - alert('Updated successfully!'); + alert('¡Actualizado correctamente!'); } catch (error) { console.error('Error updating:', error); - alert('Error updating'); + alert('Error al actualizar'); } }; const handleDelete = async (id) => { - if (!confirm('Remove this medicine from the pharmacy?')) return; + if (!confirm('¿Eliminar este medicamento de la farmacia?')) return; try { const response = await fetch(`/api/admin/pharmacy-medicines/${id}`, { @@ -172,13 +172,13 @@ function PharmacyMedicineLink() { credentials: 'include' }); - if (!response.ok) throw new Error('Failed to delete'); + if (!response.ok) throw new Error('Error al eliminar'); fetchPharmacyMedicines(selectedPharmacy.id); - alert('Medicine removed from pharmacy!'); + alert('¡Medicamento eliminado de la farmacia!'); } catch (error) { console.error('Error deleting:', error); - alert('Error removing medicine'); + alert('Error al eliminar medicamento'); } }; @@ -216,11 +216,11 @@ function PharmacyMedicineLink() { return (
-

Link Medicine to Pharmacy

+

Vincular Medicamento a Farmacia

- + setPharmacyDropdownOpen(true)} onBlur={() => setTimeout(() => setPharmacyDropdownOpen(false), 150)} - placeholder={pharmacies.length ? `Search ${pharmacies.length} pharmacies by name or address…` : 'Loading pharmacies…'} + placeholder={pharmacies.length ? `Buscar entre ${pharmacies.length} farmacias por nombre o dirección…` : 'Cargando farmacias…'} autoComplete="off" required={!selectedPharmacy} /> @@ -243,7 +243,7 @@ function PharmacyMedicineLink() {
{filteredPharmacies.length === 0 ? (
- No pharmacies match "{pharmacyQuery}" + No hay farmacias que coincidan con "{pharmacyQuery}"
) : ( filteredPharmacies.map((pharmacy) => ( @@ -264,14 +264,14 @@ function PharmacyMedicineLink() {

✅ Selected: {selectedPharmacy.name}

{selectedPharmacy.address}

)}
- + - {searching &&

Searching...

} + {searching &&

Buscando...

} {medicineResults.length > 0 && !selectedMedicine && (
@@ -316,7 +316,7 @@ function PharmacyMedicineLink() { setMedicineSearch(''); }} > - Change medicine + Cambiar medicamento
)} @@ -324,7 +324,7 @@ function PharmacyMedicineLink() {
- +
{selectedPharmacy && (
-

Medicines at {selectedPharmacy.name}

+

Medicamentos en {selectedPharmacy.name}

{loading ? ( -
Loading...
+
Cargando...
) : pharmacyMedicines.length === 0 ? ( -

No medicines linked to this pharmacy yet.

+

Aún no hay medicamentos vinculados a esta farmacia.

) : (
{pharmacyMedicines.map((pm) => ( @@ -369,7 +369,7 @@ function PharmacyMedicineLink() {

{pm.medicine_name}

- Price: {pm.price ? `€${parseFloat(pm.price).toFixed(2)}` : 'Not set'} • + Precio: {pm.price ? `€${parseFloat(pm.price).toFixed(2)}` : 'No definido'} • Stock: {pm.stock || 0}

@@ -377,17 +377,17 @@ function PharmacyMedicineLink() {
diff --git a/frontend/src/sw.js b/frontend/src/sw.js index fefec6b..edc6036 100644 --- a/frontend/src/sw.js +++ b/frontend/src/sw.js @@ -14,10 +14,10 @@ self.addEventListener('push', (event) => { try { data = event.data.json(); } catch { - data = { title: 'FarmaFinder', body: event.data.text() }; + data = { title: 'FarmaClic', body: event.data.text() }; } } - const title = data.title || 'FarmaFinder'; + const title = data.title || 'FarmaClic'; const options = { body: data.body || '', icon: '/icon.svg', diff --git a/frontend/src/utils/faro.js b/frontend/src/utils/faro.js index c7322f3..50bc81e 100644 --- a/frontend/src/utils/faro.js +++ b/frontend/src/utils/faro.js @@ -4,7 +4,7 @@ // // Required env vars (Vite injects VITE_* at build time): // VITE_FARO_ENDPOINT — e.g. http://localhost:4318 -// VITE_FARO_APP_NAME — e.g. farmafinder-frontend +// VITE_FARO_APP_NAME — e.g. farmaclic-frontend // VITE_FARO_ENV — e.g. production | staging | development // VITE_FARO_APP_VERSION — optional, defaults to 1.0.0 @@ -29,7 +29,7 @@ export function initFaro() { return; } - const appName = import.meta.env.VITE_FARO_APP_NAME || 'farmafinder-frontend'; + const appName = import.meta.env.VITE_FARO_APP_NAME || 'farmaclic-frontend'; const environment = import.meta.env.VITE_FARO_ENV || 'production'; const version = import.meta.env.VITE_FARO_APP_VERSION || '1.0.0'; diff --git a/frontend/src/utils/geo.js b/frontend/src/utils/geo.js index 81d3ae1..5acdaaa 100644 --- a/frontend/src/utils/geo.js +++ b/frontend/src/utils/geo.js @@ -13,11 +13,11 @@ export function haversineKm(lat1, lon1, lat2, lon2) { export function getUserPosition() { return new Promise((resolve, reject) => { if (!navigator.geolocation) { - reject(new Error('Geolocation not supported by this browser')); + reject(new Error('Geolocalización no compatible con este navegador')); return; } if (typeof window !== 'undefined' && window.isSecureContext === false) { - reject(new Error('Geolocation requires HTTPS (or localhost)')); + reject(new Error('La geolocalización requiere HTTPS (o localhost)')); return; } navigator.geolocation.getCurrentPosition( diff --git a/frontend/src/utils/hours.js b/frontend/src/utils/hours.js index 3fecb3e..15bfbc8 100644 --- a/frontend/src/utils/hours.js +++ b/frontend/src/utils/hours.js @@ -1,12 +1,12 @@ const DAYS = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat']; const DAY_LABELS = { - sun: 'Sunday', - mon: 'Monday', - tue: 'Tuesday', - wed: 'Wednesday', - thu: 'Thursday', - fri: 'Friday', - sat: 'Saturday', + sun: 'Domingo', + mon: 'Lunes', + tue: 'Martes', + wed: 'Miércoles', + thu: 'Jueves', + fri: 'Viernes', + sat: 'Sábado', }; export const DAY_KEYS = DAYS; @@ -33,8 +33,8 @@ function findNextOpen(hours, now) { const range = hours[day]; if (Array.isArray(range) && range.length === 2) { const openStr = range[0]; - if (offset === 1) return `tomorrow at ${openStr}`; - return `${DAY_LABELS[day]} at ${openStr}`; + if (offset === 1) return `mañana a las ${openStr}`; + return `${DAY_LABELS[day]} a las ${openStr}`; } } return null; @@ -49,7 +49,7 @@ export function getOpenStatus(rawHours, now = new Date()) { if (!Array.isArray(range) || range.length !== 2) { const next = findNextOpen(hours, now); - return { status: 'closed', label: next ? `Closed · Opens ${next}` : 'Closed' }; + return { status: 'closed', label: next ? `Cerrado · Abre ${next}` : 'Cerrado' }; } const openMins = parseHM(range[0]); @@ -59,13 +59,13 @@ export function getOpenStatus(rawHours, now = new Date()) { const nowMins = now.getHours() * 60 + now.getMinutes(); if (nowMins < openMins) { - return { status: 'closed', label: `Closed · Opens at ${range[0]}` }; + return { status: 'closed', label: `Cerrado · Abre a las ${range[0]}` }; } if (nowMins >= closeMins) { const next = findNextOpen(hours, now); - return { status: 'closed', label: next ? `Closed · Opens ${next}` : 'Closed' }; + return { status: 'closed', label: next ? `Cerrado · Abre ${next}` : 'Cerrado' }; } - return { status: 'open', label: `Open · Closes at ${range[1]}` }; + return { status: 'open', label: `Abierto · Cierra a las ${range[1]}` }; } export function emptyHours() { diff --git a/frontend/src/utils/notifications.js b/frontend/src/utils/notifications.js index 849cac7..9bfd7b7 100644 --- a/frontend/src/utils/notifications.js +++ b/frontend/src/utils/notifications.js @@ -1,4 +1,4 @@ -const STORAGE_KEY = 'farmafinder.subscriptions.v1'; +const STORAGE_KEY = 'farmaclic.subscriptions.v1'; export function getStoredSubscriptions() { try { @@ -38,12 +38,12 @@ export async function getVapidPublicKey() { const res = await fetch('/api/notifications/vapid-public-key'); if (!res.ok) { const message = res.status === 503 - ? 'Push notifications are not configured on the server yet' - : `Could not fetch VAPID key (HTTP ${res.status})`; + ? 'Las notificaciones push no están configuradas en el servidor' + : `No se pudo obtener la clave VAPID (HTTP ${res.status})`; throw new Error(message); } const { publicKey } = await res.json(); - if (!publicKey) throw new Error('Server returned an empty VAPID key'); + if (!publicKey) throw new Error('El servidor devolvió una clave VAPID vacía'); return publicKey; } @@ -58,17 +58,17 @@ export function urlBase64ToUint8Array(base64String) { async function getOrCreateSubscription() { if (!pushSupported()) { - throw new Error('Push notifications are not supported in this browser'); + throw new Error('Las notificaciones push no son compatibles con este navegador'); } if (window.isSecureContext === false) { - throw new Error('Push notifications require HTTPS (or localhost)'); + throw new Error('Las notificaciones push requieren HTTPS (o localhost)'); } const reg = await navigator.serviceWorker.ready; let sub = await reg.pushManager.getSubscription(); if (sub) return sub; if (Notification.permission === 'denied') { - throw new Error('Notification permission was denied — enable it in browser settings'); + throw new Error('Permiso de notificaciones denegado — actívalo en la configuración del navegador'); } if (Notification.permission === 'default') { const result = await Notification.requestPermission(); @@ -98,7 +98,7 @@ export async function subscribeToPush(medicineNregistro, medicineName, pharmacyI }); if (!res.ok) { const detail = await res.text().catch(() => ''); - throw new Error(`Subscribe failed (HTTP ${res.status}) ${detail}`); + throw new Error(`Suscripción fallida (HTTP ${res.status}) ${detail}`); } setStoredSubscriptions([...getStoredSubscriptions(), storageKey(medicineNregistro, pharmacyId)]); } diff --git a/frontend/src/views/AdminView.jsx b/frontend/src/views/AdminView.jsx index 3f313fc..acacce5 100644 --- a/frontend/src/views/AdminView.jsx +++ b/frontend/src/views/AdminView.jsx @@ -59,7 +59,7 @@ function AdminView() { if (loading) { return (
-
Checking authentication...
+
Comprobando autenticación...
); } @@ -68,8 +68,8 @@ function AdminView() { return ( <>
-

⚙️ Admin Panel

-

Authentication required

+

⚙️ Panel de Administración

+

Autenticación requerida

@@ -83,13 +83,13 @@ function AdminView() {
-

⚙️ Admin Panel

-

Manage pharmacies and medicines

+

⚙️ Panel de Administración

+

Gestiona farmacias y medicamentos

👤 {user?.username}
@@ -101,19 +101,19 @@ function AdminView() { className={`admin-tab ${activeTab === 'pharmacies' ? 'active' : ''}`} onClick={() => setActiveTab('pharmacies')} > - 🏥 Pharmacies + 🏥 Farmacias
diff --git a/frontend/src/views/HomeView.css b/frontend/src/views/HomeView.css index e7fe927..42ac374 100644 --- a/frontend/src/views/HomeView.css +++ b/frontend/src/views/HomeView.css @@ -20,16 +20,26 @@ .home-logo-wrapper { display: flex; + flex-direction: row; align-items: center; justify-content: center; + gap: 1rem; margin-bottom: 0.5rem; } .home-logo { - width: min(16rem, 70vw); + width: min(10rem, 50vw); height: auto; } +.home-brand-name { + font-size: 2rem; + font-weight: 800; + color: var(--primary); + letter-spacing: -0.02em; + line-height: 1; +} + @media (max-height: 700px) { .home-logo { width: min(12rem, 60vw); diff --git a/frontend/src/views/HomeView.jsx b/frontend/src/views/HomeView.jsx index f82cf05..fd6c16f 100644 --- a/frontend/src/views/HomeView.jsx +++ b/frontend/src/views/HomeView.jsx @@ -6,7 +6,8 @@ function HomeView({ onScanClick, onSearchClick }) {
- FarmaFinder + FarmaClic +

FarmaClic

Encuentra tus medicamentos en farmacias cercanas

diff --git a/frontend/src/views/PublicView.jsx b/frontend/src/views/PublicView.jsx index a323aff..1124e52 100644 --- a/frontend/src/views/PublicView.jsx +++ b/frontend/src/views/PublicView.jsx @@ -108,12 +108,12 @@ function PublicView({ console.error('[location] error', err); let msg; if (err && typeof err.code === 'number') { - if (err.code === 1) msg = 'Location permission denied — allow it in your browser settings'; - else if (err.code === 2) msg = 'Location unavailable — check OS location services / WiFi'; - else if (err.code === 3) msg = 'Location request timed out — try again'; - else msg = `Geolocation error (code ${err.code})`; + if (err.code === 1) msg = 'Permiso de ubicación denegado — actívalo en la configuración del navegador'; + else if (err.code === 2) msg = 'Ubicación no disponible — comprueba los servicios de ubicación/WiFi'; + else if (err.code === 3) msg = 'La solicitud de ubicación expiró — inténtalo de nuevo'; + else msg = `Error de geolocalización (código ${err.code})`; } else { - msg = err && err.message ? err.message : 'Could not get your location'; + msg = err && err.message ? err.message : 'No se pudo obtener tu ubicación'; } setLocationError(msg); } finally { @@ -172,22 +172,22 @@ function PublicView({ setPharmacies([]); setScreen('home'); }} - aria-label="Back to home" + aria-label="Volver al inicio" > - ← Home + ← Inicio -

💊 FarmaFinder

-

Find your medicine at nearby pharmacies

+

💊 FarmaClic

+

Encuentra tu medicamento en farmacias cercanas

- {loading &&
Searching...
} + {loading &&
Buscando...
} {searchQuery && !selectedMedicine && (

{selectedMedicine.name}

- Active Ingredient: {selectedMedicine.active_ingredient} - Dosage: {selectedMedicine.dosage} - Form: {selectedMedicine.form} + Principio Activo: {selectedMedicine.active_ingredient} + Dosis: {selectedMedicine.dosage} + Forma: {selectedMedicine.form}
@@ -227,15 +227,15 @@ function PublicView({ disabled={locating} > {locating - ? '📍 Locating…' + ? '📍 Localizando…' : sortByDistance - ? '📍 Sorted by distance · Reset' + ? '📍 Ordenado por distancia · Reset' : hasSavedCoords - ? '📍 Sort by your saved location' - : '📍 Sort by distance'} + ? '📍 Ordenar por ubicación guardada' + : '📍 Ordenar por distancia'} {sortByDistance && positionSource === 'profile' && ( - Using your saved address + Usando tu dirección guardada )} {locationError && ( {locationError} diff --git a/frontend/vite.config.js b/frontend/vite.config.js index f513b50..39f5dd9 100644 --- a/frontend/vite.config.js +++ b/frontend/vite.config.js @@ -17,9 +17,9 @@ export default defineConfig({ navigateFallback: 'index.html', }, manifest: { - name: 'FarmaFinder', - short_name: 'FarmaFinder', - description: 'Find pharmacies that stock the medicine you need', + name: 'FarmaClic', + short_name: 'FarmaClic', + description: 'Encuentra medicamentos en farmacias cercanas', theme_color: '#00450d', background_color: '#f8fafb', display: 'standalone', diff --git a/package.json b/package.json index 78688a5..22ec9c2 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { - "name": "farma-finder", + "name": "farma-clic", "version": "1.0.0", - "description": "FarmaFinder - Full stack application", + "description": "FarmaClic - Full stack application", "private": true, "scripts": { "dev": "npm-run-all --parallel dev:backend dev:frontend",