diff --git a/backend/public/downloads/yotrip-latest.apk b/backend/public/downloads/yotrip-latest.apk index 5a62155..d2ba106 100644 Binary files a/backend/public/downloads/yotrip-latest.apk and b/backend/public/downloads/yotrip-latest.apk differ diff --git a/frontend/dist/index.html b/frontend/dist/index.html index 8766eb7..9661256 100644 --- a/frontend/dist/index.html +++ b/frontend/dist/index.html @@ -21,7 +21,7 @@ - + diff --git a/frontend/src/components/AddPhotoModal.tsx b/frontend/src/components/AddPhotoModal.tsx index a04fcc2..58058e3 100644 --- a/frontend/src/components/AddPhotoModal.tsx +++ b/frontend/src/components/AddPhotoModal.tsx @@ -158,11 +158,6 @@ export const AddPhotoModal: React.FC = ({ isOpen, onClose, t if (!response.ok) throw new Error('Tải lên ảnh thất bại'); } - notify({ - title: 'Thành công', - message: `Đã tải lên ${selectedFiles.length} ảnh.`, - type: 'success' - }); // Giải phóng bộ nhớ sau khi hoàn tất previews.forEach(url => URL.revokeObjectURL(url)); diff --git a/frontend/src/pages/ExploreMap.tsx b/frontend/src/pages/ExploreMap.tsx index c4faf9e..ec4aae9 100644 --- a/frontend/src/pages/ExploreMap.tsx +++ b/frontend/src/pages/ExploreMap.tsx @@ -195,9 +195,9 @@ export const ExploreMap = ({ onBack, onLogout, user, onViewTour, onLoginSuccess, const groups: { [key: string]: any[] } = {}; filteredPhotos.forEach((photo) => { - const lat = photo.metadata?.lat; - const lng = photo.metadata?.lng; - if (typeof lat === 'number' && typeof lng === 'number') { + const lat = typeof photo.metadata?.lat === 'number' ? photo.metadata.lat : parseFloat(photo.metadata?.lat); + const lng = typeof photo.metadata?.lng === 'number' ? photo.metadata.lng : parseFloat(photo.metadata?.lng); + if (typeof lat === 'number' && !isNaN(lat) && typeof lng === 'number' && !isNaN(lng)) { const key = `${lat.toFixed(5)},${lng.toFixed(5)}`; if (!groups[key]) { groups[key] = []; @@ -629,13 +629,13 @@ export const ExploreMap = ({ onBack, onLogout, user, onViewTour, onLoginSuccess, if (photoId && publicPhotos.length > 0) { const foundPhoto = publicPhotos.find((p) => p.id === photoId); if (foundPhoto) { - const lat = foundPhoto.metadata?.lat; - const lng = foundPhoto.metadata?.lng; - if (typeof lat === 'number' && typeof lng === 'number') { + const lat = typeof foundPhoto.metadata?.lat === 'number' ? foundPhoto.metadata.lat : parseFloat(foundPhoto.metadata?.lat); + const lng = typeof foundPhoto.metadata?.lng === 'number' ? foundPhoto.metadata.lng : parseFloat(foundPhoto.metadata?.lng); + if (typeof lat === 'number' && !isNaN(lat) && typeof lng === 'number' && !isNaN(lng)) { const group = publicPhotos.filter((p) => { - const pLat = p.metadata?.lat; - const pLng = p.metadata?.lng; - return typeof pLat === 'number' && typeof pLng === 'number' && + const pLat = typeof p.metadata?.lat === 'number' ? p.metadata.lat : parseFloat(p.metadata?.lat); + const pLng = typeof p.metadata?.lng === 'number' ? p.metadata.lng : parseFloat(p.metadata?.lng); + return typeof pLat === 'number' && !isNaN(pLat) && typeof pLng === 'number' && !isNaN(pLng) && Math.abs(pLat - lat) < 0.00001 && Math.abs(pLng - lng) < 0.00001; }); @@ -969,9 +969,9 @@ export const ExploreMap = ({ onBack, onLogout, user, onViewTour, onLoginSuccess, {groupedPhotos.map((photoGroup) => { const latestPhoto = photoGroup[0]; - const lat = latestPhoto.metadata?.lat; - const lng = latestPhoto.metadata?.lng; - if (typeof lat !== 'number' || typeof lng !== 'number') return null; + const lat = typeof latestPhoto.metadata?.lat === 'number' ? latestPhoto.metadata.lat : parseFloat(latestPhoto.metadata?.lat); + const lng = typeof latestPhoto.metadata?.lng === 'number' ? latestPhoto.metadata.lng : parseFloat(latestPhoto.metadata?.lng); + if (typeof lat !== 'number' || isNaN(lat) || typeof lng !== 'number' || isNaN(lng)) return null; return ( = ({ throw new Error(errorData.message || 'Tải ảnh thất bại.'); } - notify({ title: 'Thành công!', message: 'Ảnh của bạn đã được tải lên.', type: 'success' }); - // Xóa pendingInviteToken và parameter token khỏi URL để tránh tự động chuyển sang JoinTourPage localStorage.removeItem('pendingInviteToken'); const url = new URL(window.location.href); diff --git a/frontend/src/utils/imageProcessor.ts b/frontend/src/utils/imageProcessor.ts index 94f1f0b..20d56cd 100644 --- a/frontend/src/utils/imageProcessor.ts +++ b/frontend/src/utils/imageProcessor.ts @@ -7,18 +7,26 @@ interface ProcessedImageResult { } export const processAndResizeImage = async (file: File): Promise => { - // 1. Read EXIF coordinates first from the original file using exifr + // 1. Read EXIF coordinates first from the original file using exifr via ArrayBuffer let latitude: number | null = null; let longitude: number | null = null; try { - const gps = await exifr.gps(file); - if (gps && typeof gps.latitude === 'number' && typeof gps.longitude === 'number') { - latitude = gps.latitude; - longitude = gps.longitude; - console.log('[imageProcessor] Successfully parsed EXIF coordinates via exifr:', latitude, longitude); + const buffer = await new Promise((resolve) => { + const reader = new FileReader(); + reader.onload = (e) => resolve(e.target?.result as ArrayBuffer); + reader.onerror = () => resolve(null); + reader.readAsArrayBuffer(file); + }); + if (buffer) { + const gps = await exifr.gps(buffer); + if (gps && typeof gps.latitude === 'number' && typeof gps.longitude === 'number') { + latitude = gps.latitude; + longitude = gps.longitude; + console.log('[imageProcessor] Successfully parsed EXIF coordinates via exifr ArrayBuffer:', latitude, longitude); + } } } catch (e) { - console.error('[imageProcessor] Failed to read EXIF GPS using exifr:', e); + console.error('[imageProcessor] Failed to read EXIF GPS using exifr ArrayBuffer:', e); } // 2. Perform resizing to maximum 2048px on its longest edge