diff --git a/admin/src/app/settings/page.tsx b/admin/src/app/settings/page.tsx index 8edcdba..4a78f3c 100644 --- a/admin/src/app/settings/page.tsx +++ b/admin/src/app/settings/page.tsx @@ -5,7 +5,8 @@ import { api } from '@/lib/api'; import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { toast } from 'sonner'; -import { Save, ExternalLink, Copy, FileText, Shield } from 'lucide-react'; +import { Input } from '@/components/ui/input'; +import { Save, ExternalLink, Copy, FileText, Shield, Map, Eye, EyeOff, CheckCircle2, XCircle } from 'lucide-react'; const API_BASE = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3000/api/v1'; const PUBLIC_BASE = API_BASE.replace('/api/v1', ''); @@ -30,16 +31,43 @@ export default function SettingsPage() { const [saving, setSaving] = useState(null); const [loading, setLoading] = useState(true); + // Maps key state + const [mapsConfigured, setMapsConfigured] = useState(false); + const [mapsKey, setMapsKey] = useState(''); + const [showMapsKey, setShowMapsKey] = useState(false); + const [savingMaps, setSavingMaps] = useState(false); + const load = useCallback(() => { setLoading(true); - api.get<{ privacy: string | null; terms: string | null }>('/settings/policies') - .then((data) => setPolicies({ privacy: data.privacy || '', terms: data.terms || '' })) - .catch(() => toast.error('Error al cargar políticas')) + Promise.all([ + api.get<{ privacy: string | null; terms: string | null }>('/settings/policies'), + api.get<{ configured: boolean; api_key: string }>('/settings/maps-key'), + ]) + .then(([policiesData, mapsData]) => { + setPolicies({ privacy: policiesData.privacy || '', terms: policiesData.terms || '' }); + setMapsConfigured(mapsData.configured); + }) + .catch(() => toast.error('Error al cargar configuración')) .finally(() => setLoading(false)); }, []); useEffect(() => { load(); }, [load]); + const saveMapsKey = async () => { + if (!mapsKey.trim()) return toast.error('Ingresa una API Key'); + setSavingMaps(true); + try { + await api.patch('/settings/maps', { api_key: mapsKey.trim() }); + toast.success('Google Maps API Key guardada'); + setMapsKey(''); + setMapsConfigured(true); + } catch (e: any) { + toast.error(e?.message || 'Error al guardar'); + } finally { + setSavingMaps(false); + } + }; + const savePolicy = async (key: string) => { setSaving(key); try { @@ -67,6 +95,54 @@ export default function SettingsPage() {

Gestiona las políticas legales y ajustes globales de ProsApp.

+ {/* Google Maps */} + + + + + Google Maps API Key + + + Requerida para mostrar mapas y detectar la ubicación en la app web y móvil. + Habilita Maps JavaScript API y Geocoding API en Google Cloud Console. + + + +
+ {mapsConfigured ? ( + <>Configurada + ) : ( + <>No configurada + )} +
+
+
+ setMapsKey(e.target.value)} + className="pr-10 font-mono" + /> + +
+ +
+

+ La app web carga la key desde el backend al abrir el mapa. Agrega restricción HTTP en Google Cloud: https://app.prosapp.co/* +

+
+
+ {/* Policies */}

Documentos legales

diff --git a/backend/src/settings/settings.controller.ts b/backend/src/settings/settings.controller.ts index 44a663e..4207b91 100644 --- a/backend/src/settings/settings.controller.ts +++ b/backend/src/settings/settings.controller.ts @@ -29,6 +29,19 @@ export class SettingsController { return this.settings.updatePolicy(key, body.content); } + // Maps key + @Get('maps-key') + async getMapsKey() { + const api_key = await this.settings.getMapsKey(); + return { configured: !!api_key, api_key: api_key ?? '' }; + } + + @Patch('maps') + @UseGuards(JwtAuthGuard) @ApiBearerAuth() + saveMapsKey(@Body() body: { api_key: string }) { + return this.settings.saveMapsKey(body.api_key); + } + // Public policy pages @Get('policy/:key') async getPublicPolicy(@Param('key') key: string, @Res() res: Response) { diff --git a/backend/src/settings/settings.service.ts b/backend/src/settings/settings.service.ts index c3ba125..0247058 100644 --- a/backend/src/settings/settings.service.ts +++ b/backend/src/settings/settings.service.ts @@ -41,4 +41,17 @@ export class SettingsService { ]); return { privacy, terms }; } + + async getMapsKey(): Promise { + const setting = await this.prisma.settings.findUnique({ where: { key: 'maps_config' } }); + return (setting?.value as any)?.api_key ?? null; + } + + async saveMapsKey(api_key: string) { + return this.prisma.settings.upsert({ + where: { key: 'maps_config' }, + create: { key: 'maps_config', value: { api_key } }, + update: { value: { api_key } }, + }); + } }