fixed getSaleProperiesById and make login type is email
All checks were successful
Build frontend / build (push) Successful in 1m46s
All checks were successful
Build frontend / build (push) Successful in 1m46s
This commit is contained in:
@ -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")}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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/>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -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
@ -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 {
|
||||
|
||||
@ -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} />;
|
||||
}
|
||||
|
||||
@ -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];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user