Compare commits
3 Commits
5d593d593f
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| bef133ad5b | |||
| a9eb1cc684 | |||
| 13b563e35e |
@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState, useEffect, useMemo } from "react";
|
import { useState, useEffect, useMemo, useRef } from "react";
|
||||||
import { motion, AnimatePresence } from "framer-motion";
|
import { motion, AnimatePresence } from "framer-motion";
|
||||||
import toast, { Toaster } from "react-hot-toast";
|
import toast, { Toaster } from "react-hot-toast";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
@ -69,23 +69,70 @@ import { useFavorites } from "@/app/contexts/FavoritesContext";
|
|||||||
import { BuildingTypeKeys, PropertyStatusKeys, extractCity } from "../../enums";
|
import { BuildingTypeKeys, PropertyStatusKeys, extractCity } from "../../enums";
|
||||||
import PropertyRatingList from "@/app/components/ratings/PropertyRatingList";
|
import PropertyRatingList from "@/app/components/ratings/PropertyRatingList";
|
||||||
import { getPropertyAverageRating } from "../../utils/ratings";
|
import { getPropertyAverageRating } from "../../utils/ratings";
|
||||||
import dynamic from "next/dynamic";
|
|
||||||
import "leaflet/dist/leaflet.css";
|
import "leaflet/dist/leaflet.css";
|
||||||
|
|
||||||
const MapContainer = dynamic(
|
function PropertyDetailMap({ lat, lng, title }) {
|
||||||
() => import("react-leaflet").then((m) => m.MapContainer),
|
const mapRef = useRef(null);
|
||||||
{ ssr: false },
|
const mapInstanceRef = useRef(null);
|
||||||
);
|
const markerRef = useRef(null);
|
||||||
const TileLayer = dynamic(
|
|
||||||
() => import("react-leaflet").then((m) => m.TileLayer),
|
useEffect(() => {
|
||||||
{ ssr: false },
|
if (!mapRef.current || mapInstanceRef.current) return;
|
||||||
);
|
|
||||||
const Marker = dynamic(() => import("react-leaflet").then((m) => m.Marker), {
|
if (mapRef.current._leaflet_id && !mapInstanceRef.current) {
|
||||||
ssr: false,
|
delete mapRef.current._leaflet_id;
|
||||||
});
|
}
|
||||||
const Popup = dynamic(() => import("react-leaflet").then((m) => m.Popup), {
|
|
||||||
ssr: false,
|
const L = require("leaflet");
|
||||||
});
|
|
||||||
|
delete L.Icon.Default.prototype._getIconUrl;
|
||||||
|
L.Icon.Default.mergeOptions({
|
||||||
|
iconRetinaUrl:
|
||||||
|
"https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-icon-2x.png",
|
||||||
|
iconUrl:
|
||||||
|
"https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-icon.png",
|
||||||
|
shadowUrl:
|
||||||
|
"https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-shadow.png",
|
||||||
|
});
|
||||||
|
|
||||||
|
const map = L.map(mapRef.current, {
|
||||||
|
center: [lat, lng],
|
||||||
|
zoom: 14,
|
||||||
|
scrollWheelZoom: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
|
||||||
|
attribution:
|
||||||
|
'© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
|
||||||
|
maxZoom: 19,
|
||||||
|
}).addTo(map);
|
||||||
|
|
||||||
|
const marker = L.marker([lat, lng]).addTo(map).bindPopup(title);
|
||||||
|
mapInstanceRef.current = map;
|
||||||
|
markerRef.current = marker;
|
||||||
|
map.invalidateSize();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
markerRef.current?.remove();
|
||||||
|
markerRef.current = null;
|
||||||
|
if (mapInstanceRef.current) {
|
||||||
|
mapInstanceRef.current.remove();
|
||||||
|
mapInstanceRef.current = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [lat, lng, title]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (mapInstanceRef.current) {
|
||||||
|
mapInstanceRef.current.setView([lat, lng], 14);
|
||||||
|
markerRef.current?.setLatLng([lat, lng]);
|
||||||
|
markerRef.current?.setPopupContent(title);
|
||||||
|
mapInstanceRef.current.invalidateSize();
|
||||||
|
}
|
||||||
|
}, [lat, lng, title]);
|
||||||
|
|
||||||
|
return <div ref={mapRef} className="h-full w-full" />;
|
||||||
|
}
|
||||||
|
|
||||||
function formatCurrency(amount) {
|
function formatCurrency(amount) {
|
||||||
if (!amount || isNaN(amount)) return "0";
|
if (!amount || isNaN(amount)) return "0";
|
||||||
@ -1243,19 +1290,11 @@ export default function PropertyDetailsPage() {
|
|||||||
className="bg-white rounded-2xl overflow-hidden shadow-sm border border-gray-200"
|
className="bg-white rounded-2xl overflow-hidden shadow-sm border border-gray-200"
|
||||||
>
|
>
|
||||||
<div className="h-64">
|
<div className="h-64">
|
||||||
<MapContainer
|
<PropertyDetailMap
|
||||||
center={[property.location.lat, property.location.lng]}
|
lat={property.location.lat}
|
||||||
zoom={14}
|
lng={property.location.lng}
|
||||||
className="h-full w-full"
|
title={property.title}
|
||||||
scrollWheelZoom={false}
|
/>
|
||||||
>
|
|
||||||
<TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />
|
|
||||||
<Marker
|
|
||||||
position={[property.location.lat, property.location.lng]}
|
|
||||||
>
|
|
||||||
<Popup>{property.title}</Popup>
|
|
||||||
</Marker>
|
|
||||||
</MapContainer>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="p-3 bg-amber-50 text-center text-sm text-amber-700 flex items-center justify-center gap-2">
|
<div className="p-3 bg-amber-50 text-center text-sm text-amber-700 flex items-center justify-center gap-2">
|
||||||
<Info className="w-4 h-4" />
|
<Info className="w-4 h-4" />
|
||||||
|
|||||||
@ -449,6 +449,10 @@
|
|||||||
// });
|
// });
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
import AuthService from '../services/AuthService';
|
import AuthService from '../services/AuthService';
|
||||||
const API_BASE = process.env.NEXT_PUBLIC_API_URL || 'https://45.93.137.91.nip.io/api';
|
const API_BASE = process.env.NEXT_PUBLIC_API_URL || 'https://45.93.137.91.nip.io/api';
|
||||||
const REPORT_API_BASE = process.env.NEXT_PUBLIC_REPORT_API_URL || 'http://45.93.137.91/api';
|
const REPORT_API_BASE = process.env.NEXT_PUBLIC_REPORT_API_URL || 'http://45.93.137.91/api';
|
||||||
@ -505,7 +509,13 @@ async function apiFetch(endpoint, options = {}) {
|
|||||||
headers['Content-Type'] = 'application/json';
|
headers['Content-Type'] = 'application/json';
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = await fetch(`${API_BASE}${endpoint}`, {
|
const url = `${API_BASE}${endpoint}`;
|
||||||
|
|
||||||
|
console.log('API Request:', url);
|
||||||
|
console.log('API Method:', options.method || 'GET');
|
||||||
|
console.log('API Body:', hasBody ? options.body : null);
|
||||||
|
|
||||||
|
const res = await fetch(url, {
|
||||||
...options,
|
...options,
|
||||||
headers,
|
headers,
|
||||||
body:
|
body:
|
||||||
@ -514,10 +524,13 @@ async function apiFetch(endpoint, options = {}) {
|
|||||||
: options.body,
|
: options.body,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.log('API Response Status:', res.status);
|
||||||
|
console.log('API Response OK:', res.ok);
|
||||||
assertNotBlocked(res);
|
assertNotBlocked(res);
|
||||||
|
|
||||||
if (!res.ok && res.status !== 206) {
|
if (!res.ok && res.status !== 206) {
|
||||||
const text = await res.text().catch(() => '');
|
const text = await res.text().catch(() => '');
|
||||||
|
console.error('API Error Response:', text || res.statusText);
|
||||||
throw new Error(`API ${res.status}: ${text || res.statusText}`);
|
throw new Error(`API ${res.status}: ${text || res.statusText}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1104,7 +1117,7 @@ export async function sendGeneralReport(subject, reportBody) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function submitReport(subject, body) {
|
export async function submitReport(subject, body) {
|
||||||
return apiFetch('/Reports', {
|
return apiFetch('/Reports/SendGeneralReport', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: { subject, body },
|
body: { subject, body },
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user