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