fix: bố cục lại cách hiển thị thông tin ảnh
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import { X, Send, MessageSquare, User, Loader2, Calendar, MapPin, Download, Edit } from 'lucide-react';
|
||||
import { X, Send, MessageSquare, User, Loader2, Calendar, MapPin, Download, Edit, Heart } from 'lucide-react';
|
||||
import { io } from 'socket.io-client';
|
||||
import { CoordinateSelectModal } from './CoordinateSelectModal';
|
||||
|
||||
@@ -60,6 +60,33 @@ export const PublicPhotoModal: React.FC<PublicPhotoModalProps> = ({
|
||||
const [editLng, setEditLng] = useState<number | ''>('');
|
||||
const [isSavingEdit, setIsSavingEdit] = useState(false);
|
||||
const [isMapOpen, setIsMapOpen] = useState(false);
|
||||
const [resolvedAddress, setResolvedAddress] = useState<string>('');
|
||||
|
||||
useEffect(() => {
|
||||
const lat = photo?.metadata?.lat;
|
||||
const lng = photo?.metadata?.lng;
|
||||
if (typeof lat === 'number' && typeof lng === 'number') {
|
||||
setResolvedAddress('Đang xác định địa điểm...');
|
||||
fetch(`https://nominatim.openstreetmap.org/reverse?format=json&lat=${lat}&lon=${lng}&accept-language=vi`)
|
||||
.then(res => {
|
||||
if (!res.ok) throw new Error();
|
||||
return res.json();
|
||||
})
|
||||
.then(data => {
|
||||
if (data && data.display_name) {
|
||||
const shortAddress = data.display_name.split(',').slice(0, 3).join(',').trim();
|
||||
setResolvedAddress(shortAddress || data.display_name);
|
||||
} else {
|
||||
setResolvedAddress(`${lat.toFixed(4)}, ${lng.toFixed(4)}`);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
setResolvedAddress(`${lat.toFixed(4)}, ${lng.toFixed(4)}`);
|
||||
});
|
||||
} else {
|
||||
setResolvedAddress('Chưa xác định tọa độ');
|
||||
}
|
||||
}, [photo?.id, photo?.metadata?.lat, photo?.metadata?.lng]);
|
||||
|
||||
const checkCurrentUser = () => {
|
||||
const userStr = localStorage.getItem('user');
|
||||
@@ -131,6 +158,57 @@ export const PublicPhotoModal: React.FC<PublicPhotoModalProps> = ({
|
||||
}
|
||||
};
|
||||
|
||||
const likedUserIds = photo.metadata && Array.isArray((photo.metadata as any).likedUserIds)
|
||||
? (photo.metadata as any).likedUserIds
|
||||
: [];
|
||||
const isLiked = currentUser && likedUserIds.includes(currentUser.id);
|
||||
const likeCount = likedUserIds.length;
|
||||
|
||||
const handleToggleLike = async () => {
|
||||
let token = localStorage.getItem('token');
|
||||
let userObj = currentUser;
|
||||
|
||||
if (!token) {
|
||||
try {
|
||||
const guestRes = await fetch('/api/v1/auth/create-guest', { method: 'POST' });
|
||||
if (guestRes.ok) {
|
||||
const guestData = await guestRes.json();
|
||||
token = guestData.access_token;
|
||||
userObj = guestData.user;
|
||||
localStorage.setItem('token', token!);
|
||||
localStorage.setItem('user', JSON.stringify(userObj));
|
||||
if (onLoginSuccess) {
|
||||
onLoginSuccess(userObj);
|
||||
}
|
||||
setCurrentUser(userObj);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Không thể tạo phiên khách:', e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetch(`/api/v1/photos/${photo.id}/toggle-like`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`
|
||||
}
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
const updatedPhoto = await res.json();
|
||||
if (onUpdatePhoto) {
|
||||
onUpdatePhoto(updatedPhoto);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Lỗi khi thích ảnh:', error);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const fetchComments = async () => {
|
||||
setIsLoading(true);
|
||||
@@ -296,6 +374,18 @@ export const PublicPhotoModal: React.FC<PublicPhotoModalProps> = ({
|
||||
|
||||
{/* Left Side: Photo Detail */}
|
||||
<div className="relative w-full md:w-3/5 h-2/5 md:h-full bg-slate-950 flex items-center justify-center overflow-hidden group">
|
||||
{/* Like Button Overlay */}
|
||||
<button
|
||||
onClick={handleToggleLike}
|
||||
className="absolute top-4 left-4 z-40 flex items-center gap-1.5 bg-slate-950/70 hover:bg-slate-900/80 border border-slate-700/50 text-slate-200 hover:text-white font-bold py-1.5 px-3 rounded-full transition-all active:scale-95 text-[11px] backdrop-blur-md"
|
||||
title={isLiked ? "Bỏ thích" : "Thích"}
|
||||
>
|
||||
<Heart className={`w-4 h-4 transition-colors ${
|
||||
isLiked ? 'text-rose-500 fill-rose-500 animate-in zoom-in-75' : 'text-slate-350 hover:text-rose-450'
|
||||
}`} />
|
||||
<span>{likeCount}</span>
|
||||
</button>
|
||||
|
||||
<img
|
||||
src={photo.imageUrl}
|
||||
alt="Public Map Upload"
|
||||
@@ -453,27 +543,27 @@ export const PublicPhotoModal: React.FC<PublicPhotoModalProps> = ({
|
||||
</div>
|
||||
|
||||
{/* Photo Metadata */}
|
||||
<div className="flex flex-wrap items-center gap-4 text-xs text-slate-300">
|
||||
<span className="flex items-center gap-1.5 font-semibold text-emerald-400">
|
||||
<User className="w-4 h-4" />
|
||||
{photo.uploader?.name || 'Ẩn danh'}
|
||||
</span>
|
||||
<span className="flex items-center gap-1.5 text-slate-400">
|
||||
<Calendar className="w-4 h-4" />
|
||||
{new Date(photo.capturedAt).toLocaleDateString('vi-VN', {
|
||||
day: '2-digit',
|
||||
month: '2-digit',
|
||||
year: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
})}
|
||||
</span>
|
||||
{photo.metadata?.lat && photo.metadata?.lng && (
|
||||
<span className="flex items-center gap-1.5 text-slate-400">
|
||||
<MapPin className="w-4 h-4 text-rose-500" />
|
||||
{photo.metadata.lat.toFixed(4)}, {photo.metadata.lng.toFixed(4)}
|
||||
</span>
|
||||
)}
|
||||
<div className="flex flex-wrap items-center justify-between gap-4 border-t border-slate-800/80 pt-3 text-xs text-slate-350">
|
||||
<div className="space-y-1 text-left">
|
||||
<div className="flex items-center gap-1.5 text-slate-400">
|
||||
<Calendar className="w-3.5 h-3.5" />
|
||||
Ngày chụp: {new Date(photo.capturedAt).toLocaleDateString('vi-VN', {
|
||||
day: '2-digit',
|
||||
month: '2-digit',
|
||||
year: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
})}
|
||||
</div>
|
||||
<div className="flex items-center gap-1.5 text-slate-400" title={photo.metadata?.lat && photo.metadata?.lng ? `${photo.metadata.lat.toFixed(6)}, ${photo.metadata.lng.toFixed(6)}` : ''}>
|
||||
<MapPin className="w-3.5 h-3.5 text-rose-500" />
|
||||
Địa điểm: {resolvedAddress}
|
||||
</div>
|
||||
<div className="flex items-center gap-1.5 font-semibold text-emerald-400">
|
||||
<User className="w-3.5 h-3.5" />
|
||||
Người đăng: {photo.uploader?.name || 'Ẩn danh'}
|
||||
</div>
|
||||
</div>
|
||||
{photo.originalUrl && (
|
||||
<a
|
||||
href={photo.originalUrl}
|
||||
|
||||
Reference in New Issue
Block a user