fix: paste trực tiếp tọa độ vào kinh độ vĩ độ

This commit is contained in:
2026-06-23 17:50:05 +07:00
parent 28ea9abccd
commit 7182391241
6 changed files with 238 additions and 107 deletions
+60 -4
View File
@@ -88,6 +88,45 @@ export const AddLocationModal = ({ isOpen, onClose, tourId, initialLegId, editin
const { legs, addLocation, updateLocation, updateTourStartPoint, updateTourEndPoint, mapCenter, currentTour, userRole } = useTourStore();
const notify = useNotification();
// Helper function to parse coordinates from pasted text (format: "lat, lng")
const parseCoordinates = (text: string): { latitude: number; longitude: number } | null => {
const trimmed = text.trim();
// Match format: number, number (supports negative numbers and decimals)
const coordMatch = trimmed.match(/^(-?\d+\.?\d*)\s*,\s*(-?\d+\.?\d*)$/);
if (coordMatch) {
const lat = parseFloat(coordMatch[1]);
const lng = parseFloat(coordMatch[2]);
// Validate latitude range: -90 to 90
// Validate longitude range: -180 to 180
if (lat >= -90 && lat <= 90 && lng >= -180 && lng <= 180) {
return { latitude: lat, longitude: lng };
}
}
return null;
};
// Handle paste event for coordinate inputs
const handleCoordinatePaste = (e: React.ClipboardEvent<HTMLInputElement>, field: 'latitude' | 'longitude') => {
const pastedText = e.clipboardData.getData('text');
const parsed = parseCoordinates(pastedText);
if (parsed) {
e.preventDefault();
setFormData(prev => ({
...prev,
latitude: parsed.latitude,
longitude: parsed.longitude
}));
notify({
title: 'Thành công',
message: `Tọa độ được phân tích: ${parsed.latitude}, ${parsed.longitude}`,
type: 'success'
});
}
};
// 1. Luôn khai báo Hook ở cấp cao nhất, không đặt code logic/return giữa các Hook
useEffect(() => {
if (isOpen) {
@@ -524,13 +563,30 @@ export const AddLocationModal = ({ isOpen, onClose, tourId, initialLegId, editin
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-bold text-gray-700 mb-1"> đ</label>
<input type="number" step="any" required className="w-full px-4 py-3 bg-gray-50 border border-gray-100 rounded-xl outline-none"
value={formData.latitude} onChange={e => setFormData({...formData, latitude: e.target.value as any})} />
<input
type="number"
step="any"
required
className="w-full px-4 py-3 bg-gray-50 border border-gray-100 rounded-xl outline-none"
value={formData.latitude}
onChange={e => setFormData({...formData, latitude: e.target.value as any})}
onPaste={(e) => handleCoordinatePaste(e, 'latitude')}
placeholder="13.50731401913824"
/>
<p className="text-xs text-gray-400 mt-1">💡 Dán "lat, lng" đ tự đng điền cả đ kinh đ</p>
</div>
<div>
<label className="block text-sm font-bold text-gray-700 mb-1">Kinh đ</label>
<input type="number" step="any" required className="w-full px-4 py-3 bg-gray-50 border border-gray-100 rounded-xl outline-none"
value={formData.longitude} onChange={e => setFormData({...formData, longitude: e.target.value as any})} />
<input
type="number"
step="any"
required
className="w-full px-4 py-3 bg-gray-50 border border-gray-100 rounded-xl outline-none"
value={formData.longitude}
onChange={e => setFormData({...formData, longitude: e.target.value as any})}
onPaste={(e) => handleCoordinatePaste(e, 'longitude')}
placeholder="109.28986362417251"
/>
</div>
</div>
<div>
+46
View File
@@ -102,6 +102,52 @@ export const LandingPage: React.FC<LandingPageProps> = ({ onGoToSignup, onGoToMa
}
};
// Update Open Graph meta tags when public photos are fetched
useEffect(() => {
if (publicPhotos.length > 0) {
const mainPhoto = publicPhotos[0];
const photoUrl = mainPhoto.imageUrl || mainPhoto.originalUrl || '/background.avif';
const photoTitle = mainPhoto.metadata?.title || 'Travel Planner - Khám phá chuyến đi tuyệt vời';
const photoDescription = mainPhoto.metadata?.description || `Được chia sẻ bởi ${mainPhoto.uploader?.name || 'một thành viên'}. Khám phá những hành trình tuyệt vời trên Travel Planner.`;
// Update og:image
let ogImage = document.querySelector('meta[property="og:image"]');
if (!ogImage) {
ogImage = document.createElement('meta');
ogImage.setAttribute('property', 'og:image');
document.head.appendChild(ogImage);
}
ogImage.setAttribute('content', `https://yotrip.labz.io.vn${photoUrl}`);
// Update og:title
let ogTitle = document.querySelector('meta[property="og:title"]');
if (!ogTitle) {
ogTitle = document.createElement('meta');
ogTitle.setAttribute('property', 'og:title');
document.head.appendChild(ogTitle);
}
ogTitle.setAttribute('content', photoTitle);
// Update og:description
let ogDescription = document.querySelector('meta[property="og:description"]');
if (!ogDescription) {
ogDescription = document.createElement('meta');
ogDescription.setAttribute('property', 'og:description');
document.head.appendChild(ogDescription);
}
ogDescription.setAttribute('content', photoDescription);
// Update twitter:image
let twitterImage = document.querySelector('meta[name="twitter:image"]');
if (!twitterImage) {
twitterImage = document.createElement('meta');
twitterImage.setAttribute('name', 'twitter:image');
document.head.appendChild(twitterImage);
}
twitterImage.setAttribute('content', `https://yotrip.labz.io.vn${photoUrl}`);
}
}, [publicPhotos]);
useEffect(() => {
fetchPublicPhotos();
fetchTrustedUsers();