fix: sửa UI photoview hiển thị ở public

This commit is contained in:
2026-06-28 14:55:25 +07:00
parent 41c8e67229
commit 2f5ef424c8
4 changed files with 405 additions and 417 deletions
+2 -2
View File
@@ -21,7 +21,7 @@
<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" />
<script type="module" crossorigin src="/assets/index-CSiax1SI.js"></script>
<script type="module" crossorigin src="/assets/index-BtRV7JVM.js"></script>
<link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-CMxvf4Kt.js">
<link rel="modulepreload" crossorigin href="/assets/vendor-others-CNhtyHGs.js">
<link rel="modulepreload" crossorigin href="/assets/vendor-editor-BDwQQzB8.js">
@@ -30,7 +30,7 @@
<link rel="modulepreload" crossorigin href="/assets/vendor-pdf-C3XQY6t9.js">
<link rel="stylesheet" crossorigin href="/assets/vendor-maps-CcXJxNtP.css">
<link rel="stylesheet" crossorigin href="/assets/vendor-react-CuSj0z1j.css">
<link rel="stylesheet" crossorigin href="/assets/index-CIipVipb.css">
<link rel="stylesheet" crossorigin href="/assets/index-CLipHKhu.css">
</head>
<body>
<div id="root"></div>
+252 -305
View File
@@ -427,358 +427,308 @@ export const PublicPhotoModal: React.FC<PublicPhotoModalProps> = ({
<X className="w-5 h-5" />
</button>
{/* Left Side: Photo Detail */}
<div className="relative w-full md:w-3/5 md:h-full bg-slate-950 flex flex-col overflow-hidden group shrink-0">
{/* Photo wrapper for mobile view (handles top overlay name and bottom-right like) */}
<div className="relative w-full flex items-center justify-center md:absolute md:inset-0 md:flex md:items-center md:justify-center bg-slate-950">
{/* Mobile Only: Uploader details overlay */}
<div className="absolute top-[calc(0.75rem+env(safe-area-inset-top,0px))] left-4 z-40 md:hidden flex items-center gap-2 bg-slate-950/70 backdrop-blur-md px-2.5 py-1.5 rounded-full border border-slate-700/50">
<div className="w-5 h-5 rounded-full bg-emerald-500/20 flex items-center justify-center">
<User className="w-3 h-3 text-emerald-400" />
</div>
<span className="text-xs text-slate-200 max-w-[120px] truncate">
{photo.uploader?.name || 'Ẩn danh'}
</span>
{/* ============================================================
TIER 3: FIXED PHOTO FRAME VIEWPORT (z-20 / sticky on mobile)
Aspect-square container. Does NOT resize on image swap.
Desktop: left 3/5 column, absolute-fill with overlay metadata.
============================================================ */}
<div className="w-full aspect-square bg-slate-950 flex items-center justify-center sticky top-0 z-20 shrink-0 shadow-2xl border-b border-slate-900/60 md:relative md:aspect-auto md:w-3/5 md:h-full md:overflow-hidden md:border-b-0 group">
{/* Image fill */}
<a
href={`${window.location.origin}/api/v1/public-photos/${photo.id}/share`}
className="absolute inset-0 flex items-center justify-center cursor-zoom-in"
onClick={(e) => {
e.preventDefault();
setIsFullscreen(true);
}}
>
<img
src={photo.imageUrl}
alt="Public Map Upload"
onContextMenu={(e) => { if (!isLoggedIn) e.preventDefault(); }}
onDragStart={(e) => { if (!isLoggedIn) e.preventDefault(); }}
className="w-full h-full object-contain select-none pointer-events-none"
draggable={false}
/>
{(!isAuthorized || !isLoggedIn) && (
<div className="absolute inset-0 bg-transparent select-none z-10" />
)}
</a>
{/* TIER 3.1: LOCATION & TIMESTAMP OVERLAY — bottom-left inside image (z-30) */}
<div className="absolute bottom-3 left-3 z-30 flex flex-col gap-0.5 max-w-[70vw] md:max-w-[45%] pointer-events-none drop-shadow-[0_1px_3px_rgba(0,0,0,0.9)]">
<div className="flex items-center gap-1.5 text-[10px] font-bold text-blue-300">
<MapPin className="w-3.5 h-3.5 text-red-500 shrink-0" />
<span className="truncate leading-tight">{resolvedAddress}</span>
</div>
<div className="flex items-center gap-1.5 text-[9px] text-blue-200/80 pl-5">
<Calendar className="w-2.5 h-2.5 text-blue-300/70 shrink-0" />
<span>{new Date(photo.capturedAt).toLocaleDateString('vi-VN', {
day: '2-digit', month: '2-digit', year: 'numeric',
hour: '2-digit', minute: '2-digit'
})}</span>
</div>
{/* Like Button Overlay */}
<button
onClick={(e) => {
e.stopPropagation();
handleToggleLike();
}}
className="absolute bottom-4 right-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 md:absolute md:top-[calc(1rem+env(safe-area-inset-top,0px))] md:left-4 md:bottom-auto md:right-auto"
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>
<a
href={`${window.location.origin}/api/v1/public-photos/${photo.id}/share`}
className="w-full h-auto max-h-[85vh] object-contain cursor-zoom-in md:h-full md:max-h-none flex items-center justify-center relative"
onClick={(e) => {
e.preventDefault();
setIsFullscreen(true);
}}
>
<img
src={photo.imageUrl}
alt="Public Map Upload"
onContextMenu={(e) => { if (!isLoggedIn) e.preventDefault(); }}
onDragStart={(e) => { if (!isLoggedIn) e.preventDefault(); }}
className={`w-full h-auto max-h-[85vh] object-contain md:h-full md:max-h-none select-none ${
!isLoggedIn ? 'pointer-events-none' : ''
}`}
draggable={false}
/>
{/* Shield overlay to prevent Save Image As on right-click / long press for non-owners */}
{(!isAuthorized || !isLoggedIn) && (
<div className="absolute inset-0 bg-transparent select-none z-10" />
)}
</a>
</div>
{/* Info & Timeline overlay inside photo panel */}
<div className="relative z-20 p-6 bg-slate-900 border-b border-slate-800/60 md:absolute md:bottom-0 md:left-0 md:right-0 md:bg-gradient-to-t md:from-black/60 md:via-black/20 md:to-transparent md:border-b-0 flex flex-col gap-4">
{/* Timeline scroll */}
{/* Like button — bottom-right on mobile, top-left on desktop */}
<button
onClick={(e) => {
e.stopPropagation();
handleToggleLike();
}}
className="absolute bottom-3 right-3 z-30 flex items-center gap-1 bg-black/55 backdrop-blur-sm border border-white/10 rounded-xl px-2.5 py-1.5 text-[11px] font-bold transition-all active:scale-95 md:top-[calc(1rem+env(safe-area-inset-top,0px))] md:left-4 md:bottom-auto md:right-auto"
title={isLiked ? "Bỏ thích" : "Thích"}
>
<Heart className={`w-3.5 h-3.5 transition-colors ${
isLiked ? 'text-rose-500 fill-rose-500' : 'text-white'
}`} />
<span className="text-white">{likeCount}</span>
</button>
{/* Uploader pill — top-left on mobile */}
<div className="absolute top-[calc(0.75rem+env(safe-area-inset-top,0px))] left-3 z-30 md:hidden flex items-center gap-1.5 bg-black/55 backdrop-blur-sm px-2.5 py-1 rounded-full border border-white/10">
<div className="w-4 h-4 rounded-full bg-emerald-500/30 flex items-center justify-center">
<User className="w-2.5 h-2.5 text-emerald-300" />
</div>
<span className="text-[10px] text-slate-200 max-w-[110px] truncate font-semibold">
{photo.uploader?.name || 'Ẩn danh'}
</span>
</div>
{/* Desktop: overlay metadata gradient at bottom of photo column */}
<div className="hidden md:flex absolute bottom-0 left-0 right-0 z-20 px-6 pb-5 pt-12 bg-gradient-to-t from-black/70 via-black/20 to-transparent flex-col gap-1 pointer-events-none">
<h4 className="text-sm font-black text-white tracking-tight leading-snug drop-shadow">
{photo.metadata?.title || ''}
</h4>
{photo.metadata?.description && (
<p className="text-xs text-slate-300 leading-snug line-clamp-2 drop-shadow">{photo.metadata.description}</p>
)}
<div className="flex items-center gap-1.5 text-[10px] text-slate-300 mt-1">
<MapPin className="w-3 h-3 text-rose-400 shrink-0" />
<span className="truncate">{resolvedAddress}</span>
</div>
<div className="flex items-center gap-1.5 text-[10px] text-slate-400">
<Calendar className="w-2.5 h-2.5 shrink-0" />
<span>{new Date(photo.capturedAt).toLocaleDateString('vi-VN', {
day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit'
})}</span>
</div>
</div>
{/* Desktop thumbnail timeline carousel at the bottom */}
{photoGroup && photoGroup.length > 1 && (
<div className="hidden md:flex absolute bottom-0 left-0 right-0 z-20 px-4 pb-3 gap-2 overflow-x-auto no-scrollbar justify-end items-end pointer-events-auto">
{photoGroup.map((p) => {
const isActive = p.id === photo.id;
return (
<button
key={p.id}
onClick={(e) => { e.stopPropagation(); onSelectPhoto?.(p); }}
className={`relative w-10 h-10 rounded-xl overflow-hidden transition-all active:scale-95 shrink-0 ${
isActive ? 'border-2 border-emerald-400 scale-105 shadow-lg' : 'border border-slate-600/50 opacity-70 hover:opacity-100'
}`}
>
<img src={p.imageUrl} alt="thumb" className="w-full h-full object-cover pointer-events-none" />
</button>
);
})}
</div>
)}
</div>
{/* ============================================================
TIER 2: SCROLLABLE COMMENTS PANEL (z-10)
Slides BEHIND the sticky image frame when swiping up on mobile.
Desktop: right 2/5 column with its own overflow-y-auto.
============================================================ */}
<div className="flex-1 overflow-y-auto relative z-10 flex flex-col bg-slate-900 border-t md:border-t-0 md:border-l border-slate-800 md:w-2/5 md:flex-1 md:min-h-0">
{/* ---- TITLE HEADER: replaces old "Lịch sử ảnh tại vị trí này" strip ---- */}
<div className="px-5 pt-4 pb-0 bg-slate-900/95 backdrop-blur-md border-b border-slate-800/60 sticky top-0 z-10 flex flex-col gap-2">
{/* Row 1: Title + Edit button */}
<div className="flex items-start justify-between gap-3">
<div className="flex-1 min-w-0">
<h2 className="text-sm font-black text-slate-100 tracking-wide leading-snug">
{photo.metadata?.title || 'Chưa có tiêu đề'}
</h2>
{photo.metadata?.description && (
<p className="text-[11px] text-slate-400 leading-relaxed mt-0.5 line-clamp-2">
{photo.metadata.description}
</p>
)}
</div>
{isAuthorized && (
<button
onClick={(e) => { e.stopPropagation(); setIsEditing(true); }}
className="p-1.5 bg-slate-800 hover:bg-slate-700 border border-slate-700/50 rounded-xl text-slate-300 hover:text-white transition-all shrink-0"
title="Chỉnh sửa thông tin"
>
<Edit className="w-3.5 h-3.5" />
</button>
)}
</div>
{/* Row 2: Thumbnail timeline strip — below title, mobile only */}
{photoGroup && photoGroup.length > 1 && (
<div className="flex flex-col gap-2 border-b border-slate-800/80 pb-3 relative z-20">
<span className="text-[10px] font-bold uppercase tracking-wider text-slate-400 flex items-center gap-1.5">
<span className="inline-block w-1.5 h-1.5 rounded-full bg-emerald-500 animate-ping"></span>
Lịch sử nh tại vị trí này ({photoGroup.length})
</span>
<div className="flex gap-3 overflow-x-auto no-scrollbar py-1 relative z-20">
{photoGroup.map((p) => {
const isActive = p.id === photo.id;
return (
<button
key={p.id}
onClick={(e) => {
e.stopPropagation();
onSelectPhoto?.(p);
}}
className={`relative w-12 h-12 rounded-xl overflow-hidden border-2 transition-all active:scale-95 shrink-0 ${
isActive ? 'border-emerald-500 scale-110 shadow-lg' : 'border-slate-700 hover:border-slate-500'
}`}
>
<img
src={p.imageUrl}
alt="Timeline thumbnail"
onContextMenu={(e) => { if (!isLoggedIn) e.preventDefault(); }}
onDragStart={(e) => { if (!isLoggedIn) e.preventDefault(); }}
className={`w-full h-full object-cover ${
!isLoggedIn ? 'pointer-events-none' : ''
}`}
/>
<div className="absolute bottom-0 inset-x-0 bg-slate-950/70 text-[8px] text-center font-bold text-slate-300 py-0.5">
{new Date(p.capturedAt).toLocaleDateString('vi-VN', { month: '2-digit', day: '2-digit' })}
</div>
</button>
);
})}
</div>
<div className="flex gap-2 overflow-x-auto no-scrollbar pb-3 md:hidden">
{photoGroup.map((p) => {
const isActive = p.id === photo.id;
const dateObj = new Date(p.capturedAt);
const day = String(dateObj.getDate()).padStart(2, '0');
const month = String(dateObj.getMonth() + 1).padStart(2, '0');
return (
<button
key={p.id}
onClick={(e) => { e.stopPropagation(); onSelectPhoto?.(p); }}
className={`relative w-12 h-12 rounded-2xl overflow-hidden transition-all active:scale-95 shrink-0 ${
isActive
? 'border-[3px] border-emerald-500 shadow-lg shadow-emerald-500/25'
: 'border-2 border-slate-600 hover:border-slate-400'
}`}
>
<img
src={p.imageUrl}
alt="thumb"
onContextMenu={(e) => { if (!isLoggedIn) e.preventDefault(); }}
className="w-full h-full object-cover pointer-events-none"
/>
<div className="absolute bottom-0 inset-x-0 bg-slate-950/70 text-[7px] text-center font-black text-slate-200 py-px tracking-wide">
{day}-{month}
</div>
</button>
);
})}
</div>
)}
</div>
{isEditing ? (
{/* Edit form (when editing) */}
{isEditing && (
<div className="px-5 py-4 border-b border-slate-800/60 bg-slate-900/95">
<div className="flex flex-col gap-3 bg-slate-900/95 border border-slate-800 p-4 rounded-2xl animate-in slide-in-from-bottom-2">
<h4 className="text-xs font-black uppercase tracking-wider text-emerald-400">Chỉnh sửa thông tin nh</h4>
<div className="space-y-2">
<div>
<label className="block text-[9px] uppercase font-bold tracking-wider text-slate-400 mb-1">Tiêu đ</label>
<input
type="text"
value={editTitle}
onChange={(e) => setEditTitle(e.target.value)}
<input type="text" value={editTitle} onChange={(e) => setEditTitle(e.target.value)}
placeholder="Nhập tiêu đề cho ảnh..."
className="w-full bg-slate-850 border border-slate-700/60 text-slate-100 rounded-xl px-3 py-2 text-base md:text-xs focus:outline-none focus:ring-1 focus:ring-emerald-500"
/>
className="w-full bg-slate-850 border border-slate-700/60 text-slate-100 rounded-xl px-3 py-2 text-base md:text-xs focus:outline-none focus:ring-1 focus:ring-emerald-500" />
</div>
<div>
<label className="block text-[9px] uppercase font-bold tracking-wider text-slate-400 mb-1"> tả</label>
<textarea
value={editDescription}
onChange={(e) => setEditDescription(e.target.value)}
placeholder="Mô tả bức ảnh này..."
rows={2}
className="w-full bg-slate-850 border border-slate-700/60 text-slate-100 rounded-xl px-3 py-2 text-base md:text-xs focus:outline-none focus:ring-1 focus:ring-emerald-500 resize-none"
/>
<textarea value={editDescription} onChange={(e) => setEditDescription(e.target.value)}
placeholder="Mô tả bức ảnh này..." rows={2}
className="w-full bg-slate-850 border border-slate-700/60 text-slate-100 rounded-xl px-3 py-2 text-base md:text-xs focus:outline-none focus:ring-1 focus:ring-emerald-500 resize-none" />
</div>
<div className="grid grid-cols-2 gap-2">
<div>
<label className="block text-[9px] uppercase font-bold tracking-wider text-slate-400 mb-1"> đ</label>
<input
type="number"
step="any"
value={editLat}
<input type="number" step="any" value={editLat}
onChange={(e) => setEditLat(e.target.value === '' ? '' : parseFloat(e.target.value))}
placeholder="Vĩ độ..."
className="w-full bg-slate-850 border border-slate-700/60 text-slate-100 rounded-xl px-3 py-2 text-base md:text-xs focus:outline-none focus:ring-1 focus:ring-emerald-500"
/>
className="w-full bg-slate-850 border border-slate-700/60 text-slate-100 rounded-xl px-3 py-2 text-base md:text-xs focus:outline-none focus:ring-1 focus:ring-emerald-500" />
</div>
<div>
<label className="block text-[9px] uppercase font-bold tracking-wider text-slate-400 mb-1">Kinh đ</label>
<input
type="number"
step="any"
value={editLng}
<input type="number" step="any" value={editLng}
onChange={(e) => setEditLng(e.target.value === '' ? '' : parseFloat(e.target.value))}
placeholder="Kinh độ..."
className="w-full bg-slate-850 border border-slate-700/60 text-slate-100 rounded-xl px-3 py-2 text-base md:text-xs focus:outline-none focus:ring-1 focus:ring-emerald-500"
/>
className="w-full bg-slate-850 border border-slate-700/60 text-slate-100 rounded-xl px-3 py-2 text-base md:text-xs focus:outline-none focus:ring-1 focus:ring-emerald-500" />
</div>
</div>
<div className="flex justify-start">
<button
type="button"
onClick={(e) => {
e.stopPropagation();
setIsMapOpen(true);
}}
className="flex items-center gap-1.5 px-3 py-1.5 bg-slate-800 hover:bg-slate-750 border border-slate-700/50 text-slate-300 hover:text-white rounded-xl text-[10px] font-bold transition-all"
>
<MapPin className="w-3.5 h-3.5 text-rose-500" />
Chọn trên bản đ
</button>
</div>
<button type="button" onClick={(e) => { e.stopPropagation(); setIsMapOpen(true); }}
className="flex items-center gap-1.5 px-3 py-1.5 bg-slate-800 hover:bg-slate-750 border border-slate-700/50 text-slate-300 hover:text-white rounded-xl text-[10px] font-bold transition-all">
<MapPin className="w-3.5 h-3.5 text-rose-500" />
Chọn trên bản đ
</button>
</div>
<div className="flex justify-end gap-2 mt-2">
<button
onClick={(e) => {
e.stopPropagation();
setIsEditing(false);
}}
disabled={isSavingEdit}
className="px-3 py-1.5 bg-slate-800 hover:bg-slate-700 text-slate-300 rounded-lg text-xs font-bold transition-all"
>
<div className="flex justify-end gap-2 mt-1">
<button onClick={(e) => { e.stopPropagation(); setIsEditing(false); }} disabled={isSavingEdit}
className="px-3 py-1.5 bg-slate-800 hover:bg-slate-700 text-slate-300 rounded-lg text-xs font-bold transition-all">
Hủy
</button>
<button
onClick={(e) => {
e.stopPropagation();
handleSaveEdit();
}}
disabled={isSavingEdit}
className="flex items-center gap-1 px-4 py-1.5 bg-emerald-600 hover:bg-emerald-500 disabled:bg-slate-800 disabled:opacity-50 text-white rounded-lg text-xs font-bold transition-all"
>
{isSavingEdit ? (
<>
<Loader2 className="w-3 h-3 animate-spin" />
Đang lưu...
</>
) : (
'Lưu lại'
)}
<button onClick={(e) => { e.stopPropagation(); handleSaveEdit(); }} disabled={isSavingEdit}
className="flex items-center gap-1 px-4 py-1.5 bg-emerald-600 hover:bg-emerald-500 disabled:bg-slate-800 disabled:opacity-50 text-white rounded-lg text-xs font-bold transition-all">
{isSavingEdit ? (<><Loader2 className="w-3 h-3 animate-spin" />Đang lưu...</>) : 'Lưu lại'}
</button>
</div>
</div>
) : (
<>
{/* Title & Description display */}
<div className="flex justify-between items-start gap-4">
<div className="flex-1 min-w-0">
{photo.metadata?.title ? (
<h4 className="text-sm font-black text-white tracking-tight leading-snug break-words">
{photo.metadata.title}
</h4>
) : (
<span className="text-[10px] text-slate-500 italic block mb-1">Chưa tiêu đ</span>
)}
{photo.metadata?.description ? (
<p className="text-xs text-slate-300 leading-relaxed mt-1 max-h-20 overflow-y-auto no-scrollbar break-words">
{photo.metadata.description}
</p>
) : (
<span className="text-[10px] text-slate-500 italic block mt-1">Chưa tả</span>
)}
</div>
{isAuthorized && (
<button
onClick={(e) => {
e.stopPropagation();
setIsEditing(true);
}}
className="p-1.5 bg-slate-800 hover:bg-slate-700 border border-slate-700/50 rounded-xl text-slate-300 hover:text-white transition-all shrink-0"
title="Chỉnh sửa thông tin"
>
<Edit className="w-3.5 h-3.5" />
</button>
)}
</div>
{/* Photo Metadata */}
<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="hidden md:flex items-center gap-1.5 text-xs text-emerald-400">
<User className="w-3.5 h-3.5" />
Người đăng: {photo.uploader?.name || 'Ẩn danh'}
</div>
</div>
{isAuthorized && photo.originalUrl && (
<a
href={photo.originalUrl}
download
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-1.5 bg-slate-800/80 hover:bg-slate-700/80 border border-slate-700/50 text-slate-200 hover:text-white font-bold py-1 px-3 rounded-full transition-all active:scale-95 text-[10px]"
>
<Download className="w-3.5 h-3.5 text-emerald-500" />
Tải nh gốc
</a>
)}
</div>
</>
)}
<style>{`
.no-scrollbar::-webkit-scrollbar {
display: none;
}
.no-scrollbar {
-ms-overflow-style: none;
scrollbar-width: none;
}
`}</style>
</div>
</div>
{/* Right Side: Comments */}
<div className="w-full md:w-2/5 md:flex-1 md:min-h-0 flex flex-col bg-slate-900 border-t md:border-t-0 md:border-l border-slate-800">
{/* Comments Header */}
<div className="hidden md:block p-6 border-b border-slate-800">
<div>
<h3 className="text-lg font-black tracking-tight text-white flex items-center gap-2">
<MessageSquare className="w-5 h-5 text-emerald-500" />
{t('commentSectionTitle')}
</h3>
<p className="text-xs text-slate-400 mt-1">nh chia sẻ công khai trên bản đ</p>
</div>
)}
{/* Download original (authorized) */}
{isAuthorized && photo.originalUrl && (
<div className="px-5 py-2 border-b border-slate-800/40 flex justify-end">
<a href={photo.originalUrl} download target="_blank" rel="noopener noreferrer"
className="flex items-center gap-1.5 bg-slate-800/80 hover:bg-slate-700/80 border border-slate-700/50 text-slate-200 hover:text-white font-bold py-1 px-3 rounded-full transition-all active:scale-95 text-[10px]">
<Download className="w-3.5 h-3.5 text-emerald-500" />
Tải nh gốc
</a>
</div>
)}
{/* ---- COMMENTS HEADER (desktop) ---- */}
<div className="hidden md:block px-6 py-4 border-b border-slate-800">
<h3 className="text-base font-black tracking-tight text-white flex items-center gap-2">
<MessageSquare className="w-4 h-4 text-emerald-500" />
{t('commentSectionTitle')}
</h3>
<p className="text-xs text-slate-400 mt-0.5">nh chia sẻ công khai trên bản đ</p>
</div>
{/* Comments list scroll area */}
<div className="md:flex-1 md:overflow-y-auto p-6 space-y-4 bg-slate-900/50">
{/* ---- COMMENT FEED ---- */}
<div className="p-4 space-y-4 flex-1">
{isLoading ? (
<div className="flex flex-col items-center justify-center py-12 text-slate-500 gap-2">
<Loader2 className="w-8 h-8 animate-spin text-emerald-500" />
<span className="text-xs font-semibold">{t('loading')}</span>
</div>
) : comments.length === 0 ? (
<div className="flex flex-col items-center justify-center py-16 text-slate-500 gap-3">
<div className="flex flex-col items-center justify-center py-12 text-slate-500 gap-3">
<div className="p-4 bg-slate-800/40 rounded-full text-slate-600">
<MessageSquare className="w-8 h-8" />
<MessageSquare className="w-7 h-7" />
</div>
<span className="text-sm font-semibold italic">Chưa bình luận nào. Hãy bắt đu cuộc trò chuyện!</span>
</div>
) : (
comments.map((c) => {
return (
<div key={c.id} className="flex gap-3 items-start animate-in fade-in slide-in-from-bottom-2 duration-200">
<div className="w-8 h-8 rounded-full bg-slate-800 flex items-center justify-center flex-shrink-0 border border-slate-700/60">
<User className="w-4.5 h-4.5 text-slate-400" />
</div>
<div className="flex-1 min-w-0">
<div className="bg-slate-800/55 p-3.5 rounded-2xl rounded-tl-none border border-slate-800 shadow-lg">
<div className="flex justify-between items-center mb-1">
<span className="text-xs font-black text-slate-200 truncate">{c.userName}</span>
<div className="flex items-center gap-2">
<span className="text-[9px] font-medium text-slate-500">
{new Date(c.createdAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
</span>
{(currentUser?.isAdmin ||
currentUser?.id === c.userId ||
currentUser?.id === photo.uploaderId ||
currentUser?.id === photo.uploader?.id) && (
<button
onClick={(e) => {
e.stopPropagation();
handleDeleteComment(c.id);
}}
className="text-slate-500 hover:text-rose-500 transition-colors p-0.5"
title={t('delete') || "Xóa"}
>
<Trash2 className="w-3.5 h-3.5" />
</button>
)}
</div>
comments.map((c) => (
<div key={c.id} className="flex gap-3 items-start animate-in fade-in slide-in-from-bottom-2 duration-200">
<div className="w-8 h-8 rounded-full bg-slate-800 flex items-center justify-center flex-shrink-0 border border-slate-700/60">
<User className="w-4 h-4 text-slate-400" />
</div>
<div className="flex-1 min-w-0">
<div className="bg-slate-800/55 p-3.5 rounded-2xl rounded-tl-none border border-slate-800 shadow-lg">
<div className="flex justify-between items-center mb-1">
<span className="text-xs font-black text-slate-200 truncate">{c.userName}</span>
<div className="flex items-center gap-2">
<span className="text-[9px] font-medium text-slate-500">
{new Date(c.createdAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
</span>
{(currentUser?.isAdmin || currentUser?.id === c.userId ||
currentUser?.id === photo.uploaderId || currentUser?.id === photo.uploader?.id) && (
<button
onClick={(e) => { e.stopPropagation(); handleDeleteComment(c.id); }}
className="text-slate-500 hover:text-rose-500 transition-colors p-0.5"
title={t('delete') || "Xóa"}
>
<Trash2 className="w-3.5 h-3.5" />
</button>
)}
</div>
<p className="text-sm text-slate-300 leading-relaxed break-words">{c.content}</p>
</div>
<p className="text-sm text-slate-300 leading-relaxed break-words">{c.content}</p>
</div>
</div>
);
})
</div>
))
)}
<div ref={commentsEndRef} />
</div>
{/* Comment Input Area */}
<div className="sticky bottom-0 md:static p-4 bg-slate-950 md:bg-slate-950/40 border-t border-slate-800/80 pb-[calc(1rem+env(safe-area-inset-bottom,0px))] md:pb-4 z-40">
{/* ---- STICKY COMMENT INPUT TRAY BAR (z-20 stays above comments) ---- */}
<div className="sticky bottom-0 z-20 p-4 bg-slate-950 border-t border-slate-800/80 pb-[calc(1rem+env(safe-area-inset-bottom,0px))] md:pb-4">
<div className="relative flex items-center gap-2">
<input
type="text"
@@ -790,22 +740,19 @@ export const PublicPhotoModal: React.FC<PublicPhotoModalProps> = ({
disabled={isSending}
/>
<button
onClick={(e) => {
e.stopPropagation();
handleSend();
}}
onClick={(e) => { e.stopPropagation(); handleSend(); }}
disabled={!newComment.trim() || isSending}
className="p-3 bg-emerald-600 hover:bg-emerald-500 disabled:bg-slate-800 disabled:opacity-40 text-white rounded-2xl transition-all active:scale-95 shadow-lg shadow-emerald-950/30 flex items-center justify-center"
className="p-3 bg-emerald-600 hover:bg-emerald-500 disabled:bg-slate-800 disabled:opacity-40 text-white rounded-2xl transition-all active:scale-95 shadow-lg flex items-center justify-center"
>
{isSending ? (
<Loader2 className="w-4.5 h-4.5 animate-spin" />
) : (
<Send className="w-4.5 h-4.5" />
)}
{isSending ? <Loader2 className="w-4 h-4 animate-spin" /> : <Send className="w-4 h-4" />}
</button>
</div>
</div>
<style>{`
.no-scrollbar::-webkit-scrollbar { display: none; }
.no-scrollbar { -ms-overflow-style: none; scrollbar-width: none; }
`}</style>
</div>
</div>