fixed getSaleProperiesById and make login type is email
All checks were successful
Build frontend / build (push) Successful in 1m46s

This commit is contained in:
hamzaobed7
2026-08-11 16:04:09 +03:00
parent 4924e7c2f3
commit 00dde588cb
10 changed files with 421 additions and 394 deletions

View File

@ -80,7 +80,7 @@ export default function BlockedPage() {
<button
type="button"
onClick={handleLogout}
className="mt-8 w-full bg-red-600 hover:bg-red-700 text-white rounded-2xl py-4 font-bold transition-colors flex items-center justify-center gap-3"
className="mt-8 w-full cursor-pointer bg-red-600 hover:bg-red-700 text-white rounded-2xl py-4 font-bold transition-colors flex items-center justify-center gap-3"
>
<LogOut className="w-5 h-5" />
{t("blocked.logoutButton")}

View File

@ -61,7 +61,7 @@ export default function Login() {
setStep("otp");
break;
default:
toast.error("Login failed the email or password is wrong ");
toast.error("Login failed the email or password is wrong");
}
} catch (err) {
console.error("[Login] Error:", err);

View File

@ -19,17 +19,34 @@ export default function LoginForm({ loginMethod, errors, formData, isLoading, is
<Phone className={`w-5 h-5 transition-colors ${errors.credential ? "text-red-500" : "text-gray-400 group-focus-within:text-amber-500"}`} />
)}
</div>
<input
type="text"
// type={loginMethod === 'email' ? 'email' : 'tel'}
value={formData.credential}
onChange={(e) => handleCredentialChange(e.target.value)}
className={`w-full pr-12 pl-4 py-4 bg-white/5 border rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500 focus:border-transparent text-amber-50 placeholder-gray-500 transition-all ${
errors.credential ? "border-red-500" : "border-gray-700"
}`}
placeholder={loginMethod === "email" ? t("emailPlaceholder") : t("phonePlaceholder")}
dir="ltr"
/>
{loginMethod === "email" ? (
<>
<input
type="email"
// type={loginMethod === 'email' ? 'email' : 'tel'}
value={formData.credential}
onChange={(e) => handleCredentialChange(e.target.value)}
className={`w-full pr-12 pl-4 py-4 bg-white/5 border rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500 focus:border-transparent text-amber-50 placeholder-gray-500 transition-all ${
errors.credential ? "border-red-500" : "border-gray-700"
}`}
placeholder={t("emailPlaceholder")}
dir="ltr"
/>
</>
) : (
<>
<input
type="text"
value={formData.credential}
onChange={(e) => handleCredentialChange(e.target.value)}
className={`w-full pr-12 pl-4 py-4 bg-white/5 border rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500 focus:border-transparent text-amber-50 placeholder-gray-500 transition-all ${
errors.credential ? "border-red-500" : "border-gray-700"
}`}
placeholder={t("phonePlaceholder")}
dir="ltr"
/>
</>
)}
</div>
{errors.credential && (
<motion.p initial={{ opacity: 0, y: -10 }} animate={{ opacity: 1, y: 0 }} className="text-red-500 text-sm mt-1">

View File

@ -8,6 +8,7 @@ import { Calendar, User, Mail, Phone, CheckCircle, XCircle, Clock, MapPin, Bed,
import toast, { Toaster } from "react-hot-toast";
import { getMyRentListings, getOwnerReservationRequests } from "@/app/utils/api";
import useAuth from "@/app/hooks/useAuth";
import Loading from "@/app/loading";
const OwnerBookingCalendar = ({ property, onDateSelect, selectedDates }) => {
const { t } = useTranslation();
@ -476,12 +477,7 @@ export default function OwnerBookingsPage() {
if (isLoading) {
return (
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
<div className="text-center">
<Loader2 className="w-12 h-12 text-amber-500 animate-spin mx-auto mb-4" />
<p className="text-gray-600">{t("loading")}</p>
</div>
</div>
<Loading/>
);
}

View File

@ -665,6 +665,7 @@ import {
import toast, { Toaster } from 'react-hot-toast';
import AuthService from '../../services/AuthService';
import { getRentProperties } from '../../utils/api';
import Loading from '@/app/loading';
const API_BASE =
process.env.NEXT_PUBLIC_API_URL || 'https://45.93.137.91.nip.io/api';
@ -1433,9 +1434,7 @@ export default function OwnerReservationRequestsPage() {
if (loading) {
return (
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
<Loader2 className="w-12 h-12 text-amber-500 animate-spin" />
</div>
<Loading/>
);
}

File diff suppressed because it is too large Load Diff

View File

@ -5,7 +5,7 @@ import { useTranslation } from "react-i18next";
import { motion, AnimatePresence } from "framer-motion";
import toast, { Toaster } from "react-hot-toast";
import Link from "next/link";
import { useParams, useRouter } from "next/navigation";
import { useParams, useRouter, useSearchParams } from "next/navigation";
import {
MapPin,
Bed,
@ -244,9 +244,10 @@ function mapApiDetail(item) {
};
}
export default function PropertyDetailsPage() {
export default function PropertyDetailsPage({type}) {
const { t, i18n } = useTranslation();
const params = useParams();
const router = useRouter();
const { isFavorite, addFavorite, removeFavorite } = useFavorites();
@ -271,6 +272,7 @@ export default function PropertyDetailsPage() {
useEffect(() => {
const id = params.id;
if (!id) return;
setLoading(true);
@ -278,7 +280,7 @@ export default function PropertyDetailsPage() {
try {
let data = null;
try {
data = await getRentProperty(id);
data = type==="rent"?await getRentProperty(id):(await getSalePropertyById(id)) || (await getSaleProperty(id));
} catch {}
if (!data) {
try {

View File

@ -1,15 +1,20 @@
import PropertyDetail from "./PropertyDetail";
// Server-side API fetch for metadata (runs at request time on server)
async function fetchPropertyForMeta(id) {
async function fetchPropertyForMeta(id, type) {
try {
const res = await fetch(`http://45.93.137.91/api/RentProperties/GetRentProperties`, {
const endpoint = type === "sale" ? "http://45.93.137.91/api/SaleProperties/GetSaleProperties" : "http://45.93.137.91/api/RentProperties/GetRentProperties";
const res = await fetch(endpoint, {
next: { revalidate: 60 },
});
if (!res.ok) return null;
const text = await res.text();
const json = JSON.parse(text);
const items = Array.isArray(json?.data) ? json.data : Array.isArray(json) ? json : [];
return items.find((p) => p.id == id) || items[0] || null;
} catch {
return null;
@ -18,7 +23,9 @@ async function fetchPropertyForMeta(id) {
function mapProperty(item) {
const info = item.propertyInformation || item.PropertyInformation || {};
let details = {};
if (typeof info.detailsJSON === "object" && info.detailsJSON) {
details = info.detailsJSON;
} else if (typeof info.DetailsJSON === "object" && info.DetailsJSON) {
@ -30,10 +37,25 @@ function mapProperty(item) {
}
const price = item.monthlyRent || item.MonthlyRent || item.dailyRent || item.DailyRent || 0;
const priceUnit = item.monthlyRent || item.MonthlyRent ? "monthly" : "daily";
const buildingType = info.buildingType ?? info.BuildingType ?? 0;
const type = { 0: "apartment", 1: "villa", 2: "house" }[buildingType] || "apartment";
const typeLabel = { 0: "apartment", 1: "villa", 2: "house" }[buildingType] || "apartment";
const type =
{
0: "apartment",
1: "villa",
2: "house",
}[buildingType] || "apartment";
const typeLabel =
{
0: "apartment",
1: "villa",
2: "house",
}[buildingType] || "apartment";
const address = info.address || info.Address || "";
const bedrooms = info.numberOfBedRooms || info.NumberOfBedRooms || 0;
const bathrooms = info.numberOfBathRooms || info.NumberOfBathRooms || 0;
@ -56,9 +78,13 @@ function mapProperty(item) {
};
}
export async function generateMetadata({ params }) {
export async function generateMetadata({ params, searchParams }) {
const { id } = await params;
const raw = await fetchPropertyForMeta(id);
const { type } = await searchParams;
console.log("Property type:", type);
const raw = await fetchPropertyForMeta(id, type);
if (!raw) {
return {
@ -68,11 +94,13 @@ export async function generateMetadata({ params }) {
}
const p = mapProperty(raw);
const priceStr = `${p.price.toLocaleString()} ل.س / ${p.priceUnit === "daily" ? "يوم" : "شهر"}`;
const propertyImage = p.image ? (p.image.startsWith("http") ? p.image : `http://45.93.137.91${p.image}`) : "";
const logoUrl = `http://45.93.137.91/logo.png`;
// Use property image if available, otherwise logo
const priceStr = `${p.price.toLocaleString()} ل.س / ${p.priceUnit === "daily" ? "يوم" : "شهر"}`;
const propertyImage = p.image ? (p.image.startsWith("http") ? p.image : `http://45.93.137.91${p.image}`) : "";
const logoUrl = "http://45.93.137.91/logo.png";
const ogImages = propertyImage
? [
{ url: propertyImage, width: 1200, height: 630 },
@ -83,14 +111,16 @@ export async function generateMetadata({ params }) {
return {
title: `${p.title} - ${priceStr}`,
description: p.description,
openGraph: {
title: `${p.title} - ${priceStr}`,
description: p.description,
images: ogImages,
url: `http://45.93.137.91/property/${id}`,
url: `http://45.93.137.91/property/${id}?type=${type}`,
type: "website",
siteName: "SweetHome",
},
twitter: {
card: "summary_large_image",
title: `${p.title} - ${priceStr}`,
@ -100,6 +130,8 @@ export async function generateMetadata({ params }) {
};
}
export default function PropertyPage({ params }) {
return <PropertyDetail params={params} />;
export default async function PropertyPage({ params, searchParams }) {
const { type } = await searchParams;
return <PropertyDetail params={params} type={type} />;
}

View File

@ -192,7 +192,7 @@ export async function getSaleProperties() {
}
export async function getSaleProperty(id) {
const items = await apiFetch("/SaleProperties/GetSaleProperties");
const items = await apiFetch(`/SaleProperties/GetSalePropertyById/${id}`);
if (!Array.isArray(items)) return items;
return items.find((p) => p.id == id) || items[0];
}