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
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={handleLogout}
|
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" />
|
<LogOut className="w-5 h-5" />
|
||||||
{t("blocked.logoutButton")}
|
{t("blocked.logoutButton")}
|
||||||
|
|||||||
@ -61,7 +61,7 @@ export default function Login() {
|
|||||||
setStep("otp");
|
setStep("otp");
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
toast.error("Login failed the email or password is wrong ");
|
toast.error("Login failed the email or password is wrong");
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("[Login] Error:", 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"}`} />
|
<Phone className={`w-5 h-5 transition-colors ${errors.credential ? "text-red-500" : "text-gray-400 group-focus-within:text-amber-500"}`} />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<input
|
{loginMethod === "email" ? (
|
||||||
type="text"
|
<>
|
||||||
// type={loginMethod === 'email' ? 'email' : 'tel'}
|
<input
|
||||||
value={formData.credential}
|
type="email"
|
||||||
onChange={(e) => handleCredentialChange(e.target.value)}
|
// type={loginMethod === 'email' ? 'email' : 'tel'}
|
||||||
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 ${
|
value={formData.credential}
|
||||||
errors.credential ? "border-red-500" : "border-gray-700"
|
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 ${
|
||||||
placeholder={loginMethod === "email" ? t("emailPlaceholder") : t("phonePlaceholder")}
|
errors.credential ? "border-red-500" : "border-gray-700"
|
||||||
dir="ltr"
|
}`}
|
||||||
/>
|
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>
|
</div>
|
||||||
{errors.credential && (
|
{errors.credential && (
|
||||||
<motion.p initial={{ opacity: 0, y: -10 }} animate={{ opacity: 1, y: 0 }} className="text-red-500 text-sm mt-1">
|
<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 toast, { Toaster } from "react-hot-toast";
|
||||||
import { getMyRentListings, getOwnerReservationRequests } from "@/app/utils/api";
|
import { getMyRentListings, getOwnerReservationRequests } from "@/app/utils/api";
|
||||||
import useAuth from "@/app/hooks/useAuth";
|
import useAuth from "@/app/hooks/useAuth";
|
||||||
|
import Loading from "@/app/loading";
|
||||||
|
|
||||||
const OwnerBookingCalendar = ({ property, onDateSelect, selectedDates }) => {
|
const OwnerBookingCalendar = ({ property, onDateSelect, selectedDates }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@ -476,12 +477,7 @@ export default function OwnerBookingsPage() {
|
|||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
|
<Loading/>
|
||||||
<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>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -665,6 +665,7 @@ import {
|
|||||||
import toast, { Toaster } from 'react-hot-toast';
|
import toast, { Toaster } from 'react-hot-toast';
|
||||||
import AuthService from '../../services/AuthService';
|
import AuthService from '../../services/AuthService';
|
||||||
import { getRentProperties } from '../../utils/api';
|
import { getRentProperties } from '../../utils/api';
|
||||||
|
import Loading from '@/app/loading';
|
||||||
|
|
||||||
const API_BASE =
|
const API_BASE =
|
||||||
process.env.NEXT_PUBLIC_API_URL || 'https://45.93.137.91.nip.io/api';
|
process.env.NEXT_PUBLIC_API_URL || 'https://45.93.137.91.nip.io/api';
|
||||||
@ -1433,9 +1434,7 @@ export default function OwnerReservationRequestsPage() {
|
|||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
|
<Loading/>
|
||||||
<Loader2 className="w-12 h-12 text-amber-500 animate-spin" />
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
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 { 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";
|
||||||
import { useParams, useRouter } from "next/navigation";
|
import { useParams, useRouter, useSearchParams } from "next/navigation";
|
||||||
import {
|
import {
|
||||||
MapPin,
|
MapPin,
|
||||||
Bed,
|
Bed,
|
||||||
@ -244,9 +244,10 @@ function mapApiDetail(item) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function PropertyDetailsPage() {
|
export default function PropertyDetailsPage({type}) {
|
||||||
const { t, i18n } = useTranslation();
|
const { t, i18n } = useTranslation();
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { isFavorite, addFavorite, removeFavorite } = useFavorites();
|
const { isFavorite, addFavorite, removeFavorite } = useFavorites();
|
||||||
|
|
||||||
@ -271,6 +272,7 @@ export default function PropertyDetailsPage() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const id = params.id;
|
const id = params.id;
|
||||||
|
|
||||||
if (!id) return;
|
if (!id) return;
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
@ -278,7 +280,7 @@ export default function PropertyDetailsPage() {
|
|||||||
try {
|
try {
|
||||||
let data = null;
|
let data = null;
|
||||||
try {
|
try {
|
||||||
data = await getRentProperty(id);
|
data = type==="rent"?await getRentProperty(id):(await getSalePropertyById(id)) || (await getSaleProperty(id));
|
||||||
} catch {}
|
} catch {}
|
||||||
if (!data) {
|
if (!data) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@ -1,15 +1,20 @@
|
|||||||
import PropertyDetail from "./PropertyDetail";
|
import PropertyDetail from "./PropertyDetail";
|
||||||
|
|
||||||
// Server-side API fetch for metadata (runs at request time on server)
|
async function fetchPropertyForMeta(id, type) {
|
||||||
async function fetchPropertyForMeta(id) {
|
|
||||||
try {
|
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 },
|
next: { revalidate: 60 },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!res.ok) return null;
|
if (!res.ok) return null;
|
||||||
|
|
||||||
const text = await res.text();
|
const text = await res.text();
|
||||||
const json = JSON.parse(text);
|
const json = JSON.parse(text);
|
||||||
|
|
||||||
const items = Array.isArray(json?.data) ? json.data : Array.isArray(json) ? json : [];
|
const items = Array.isArray(json?.data) ? json.data : Array.isArray(json) ? json : [];
|
||||||
|
|
||||||
return items.find((p) => p.id == id) || items[0] || null;
|
return items.find((p) => p.id == id) || items[0] || null;
|
||||||
} catch {
|
} catch {
|
||||||
return null;
|
return null;
|
||||||
@ -18,7 +23,9 @@ async function fetchPropertyForMeta(id) {
|
|||||||
|
|
||||||
function mapProperty(item) {
|
function mapProperty(item) {
|
||||||
const info = item.propertyInformation || item.PropertyInformation || {};
|
const info = item.propertyInformation || item.PropertyInformation || {};
|
||||||
|
|
||||||
let details = {};
|
let details = {};
|
||||||
|
|
||||||
if (typeof info.detailsJSON === "object" && info.detailsJSON) {
|
if (typeof info.detailsJSON === "object" && info.detailsJSON) {
|
||||||
details = info.detailsJSON;
|
details = info.detailsJSON;
|
||||||
} else if (typeof info.DetailsJSON === "object" && 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 price = item.monthlyRent || item.MonthlyRent || item.dailyRent || item.DailyRent || 0;
|
||||||
|
|
||||||
const priceUnit = item.monthlyRent || item.MonthlyRent ? "monthly" : "daily";
|
const priceUnit = item.monthlyRent || item.MonthlyRent ? "monthly" : "daily";
|
||||||
|
|
||||||
const buildingType = info.buildingType ?? info.BuildingType ?? 0;
|
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 address = info.address || info.Address || "";
|
||||||
const bedrooms = info.numberOfBedRooms || info.NumberOfBedRooms || 0;
|
const bedrooms = info.numberOfBedRooms || info.NumberOfBedRooms || 0;
|
||||||
const bathrooms = info.numberOfBathRooms || info.NumberOfBathRooms || 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 { 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) {
|
if (!raw) {
|
||||||
return {
|
return {
|
||||||
@ -68,11 +94,13 @@ export async function generateMetadata({ params }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const p = mapProperty(raw);
|
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
|
const ogImages = propertyImage
|
||||||
? [
|
? [
|
||||||
{ url: propertyImage, width: 1200, height: 630 },
|
{ url: propertyImage, width: 1200, height: 630 },
|
||||||
@ -83,14 +111,16 @@ export async function generateMetadata({ params }) {
|
|||||||
return {
|
return {
|
||||||
title: `${p.title} - ${priceStr}`,
|
title: `${p.title} - ${priceStr}`,
|
||||||
description: p.description,
|
description: p.description,
|
||||||
|
|
||||||
openGraph: {
|
openGraph: {
|
||||||
title: `${p.title} - ${priceStr}`,
|
title: `${p.title} - ${priceStr}`,
|
||||||
description: p.description,
|
description: p.description,
|
||||||
images: ogImages,
|
images: ogImages,
|
||||||
url: `http://45.93.137.91/property/${id}`,
|
url: `http://45.93.137.91/property/${id}?type=${type}`,
|
||||||
type: "website",
|
type: "website",
|
||||||
siteName: "SweetHome",
|
siteName: "SweetHome",
|
||||||
},
|
},
|
||||||
|
|
||||||
twitter: {
|
twitter: {
|
||||||
card: "summary_large_image",
|
card: "summary_large_image",
|
||||||
title: `${p.title} - ${priceStr}`,
|
title: `${p.title} - ${priceStr}`,
|
||||||
@ -100,6 +130,8 @@ export async function generateMetadata({ params }) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function PropertyPage({ params }) {
|
export default async function PropertyPage({ params, searchParams }) {
|
||||||
return <PropertyDetail params={params} />;
|
const { type } = await searchParams;
|
||||||
|
|
||||||
|
return <PropertyDetail params={params} type={type} />;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -192,7 +192,7 @@ export async function getSaleProperties() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function getSaleProperty(id) {
|
export async function getSaleProperty(id) {
|
||||||
const items = await apiFetch("/SaleProperties/GetSaleProperties");
|
const items = await apiFetch(`/SaleProperties/GetSalePropertyById/${id}`);
|
||||||
if (!Array.isArray(items)) return items;
|
if (!Array.isArray(items)) return items;
|
||||||
return items.find((p) => p.id == id) || items[0];
|
return items.find((p) => p.id == id) || items[0];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -40,6 +40,7 @@ export function middleware(request) {
|
|||||||
return NextResponse.redirect(new URL("/", request.url));
|
return NextResponse.redirect(new URL("/", request.url));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (isOwnerRoute && !isOwner) {
|
if (isOwnerRoute && !isOwner) {
|
||||||
return NextResponse.redirect(new URL("/", request.url));
|
return NextResponse.redirect(new URL("/", request.url));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user