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
+15
View File
@@ -6,6 +6,21 @@
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
<script src="https://accounts.google.com/gsi/client" async defer></script>
<title>Travel Planner</title>
<!-- Open Graph Meta Tags for Social Media Sharing -->
<meta property="og:type" content="website" />
<meta property="og:url" content="https://yotrip.labz.io.vn" />
<meta property="og:title" content="Travel Planner - Lên kế hoạch chuyến đi của bạn" />
<meta property="og:description" content="Khám phá những hành trình tuyệt vời và chia sẻ khoảnh khắc đẹp với cộng đồng du lịch." />
<meta property="og:image" content="https://yotrip.labz.io.vn/background.avif" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<!-- Twitter Card Meta Tags -->
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="Travel Planner - Lên kế hoạch chuyến đi của bạn" />
<meta name="twitter:description" content="Khám phá những hành trình tuyệt vời và chia sẻ khoảnh khắc đẹp với cộng đồng du lịch." />
<meta name="twitter:image" content="https://yotrip.labz.io.vn/background.avif" />
</head>
<body>
<div id="root"></div>
+1 -1
View File
@@ -22,7 +22,7 @@ server {
}
# Images and fonts - long caching (NOT including /uploads/)
location ~* ^(?!/uploads/).*\.(?:jpg|jpeg|png|gif|ico|svg|woff|woff2|ttf|eot)$ {
location ~* ^(?!/uploads/).*\.(?:jpg|jpeg|png|gif|ico|svg|avif|woff|woff2|ttf|eot)$ {
root /usr/share/nginx/html;
expires 1y;
add_header Cache-Control "public, max-age=31536000";
+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();