fix: tạo tags cho ảnh public của guest

This commit is contained in:
2026-06-23 16:01:46 +07:00
parent 9f41400bd8
commit 28ea9abccd
28 changed files with 601 additions and 60 deletions
+96 -36
View File
@@ -2,6 +2,7 @@ import React, { useState, useRef, useEffect } from 'react';
import { LogIn, Compass, Map as MapIcon, Camera, ShieldAlert } from 'lucide-react';
import { LoginModal } from '../components/LoginModal';
import { ReportBusinessModal } from '../components/ReportBusinessModal';
import { TagSelectModal } from '../components/TagSelectModal';
import { useNotification } from '@/hooks/useNotification';
import { processImageModeration } from '../hooks/useImageModeration';
import { useTranslation } from '../hooks/useTranslation';
@@ -18,7 +19,12 @@ interface LandingPageProps {
export const LandingPage: React.FC<LandingPageProps> = ({ onGoToSignup, onGoToMap, onLoginSuccess }) => {
const [isLoginModalOpen, setIsLoginModalOpen] = useState(false);
const [isReportModalOpen, setIsReportModalOpen] = useState(false);
const [isTagsModalOpen, setIsTagsModalOpen] = useState(false);
const fileInputRef = useRef<HTMLInputElement>(null);
const [pendingPhotoFile, setPendingPhotoFile] = useState<File | null>(null);
const [pendingPhotoLocation, setPendingPhotoLocation] = useState<GeolocationPosition | null>(null);
const [photoPreviewUrl, setPhotoPreviewUrl] = useState<string>('');
const notify = useNotification();
const { t, lang, changeLanguage } = useTranslation();
const { theme, changeTheme } = useTheme();
@@ -26,7 +32,6 @@ export const LandingPage: React.FC<LandingPageProps> = ({ onGoToSignup, onGoToMa
const [publicPhotos, setPublicPhotos] = useState<any[]>([]);
const [trustedUsers, setTrustedUsers] = useState<any[]>([]);
const [blacklist, setBlacklist] = useState<any[]>([]);
const [isReportModalOpen, setIsReportModalOpen] = useState(false);
const [currentBgIndex, setCurrentBgIndex] = useState(0);
const [bg1, setBg1] = useState('/background.avif');
const [bg2, setBg2] = useState('');
@@ -144,30 +149,58 @@ export const LandingPage: React.FC<LandingPageProps> = ({ onGoToSignup, onGoToMa
new Promise<null>((resolve) => setTimeout(() => resolve(null), 4500))
]);
// 1. Xóa token cũ nếu có để đảm bảo guest không bị nhầm lẫn
localStorage.removeItem('token');
localStorage.removeItem('user');
// 2. Tạo tài khoản khách và lấy token
let guestToken = localStorage.getItem('guest_token');
let guestUser = JSON.parse(localStorage.getItem('guest_user') || 'null');
if (!guestToken) {
const guestRes = await fetch('/api/v1/auth/create-guest', { method: 'POST' });
if (!guestRes.ok) throw new Error('Không thể tạo phiên khách.');
const guestData = await guestRes.json();
guestToken = guestData.access_token;
guestUser = guestData.user;
localStorage.setItem('guest_token', guestToken!);
localStorage.setItem('guest_user', JSON.stringify(guestUser));
}
// Lưu file và location vào state pending, hiển thị modal tags
setPendingPhotoFile(processedFile);
setPendingPhotoLocation(location);
// Tạo preview URL cho ảnh
const previewUrl = URL.createObjectURL(processedFile);
setPhotoPreviewUrl(previewUrl);
setIsTagsModalOpen(true);
} catch (error: any) {
notify({ title: 'Lỗi', message: error.message, type: 'error' });
} finally {
// Reset input để có thể chọn lại cùng 1 file
if (event.target) event.target.value = '';
}
};
const handleConfirmTags = async (selectedTags: string[]) => {
if (!pendingPhotoFile) return;
setIsTagsModalOpen(false);
notify({ title: 'Đang tải lên...', message: 'Vui lòng chờ trong giây lát.', type: 'info' });
try {
// 1. Xóa token cũ nếu có để đảm bảo guest không bị nhầm lẫn
localStorage.removeItem('token');
localStorage.removeItem('user');
// 2. Tạo tài khoản khách và lấy token
let guestToken = localStorage.getItem('guest_token');
let guestUser = JSON.parse(localStorage.getItem('guest_user') || 'null');
// 2. Tải ảnh lên
if (!guestToken) {
const guestRes = await fetch('/api/v1/auth/create-guest', { method: 'POST' });
if (!guestRes.ok) throw new Error('Không thể tạo phiên khách.');
const guestData = await guestRes.json();
guestToken = guestData.access_token;
guestUser = guestData.user;
localStorage.setItem('guest_token', guestToken!);
localStorage.setItem('guest_user', JSON.stringify(guestUser));
}
// 3. Tải ảnh lên
const formData = new FormData();
formData.append('images', processedFile);
if (location) {
formData.append('latitude', location.coords.latitude.toString());
formData.append('longitude', location.coords.longitude.toString());
formData.append('images', pendingPhotoFile);
if (pendingPhotoLocation) {
formData.append('latitude', pendingPhotoLocation.coords.latitude.toString());
formData.append('longitude', pendingPhotoLocation.coords.longitude.toString());
}
// Thêm tags vào formData
if (selectedTags.length > 0) {
formData.append('tags', JSON.stringify(selectedTags));
}
let uploadRes = await fetch('/api/v1/photos/upload-anonymous', {
@@ -203,23 +236,34 @@ export const LandingPage: React.FC<LandingPageProps> = ({ onGoToSignup, onGoToMa
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' });
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);
url.searchParams.delete('token');
window.history.replaceState({}, '', url.toString());
// 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);
url.searchParams.delete('token');
window.history.replaceState({}, '', url.toString());
// Cập nhật lại danh sách ảnh lập tức
await fetchPublicPhotos();
setCurrentBgIndex(0);
// Cập nhật lại danh sách ảnh lập tức
await fetchPublicPhotos();
setCurrentBgIndex(0);
// Xóa pending data
setPendingPhotoFile(null);
setPendingPhotoLocation(null);
if (photoPreviewUrl) {
URL.revokeObjectURL(photoPreviewUrl);
setPhotoPreviewUrl('');
}
} catch (error: any) {
notify({ title: 'Lỗi', message: error.message, type: 'error' });
} finally {
// Reset input để có thể chọn lại cùng 1 file
if (event.target) event.target.value = '';
// Cleanup on error too
setPendingPhotoFile(null);
setPendingPhotoLocation(null);
if (photoPreviewUrl) {
URL.revokeObjectURL(photoPreviewUrl);
setPhotoPreviewUrl('');
}
}
};
@@ -549,6 +593,22 @@ return (
isOpen={isReportModalOpen}
onClose={() => setIsReportModalOpen(false)}
/>
{/* Tag Select Modal */}
<TagSelectModal
isOpen={isTagsModalOpen}
onClose={() => {
setIsTagsModalOpen(false);
setPendingPhotoFile(null);
setPendingPhotoLocation(null);
if (photoPreviewUrl) {
URL.revokeObjectURL(photoPreviewUrl);
setPhotoPreviewUrl('');
}
}}
onConfirm={handleConfirmTags}
photoUrl={photoPreviewUrl}
/>
</div>
);
};