- Integração Mapbox GL JS para seleção interativa de localização - Mapa arrastável com pin para localização exata - Geocoding e reverse geocoding automático - Busca de endereços com autocomplete - Campos editáveis que atualizam mapa automaticamente - Token configurado via variável de ambiente (.env.local) - Sistema de upload de fotos de fotógrafos - Upload via input de arquivo (substituiu URL) - Preview automático com FileReader API - Botão para remover foto selecionada - Placeholder com ícone de câmera - Remoção de funcionalidades de uploads/álbuns - Removida página Albums.tsx - Removido sistema de attachments - Removida aba Inspiração para empresas - Criada página Inspiração com galeria de exemplo - Melhorias de responsividade - Cards do mapa adaptados para mobile - Texto e padding reduzidos em telas pequenas - Arquivos de configuração - .env.example criado - vite-env.d.ts para tipagem - MAPBOX_SETUP.md com instruções - Footer atualizado com serviços universitários
149 lines
4.5 KiB
TypeScript
149 lines
4.5 KiB
TypeScript
// Mapbox Geocoding Service
|
|
// Docs: https://docs.mapbox.com/api/search/geocoding/
|
|
|
|
export interface MapboxFeature {
|
|
id: string;
|
|
place_name: string;
|
|
center: [number, number]; // [longitude, latitude]
|
|
geometry: {
|
|
coordinates: [number, number];
|
|
};
|
|
context?: Array<{
|
|
id: string;
|
|
text: string;
|
|
short_code?: string;
|
|
}>;
|
|
place_type: string[];
|
|
text: string;
|
|
address?: string;
|
|
}
|
|
|
|
export interface MapboxResult {
|
|
description: string;
|
|
street: string;
|
|
number: string;
|
|
city: string;
|
|
state: string;
|
|
zip: string;
|
|
lat: number;
|
|
lng: number;
|
|
mapLink: string;
|
|
}
|
|
|
|
// Token do Mapbox configurado no arquivo .env.local
|
|
const MAPBOX_TOKEN = import.meta.env.VITE_MAPBOX_TOKEN || '';
|
|
|
|
/**
|
|
* Busca endereços usando a API de Geocoding do Mapbox
|
|
* @param query - Texto de busca (endereço, local, etc)
|
|
* @param country - Código do país (ex: 'br' para Brasil)
|
|
*/
|
|
export async function searchMapboxLocation(
|
|
query: string,
|
|
country: string = 'br'
|
|
): Promise<MapboxResult[]> {
|
|
if (!MAPBOX_TOKEN || MAPBOX_TOKEN.startsWith('YOUR_')) {
|
|
console.warn('⚠️ Mapbox Token não configurado. Configure em services/mapboxService.ts');
|
|
return [];
|
|
}
|
|
|
|
try {
|
|
const encodedQuery = encodeURIComponent(query);
|
|
const url = `https://api.mapbox.com/geocoding/v5/mapbox.places/${encodedQuery}.json?` +
|
|
`access_token=${MAPBOX_TOKEN}&` +
|
|
`country=${country}&` +
|
|
`language=pt&` +
|
|
`limit=5`;
|
|
|
|
console.log('🔍 Buscando endereço:', query);
|
|
const response = await fetch(url);
|
|
|
|
if (!response.ok) {
|
|
console.error('❌ Erro na API Mapbox:', response.statusText);
|
|
throw new Error(`Mapbox API error: ${response.statusText}`);
|
|
}
|
|
|
|
const data = await response.json();
|
|
console.log('✅ Resultados encontrados:', data.features?.length || 0);
|
|
|
|
if (!data.features || data.features.length === 0) {
|
|
return [];
|
|
}
|
|
|
|
return data.features.map((feature: MapboxFeature) => {
|
|
// Extrair informações do contexto
|
|
const context = feature.context || [];
|
|
const place = context.find(c => c.id.startsWith('place'));
|
|
const region = context.find(c => c.id.startsWith('region'));
|
|
const postcode = context.find(c => c.id.startsWith('postcode'));
|
|
|
|
// Extrair número do endereço
|
|
const addressMatch = feature.address || feature.text.match(/\d+/)?.[0] || '';
|
|
|
|
return {
|
|
description: feature.place_name,
|
|
street: feature.text,
|
|
number: addressMatch,
|
|
city: place?.text || '',
|
|
state: region?.short_code?.replace('BR-', '') || region?.text || '',
|
|
zip: postcode?.text || '',
|
|
lat: feature.center[1],
|
|
lng: feature.center[0],
|
|
mapLink: `https://www.google.com/maps/search/?api=1&query=${feature.center[1]},${feature.center[0]}`
|
|
};
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('Erro ao buscar localização no Mapbox:', error);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Busca reversa: converte coordenadas em endereço
|
|
* Retorna o endereço completo baseado nas coordenadas do pin
|
|
*/
|
|
export async function reverseGeocode(lat: number, lng: number): Promise<MapboxResult | null> {
|
|
if (!MAPBOX_TOKEN || MAPBOX_TOKEN.startsWith('YOUR_')) {
|
|
console.warn('⚠️ Mapbox Token não configurado');
|
|
return null;
|
|
}
|
|
|
|
try {
|
|
const url = `https://api.mapbox.com/geocoding/v5/mapbox.places/${lng},${lat}.json?` +
|
|
`access_token=${MAPBOX_TOKEN}&` +
|
|
`language=pt&` +
|
|
`types=address,place`;
|
|
|
|
const response = await fetch(url);
|
|
const data = await response.json();
|
|
|
|
if (data.features && data.features.length > 0) {
|
|
const feature = data.features[0];
|
|
const context = feature.context || [];
|
|
const place = context.find((c: any) => c.id.startsWith('place'));
|
|
const region = context.find((c: any) => c.id.startsWith('region'));
|
|
const postcode = context.find((c: any) => c.id.startsWith('postcode'));
|
|
|
|
// Extrair número se houver
|
|
const addressMatch = feature.address || '';
|
|
|
|
return {
|
|
description: feature.place_name,
|
|
street: feature.text,
|
|
number: addressMatch.toString(),
|
|
city: place?.text || '',
|
|
state: region?.short_code?.replace('BR-', '') || region?.text || '',
|
|
zip: postcode?.text || '',
|
|
lat,
|
|
lng,
|
|
mapLink: `https://www.google.com/maps/search/?api=1&query=${lat},${lng}`
|
|
};
|
|
}
|
|
|
|
return null;
|
|
} catch (error) {
|
|
console.error('Erro no reverse geocode:', error);
|
|
return null;
|
|
}
|
|
}
|