🚀 Complete Laca City Website with VPS Deployment
- Added complete Next.js frontend with responsive design - Added NestJS backend with PostgreSQL and Redis - Added comprehensive VPS deployment script (vps-deploy.sh) - Added deployment guide and documentation - Added all assets and static files - Configured SSL, Nginx, PM2, and monitoring - Ready for production deployment on any VPS
This commit is contained in:
@@ -8,23 +8,9 @@ import { HCMCGPSSimulator } from '@/components/HCMCGPSSimulator';
|
||||
// import { ErrorMessage } from '@/components/ui/ErrorMessage';
|
||||
import { LoadingSpinner } from '@/components/ui/LoadingSpinner';
|
||||
import { useParkingSearch } from '@/hooks/useParkingSearch';
|
||||
import { useRouting } from '@/hooks/useRouting';
|
||||
import { ParkingLot, UserLocation, TransportationMode } from '@/types';
|
||||
import toast from 'react-hot-toast';
|
||||
|
||||
// Dynamic import for map component (client-side only)
|
||||
const MapView = dynamic(
|
||||
() => import('@/components/map/MapView').then((mod) => mod.MapView),
|
||||
{
|
||||
ssr: false,
|
||||
loading: () => (
|
||||
<div className="h-full flex items-center justify-center bg-gray-100 rounded-lg">
|
||||
<LoadingSpinner size="lg" />
|
||||
</div>
|
||||
),
|
||||
}
|
||||
);
|
||||
|
||||
export default function ParkingFinderPage() {
|
||||
// State management
|
||||
const [selectedParkingLot, setSelectedParkingLot] = useState<ParkingLot | null>(null);
|
||||
@@ -42,14 +28,6 @@ export default function ParkingFinderPage() {
|
||||
searchLocation
|
||||
} = useParkingSearch();
|
||||
|
||||
const {
|
||||
route,
|
||||
isLoading: routeLoading,
|
||||
error: routeError,
|
||||
calculateRoute,
|
||||
clearRoute
|
||||
} = useRouting();
|
||||
|
||||
// Handle GPS location change from simulator
|
||||
const handleLocationChange = (location: UserLocation) => {
|
||||
setUserLocation(location);
|
||||
@@ -70,35 +48,16 @@ export default function ParkingFinderPage() {
|
||||
}
|
||||
};
|
||||
|
||||
const handleParkingLotSelect = async (lot: ParkingLot) => {
|
||||
const handleParkingLotSelect = (lot: ParkingLot) => {
|
||||
// If the same parking lot is selected again, deselect it
|
||||
if (selectedParkingLot && selectedParkingLot.id === lot.id) {
|
||||
setSelectedParkingLot(null);
|
||||
clearRoute();
|
||||
toast.success('Đã bỏ chọn bãi đỗ xe');
|
||||
return;
|
||||
}
|
||||
|
||||
setSelectedParkingLot(lot);
|
||||
|
||||
if (userLocation) {
|
||||
try {
|
||||
await calculateRoute(
|
||||
{ latitude: userLocation.lat, longitude: userLocation.lng },
|
||||
{ latitude: lot.lat, longitude: lot.lng },
|
||||
{ mode: 'driving' }
|
||||
);
|
||||
toast.success(`Đã tính đường đến ${lot.name}`);
|
||||
} catch (error) {
|
||||
toast.error('Không thể tính toán đường đi');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleClearRoute = () => {
|
||||
clearRoute();
|
||||
setSelectedParkingLot(null);
|
||||
toast.success('Đã xóa tuyến đường');
|
||||
toast.success(`Đã chọn ${lot.name}`);
|
||||
};
|
||||
|
||||
// Show error messages
|
||||
@@ -108,35 +67,35 @@ export default function ParkingFinderPage() {
|
||||
}
|
||||
}, [parkingError]);
|
||||
|
||||
useEffect(() => {
|
||||
if (routeError) {
|
||||
toast.error(routeError);
|
||||
}
|
||||
}, [routeError]);
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50">
|
||||
<Header
|
||||
title="Smart Parking Finder - TP.HCM"
|
||||
subtitle="Chỉ hỗ trợ ô tô"
|
||||
onClearRoute={route ? handleClearRoute : undefined}
|
||||
/>
|
||||
|
||||
<main className="container mx-auto px-4 py-6">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-4 gap-6 h-full">
|
||||
{/* Left Column - Map and Parking List */}
|
||||
<div className="lg:col-span-3 space-y-6">
|
||||
{/* Map Section */}
|
||||
{/* Summary Section */}
|
||||
<div className="bg-white rounded-xl shadow-lg overflow-hidden">
|
||||
<div className="h-96">
|
||||
<MapView
|
||||
userLocation={userLocation}
|
||||
parkingLots={parkingLots}
|
||||
selectedParkingLot={selectedParkingLot}
|
||||
route={route}
|
||||
onParkingLotSelect={handleParkingLotSelect}
|
||||
isLoading={routeLoading}
|
||||
/>
|
||||
<div className="h-96 bg-gradient-to-br from-gray-50 to-blue-50 flex items-center justify-center">
|
||||
<div className="text-center p-8">
|
||||
<div className="w-24 h-24 bg-blue-100 rounded-full mx-auto mb-6 flex items-center justify-center">
|
||||
<svg className="w-12 h-12 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z" />
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 11a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
</svg>
|
||||
</div>
|
||||
<h2 className="text-2xl font-bold text-gray-800 mb-3">Parking Finder - HCMC</h2>
|
||||
<p className="text-gray-600 mb-4">Find and book parking spots in Ho Chi Minh City</p>
|
||||
{parkingLots.length > 0 && (
|
||||
<div className="text-sm text-gray-500">
|
||||
Found {parkingLots.length} parking locations nearby
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -199,14 +158,6 @@ export default function ParkingFinderPage() {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{routeError && (
|
||||
<div className="fixed bottom-4 right-4 max-w-sm">
|
||||
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded">
|
||||
{routeError}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user