Browse Source

fix: add 'tags' translation to multiple languages and update navigation links for localization

main
sina_sajjadi 6 days ago
parent
commit
15ce806eb6
  1. 9
      package-lock.json
  2. 1
      package.json
  3. 4
      public/locales/ar/common.json
  4. 6
      public/locales/ar/navigation.json
  5. 3
      public/locales/en/common.json
  6. 6
      public/locales/en/navigation.json
  7. 4
      public/locales/id/common.json
  8. 6
      public/locales/id/navigation.json
  9. 3
      public/locales/ru/common.json
  10. 6
      public/locales/ru/navigation.json
  11. 190
      src/app/[locale]/(account-pages)/bills/[slug]/page.tsx
  12. 4
      src/app/[locale]/(account-pages)/bills/page.tsx
  13. 3
      src/app/[locale]/(account-pages)/my-trips/page.tsx
  14. 3
      src/app/[locale]/(account-pages)/passengers-list/PassengerTable.tsx
  15. 3
      src/app/[locale]/(account-pages)/passengers-list/[id]/page.tsx
  16. 3
      src/app/[locale]/(account-pages)/passengers-list/page.tsx
  17. 32
      src/app/[locale]/(client-components)/(Header)/LangDropdown.tsx
  18. 3
      src/app/[locale]/(client-components)/(HeroSearchForm)/GuestsInput.tsx
  19. 3
      src/app/[locale]/(client-components)/(HeroSearchForm2Mobile)/LocationInput.tsx
  20. 3
      src/app/[locale]/(server-components)/SectionHero.tsx
  21. 5
      src/app/[locale]/about/SectionHero.tsx
  22. 4
      src/app/[locale]/about/page.tsx
  23. 3
      src/app/[locale]/add-listing/[[...stepIndex]]/PageAddListing1.tsx
  24. 2
      src/app/[locale]/add-listing/[[...stepIndex]]/PageAddListing10.tsx
  25. 3
      src/app/[locale]/add-listing/[[...stepIndex]]/page.tsx
  26. 3
      src/app/[locale]/add-new-passenger/page.tsx
  27. 5
      src/app/[locale]/blog/Card12.tsx
  28. 5
      src/app/[locale]/blog/Card13.tsx
  29. 42
      src/app/[locale]/blog/[...slug]/Survey.tsx
  30. 26
      src/app/[locale]/blog/[...slug]/page.tsx
  31. 3
      src/app/[locale]/custom-history/page.tsx
  32. 6
      src/app/[locale]/custom-trip/page.tsx
  33. 2
      src/app/[locale]/faq/Table.tsx
  34. 96
      src/app/[locale]/faq/page.tsx
  35. 5
      src/app/[locale]/forgot-password/page.tsx
  36. 7
      src/app/[locale]/login/page.tsx
  37. 3
      src/app/[locale]/page.tsx
  38. 5
      src/app/[locale]/signup/methodes/page.tsx
  39. 5
      src/app/[locale]/signup/otp-code/page.tsx
  40. 7
      src/app/[locale]/signup/page.tsx
  41. 5
      src/app/[locale]/tours/Card.tsx
  42. 18
      src/app/[locale]/tours/SectionGridFilterCard.tsx
  43. 2
      src/app/[locale]/tours/TabFilters.tsx
  44. 3
      src/app/[locale]/tours/[slug]/page.tsx
  45. 3
      src/components/Footer.tsx
  46. 3
      src/components/FooterNav.tsx
  47. 3
      src/components/MobileFooterSticky.tsx
  48. 3
      src/components/SearchCard.tsx
  49. 3
      src/components/SectionCustomTour.tsx
  50. 3
      src/components/SectionGridFeaturePlaces.tsx
  51. 5
      src/components/UserMenu.tsx
  52. 3
      src/components/contexts/userContext.tsx
  53. 5
      src/components/listing-image-gallery/ListingImageGallery.tsx
  54. 8
      src/data/navigation.ts
  55. BIN
      src/images/about-hero-right.jpg
  56. 3
      src/shared/Logo.tsx
  57. 3
      src/shared/Navigation/Navigation.tsx
  58. 6
      src/shared/Navigation/NavigationItem.tsx
  59. 19
      src/utils/routes.ts
  60. 5
      yarn.lock

9
package-lock.json

@ -20,6 +20,7 @@
"@types/react-dom": "18.2.4",
"axios": "^1.7.5",
"client-only": "^0.0.1",
"cookie": "^1.0.2",
"eslint": "8.41.0",
"eslint-config-next": "^13.4.3",
"framer-motion": "^10.12.16",
@ -1359,6 +1360,14 @@
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
"license": "MIT"
},
"node_modules/cookie": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz",
"integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==",
"engines": {
"node": ">=18"
}
},
"node_modules/copy-to-clipboard": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz",

1
package.json

@ -27,6 +27,7 @@
"@types/react-dom": "18.2.4",
"axios": "^1.7.5",
"client-only": "^0.0.1",
"cookie": "^1.0.2",
"eslint": "8.41.0",
"eslint-config-next": "^13.4.3",
"framer-motion": "^10.12.16",

4
public/locales/ar/common.json

@ -227,5 +227,7 @@
"$": "$",
"apply": "تطبيق",
"moreFilters": "المزيد من الفلاتر",
"clear": "مسح"
"clear": "مسح",
"tags" : "وسوم"
}

6
public/locales/ar/navigation.json

@ -11,9 +11,11 @@
"navAccount": "الحساب",
"navMyTrips": "رحلاتي",
"navPassengersList": "قائمة الركاب",
"navBills": "الفواتير",
"navBills": "الفوترة",
"discover-articles": "اكتشف أبرز المقالات حول جميع مواضيع الحياة. اكتب قصصك وشاركها",
"custom-trip": "رحلة مخصصة",
"Where to?": "إلى أين؟",
"Anywhere • Any week • Add guests": "في أي مكان • أي أسبوع • أضف الضيوف"
"Anywhere • Any week • Add guests": "في أي مكان • أي أسبوع • أضف الضيوف",
"text-select-language" : " اختر لغتك"
}

3
public/locales/en/common.json

@ -228,5 +228,6 @@
"$": "$",
"apply" : "Apply",
"moreFilters": "More Filters",
"clear" : "Clear"
"clear" : "Clear",
"tags" : "Tags"
}

6
public/locales/en/navigation.json

@ -11,9 +11,11 @@
"navAccount": "Account",
"navMyTrips": "My Trips",
"navPassengersList": "Passengers List",
"navBills": "Bills",
"navBills": "Billing",
"discover-articles": "Discover the most outstanding articles on all topics of life. Write your stories and share them",
"custom-trip": "Custom Trip",
"Where to?": "Where to?",
"Anywhere • Any week • Add guests": "Anywhere • Any week • Add guests"
"Anywhere • Any week • Add guests": "Anywhere • Any week • Add guests",
"text-select-language" : "Select your language"
}

4
public/locales/id/common.json

@ -227,5 +227,7 @@
"$": "$",
"apply": "Terapkan",
"moreFilters": "Lebih Banyak Filter",
"clear": "Bersihkan"
"clear": "Bersihkan",
"tags" : "Tag"
}

6
public/locales/id/navigation.json

@ -11,9 +11,11 @@
"navAccount": "Akun",
"navMyTrips": "Perjalanan Saya",
"navPassengersList": "Daftar Penumpang",
"navBills": "Tagihan",
"navBills": "Penagihan",
"discover-articles": "Temukan artikel paling menonjol tentang semua topik kehidupan. Tulis cerita Anda dan bagikan",
"custom-trip": "Perjalanan Khusus",
"Where to?": "Kemana?",
"Anywhere • Any week • Add guests": "Dimana saja • Minggu apa saja • Tambahkan tamu"
"Anywhere • Any week • Add guests": "Dimana saja • Minggu apa saja • Tambahkan tamu",
"text-select-language" : "Pilih bahasa Anda"
}

3
public/locales/ru/common.json

@ -227,5 +227,6 @@
"$": "$",
"apply": "Применить",
"moreFilters": "Больше фильтров",
"clear": "Очистить"
"clear": "Очистить",
"tags" : "Теги"
}

6
public/locales/ru/navigation.json

@ -11,10 +11,12 @@
"navAccount": "Аккаунт",
"navMyTrips": "Мои поездки",
"navPassengersList": "Список пассажиров",
"navBills": "Счета",
"navBills": "Биллинг",
"uploadPassword": "Загрузите изображение паспорта здесь",
"discover-articles": "Откройте для себя самые выдающиеся статьи на все темы жизни. Пишите свои истории и делитесь ими",
"custom-trip": "Индивидуальная поездка",
"Where to?": "Куда?",
"Anywhere • Any week • Add guests": "В любом месте • Любая неделя • Добавить гостей"
"Anywhere • Any week • Add guests": "В любом месте • Любая неделя • Добавить гостей",
"text-select-language" : "Выберите ваш язык"
}

190
src/app/[locale]/(account-pages)/bills/[slug]/page.tsx

@ -13,13 +13,34 @@ import { useTranslation } from "react-i18next";
import { BiCloudUpload } from "react-icons/bi";
import { HiOutlineTrash } from "react-icons/hi";
import { useRouter } from "next/router";
import { useParams } from "next/navigation";
// Define separate interfaces for different service types
export type BillStatus =
| "awaiting_payment"
| "approved"
| "rejected"
| "pending";
export interface PassengerData {
adults: { count: number; names: string[] };
children: { count: number; names: string[] };
infants: { count: number; names: string[] };
}
export interface TourDetailService {
tour_id: number;
tour: string;
tour_slug: string;
passenger_data: PassengerData;
}
export interface CustomTripDetail {
total_price: number;
detail: string; // JSON string
destinations: string[];
}
export interface Bill {
id: number;
title: string;
@ -27,7 +48,7 @@ export interface Bill {
expiration_date: string;
amount: number;
status: BillStatus;
passengers: { type: string; count: number }[];
passengers?: { type: string; count: number }[]; // Optional since custom_trip doesn't use this
accountNumber: string;
message?: string;
uploadedImage?: {
@ -35,19 +56,17 @@ export interface Bill {
size: string;
src: string;
};
detail_service: {
passenger_data: {
adults: { count: number; names: string[] };
children: { count: number; names: string[] };
infants: { count: number; names: string[] };
};
};
detail_service: TourDetailService | CustomTripDetail;
transaction_receipt: string | null;
card_number: string | number;
service: "tour" | "custom_trip"; // Added service field
description?: string;
rejected_description?: string;
object_id?: number;
}
interface BillDetailCardProps {
billId: number;
id: number;
}
const formatDate = (dateString: string): string => {
@ -59,13 +78,19 @@ const formatDate = (dateString: string): string => {
});
};
const BillDetailCard: React.FC<BillDetailCardProps> = ({ billId }) => {
const BillDetailCard: React.FC<BillDetailCardProps> = ({ id }) => {
const [bill, setBill] = useState<Bill | null>(null);
const [uploadedFile, setUploadedFile] = useState<string | null>(null);
const [loadingUpload, setLoadingUpload] = useState(false);
const [loading, setLoading] = useState(false);
const { user } = useUserContext();
const { t } = useTranslation("form");
const params = useParams();
const slug = params?.slug;
const billId = slug || id;
console.log(billId, slug, id);
const statusStyles: { [key in BillStatus]: JSX.Element } = {
awaiting_payment: (
@ -89,18 +114,31 @@ const BillDetailCard: React.FC<BillDetailCardProps> = ({ billId }) => {
</span>
),
};
const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
setLoadingUpload(true);
const file = e.target.files?.[0];
if (file) {
const uploadedFile = await getImageURL(file);
setUploadedFile(uploadedFile.url);
try {
const uploadedFile = await getImageURL(file);
setUploadedFile(uploadedFile.url);
} catch (error) {
console.error("Error uploading file:", error);
toast.error(t("uploadFailed"));
} finally {
setLoadingUpload(false);
}
} else {
setLoadingUpload(false);
}
};
const getBillData = async () => {
if (!(Object.keys(user).length)) {
return;
}
try {
const response = await axiosInstance(`/api/factors/${billId}/`, {
const response = await axiosInstance.get(`/api/factors/${billId}/`, {
headers: {
Authorization: `token ${user.token}`,
},
@ -114,7 +152,8 @@ const BillDetailCard: React.FC<BillDetailCardProps> = ({ billId }) => {
useEffect(() => {
getBillData();
}, [billId]);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [billId, user]);
const handleSubmit = () => {
setLoading(true);
@ -135,6 +174,7 @@ const BillDetailCard: React.FC<BillDetailCardProps> = ({ billId }) => {
toast.success(t("transactionReceiptUpdated"));
setLoading(false);
setUploadedFile(null);
getBillData(); // Refresh bill data
})
.catch((error) => {
console.error(error);
@ -142,7 +182,8 @@ const BillDetailCard: React.FC<BillDetailCardProps> = ({ billId }) => {
setLoading(false);
});
} else {
console.log(t("noFileSelected"));
toast.warn(t("noFileSelected"));
setLoading(false);
}
};
@ -164,6 +205,7 @@ const BillDetailCard: React.FC<BillDetailCardProps> = ({ billId }) => {
)
.then(() => {
toast.success(t("transactionReceiptDeleted"));
getBillData(); // Refresh bill data
})
.catch((error) => {
console.error(error);
@ -171,7 +213,7 @@ const BillDetailCard: React.FC<BillDetailCardProps> = ({ billId }) => {
})
.finally(() => {
setLoading(false);
useRouter().reload();
// useRouter().reload(); // Prefer refreshing bill data instead of full reload
});
}
};
@ -180,6 +222,77 @@ const BillDetailCard: React.FC<BillDetailCardProps> = ({ billId }) => {
return <p>{t("loadingBillData")}</p>;
}
// Function to parse custom trip details
const parseCustomTripDetails = (detail: string) => {
try {
const parsedDetail = JSON.parse(detail);
const beginTrip = parsedDetail["1"];
const destinations = bill.detail_service.destinations;
return { beginTrip, destinations };
} catch (error) {
console.error("Error parsing custom trip details:", error);
return { beginTrip: null, destinations: [] };
}
};
const renderPassengerData = () => {
if (bill.service !== "tour") return null;
const detailService = bill.detail_service as TourDetailService;
const passengerData = detailService.passenger_data;
return (
<div className="mt-4">
<h3 className="font-semibold">{t("numberOfPassengers")}</h3>
<div className="flex justify-between">
{passengerData.adults.count > 0 && (
<span>
{passengerData.adults.names.join(", ")} ({t("adult")})
</span>
)}
</div>
<div className="flex justify-between">
{passengerData.children.count > 0 && (
<span>
{passengerData.children.names.join(", ")} ({t("child")})
</span>
)}
</div>
<div className="flex justify-between">
{passengerData.infants.count > 0 && (
<span>
{passengerData.infants.names.join(", ")} ({t("infant")})
</span>
)}
</div>
</div>
);
};
const renderCustomTripDetails = () => {
if (bill.service !== "custom_trip") return null;
const detailService = bill.detail_service as CustomTripDetail;
const { beginTrip, destinations } = parseCustomTripDetails(detailService.detail);
return (
<div className="mt-4">
{beginTrip && (
<div className="mb-2 flex justify-between">
<h3 className="font-semibold">{t("beginTripCity")}:</h3>
<span>{beginTrip.city}</span>
</div>
)}
{destinations && destinations.length > 0 && (
<div className="mb-2">
<h3 className="font-semibold">{t("destinations")}:</h3>
<ul className="list-disc list-inside">
{destinations.map((destination, index) => (
<li key={index}>{destination}</li>
))}
</ul>
</div>
)}
</div>
);
};
return (
<div className="bg-white shadow-md rounded-lg p-4 mb-4 dark:bg-neutral-800">
<div className="flex items-center justify-between">
@ -206,33 +319,9 @@ const BillDetailCard: React.FC<BillDetailCardProps> = ({ billId }) => {
<span>{formatDate(bill.expiration_date)}</span>
</div>
<div className="mt-4">
<h3 className="font-semibold">{t("numberOfPassengers")}</h3>
<div className="flex justify-between">
{!!bill.detail_service.passenger_data.adults.count && (
<span>
{bill.detail_service.passenger_data.adults.names.join(", ")} (
{t("adult")})
</span>
)}
</div>
<div className="flex justify-between">
{!!bill.detail_service.passenger_data.children.count && (
<span>
{bill.detail_service.passenger_data.children.names.join(", ")} (
{t("child")})
</span>
)}
</div>
<div className="flex justify-between">
{!!bill.detail_service.passenger_data.infants.count && (
<span>
{bill.detail_service.passenger_data.infants.names.join(", ")} (
{t("infant")})
</span>
)}
</div>
</div>
{/* Conditional Rendering Based on Service Type */}
{renderPassengerData()}
{renderCustomTripDetails()}
<div className="flex justify-between mt-4 font-semibold">
<span>{t("tourPrice")}:</span>
@ -245,7 +334,15 @@ const BillDetailCard: React.FC<BillDetailCardProps> = ({ billId }) => {
<span className="flex-grow text-gray text-lg">
{bill.card_number}
</span>
<Button className="text-blue-500">{t("copy")}</Button>
<Button
className="text-blue-500"
onClick={() => {
navigator.clipboard.writeText(String(bill.card_number));
toast.success(t("copiedToClipboard"));
}}
>
{t("copy")}
</Button>
</div>
</div>
@ -293,17 +390,18 @@ const BillDetailCard: React.FC<BillDetailCardProps> = ({ billId }) => {
width={200}
height={200}
src={
!!uploadedFile
uploadedFile
? uploadedFile
: bill.transaction_receipt || ""
}
alt={t("currentReceipt")}
className="object-contain"
/>
</span>
) : (
<div className="flex flex-grow flex-col items-center text-gray-500 text-sm mt-14">
<BiCloudUpload size={36} />
<p>{t("uploadPassword")}</p>
<p>{t("uploadPassport")}</p>
</div>
)}
</div>

4
src/app/[locale]/(account-pages)/bills/page.tsx

@ -83,11 +83,11 @@ const BillsPage: React.FC = () => {
<button onClick={handleBackToList} className="text-blue-500 mb-4">
{t("backToBills")}
</button>
<BillDetailCard billId={selectedBill.id} />
<BillDetailCard id={selectedBill.id} />
</div>
) : bills.length > 0 ? (
bills.map((bill, index) => (
<BillCard key={index} bill={bill} onViewDetail={handleViewDetail} />
(bill.service === "custom_trip" || bill.service === "tour") && <BillCard key={index} bill={bill} onViewDetail={handleViewDetail} />
))
) : (
<div className="text-center mt-10 text-gray-500 dark:text-gray-400">

3
src/app/[locale]/(account-pages)/my-trips/page.tsx

@ -8,6 +8,7 @@ import { useRouter } from "next/navigation";
import { useUserContext } from "@/components/contexts/userContext";
import { useTranslation } from "react-i18next";
import { StayDataType } from "@/data/types";
import getLocalizedRoute from "@/utils/routes";
const MyTrips = () => {
const router = useRouter();
@ -17,7 +18,7 @@ const MyTrips = () => {
useEffect(() => {
if (!Object.keys(user).length) {
router.replace("/signup");
router.replace(getLocalizedRoute("/signup"));
}
}, [user, router]);

3
src/app/[locale]/(account-pages)/passengers-list/PassengerTable.tsx

@ -7,6 +7,7 @@ import { useRouter } from "next/navigation";
import { IoMdTrash } from "react-icons/io";
import { MdEdit } from "react-icons/md";
import { useTranslation } from "react-i18next";
import getLocalizedRoute from "@/utils/routes";
interface Passenger {
birthdate: string;
@ -66,7 +67,7 @@ const PassengerTable: FC<TableProps> = ({ data }) => {
</button>
<button
onClick={() => {
router.push(`/passengers-list/${data.id}`);
router.push(getLocalizedRoute(`/passengers-list/${data.id}`));
}}
className="hover:text-blue-500 transition-colors"
>

3
src/app/[locale]/(account-pages)/passengers-list/[id]/page.tsx

@ -10,6 +10,7 @@ import axiosInstance from "@/components/api/axios";
import { useRouter } from "next/navigation";
import { useUserContext } from "@/components/contexts/userContext";
import { toast } from "react-toastify"; // Import toast
import getLocalizedRoute from "@/utils/routes";
export interface CommonLayoutProps {
params: {
@ -167,7 +168,7 @@ const EditPassenger: FC<CommonLayoutProps> = ({ params }) => {
if (response.status === 200) {
toast.success("Passenger details updated successfully!");
router.push("/passengers-list");
router.push(getLocalizedRoute("/passengers-list"));
}
} catch (error) {
toast.error("Error saving passenger details.");

3
src/app/[locale]/(account-pages)/passengers-list/page.tsx

@ -8,6 +8,7 @@ import Link from "next/link";
import { useUserContext } from "@/components/contexts/userContext";
import { useRouter } from "next/navigation";
import { useTranslation } from "react-i18next";
import getLocalizedRoute from "@/utils/routes";
interface data {
@ -28,7 +29,7 @@ const {t} = useTranslation("form")
useEffect(() => {
if (!Object.keys(user).length) {
router.replace("/signup");
router.replace(getLocalizedRoute("/signup"));
}
}, [user, router]);

32
src/app/[locale]/(client-components)/(Header)/LangDropdown.tsx

@ -17,19 +17,19 @@ const languageOptions = [
},
{
id: "ar",
name: "Arabic",
name: "العربية",
description: "Arabic",
icon: MdOutlineLanguage,
},
{
id: "ru",
name: "Russian",
name: "Русский",
description: "Russian",
icon: MdOutlineLanguage,
},
{
id: "id",
name: "Indonesian",
name: "Bahasa Indonesia",
description: "Indonesian",
icon: MdOutlineLanguage,
},
@ -51,6 +51,7 @@ const LangDropdown: React.FC<LangDropdownProps> = ({
const router = useRouter();
const pathname = usePathname();
const [selectedItem, setSelectedItem] = useState(languageOptions[0]);
const [changed , setChanged] = useState(false)
const {t} = useTranslation("navigation");
// Update selectedItem based on the current locale in the URL
useEffect(() => {
@ -61,26 +62,34 @@ const LangDropdown: React.FC<LangDropdownProps> = ({
languageOptions.find((item) => item.id === currentLocale) ||
languageOptions[0];
setSelectedItem(newSelectedItem);
if (changed) {
window.location.reload();
}
}, [pathname]);
const handleLanguageChange = (locale: string) => {
const segments = pathname.split("/").filter(Boolean);
// Remove the current locale if present
const locales = ["en", "ru", "id", "ar"];
if (locales.includes(segments[0])) {
segments.shift();
}
// Prepend the new locale
const newPathname = `/${locale}/`;
const newPathname = `/${locale}`;
// Set the locale cookie
Cookies.set("locale", locale, { expires: 365 });
// Navigate to the new locale path
setChanged(true)
router.push(newPathname);
// Use a slight delay to ensure navigation completes before reloading
};
return (
<Popover className={`LangDropdown relative ${className}`}>
@ -125,7 +134,7 @@ const LangDropdown: React.FC<LangDropdownProps> = ({
)
}
>
{t("text-language")}
{t("text-select-language")}
</Tab>
</Tab.List>
@ -145,7 +154,7 @@ const LangDropdown: React.FC<LangDropdownProps> = ({
handleLanguageChange(item.id);
close(); // Close the popover after selection
}}
className={`flex items-center p-2 -m-3 transition duration-150 ease-in-out rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 focus:outline-none ${
className={`flex items-center p-2 -m-3 h-12 transition duration-150 ease-in-out rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 focus:outline-none ${
selectedItem.id === item.id
? "bg-gray-100 dark:bg-gray-700"
: "opacity-80"
@ -153,9 +162,6 @@ const LangDropdown: React.FC<LangDropdownProps> = ({
>
<div>
<p className="text-sm font-medium">{item.name}</p>
<p className="text-xs text-gray-500 dark:text-gray-400">
{item.description}
</p>
</div>
</button>
))}

3
src/app/[locale]/(client-components)/(HeroSearchForm)/GuestsInput.tsx

@ -11,6 +11,7 @@ import { UserPlusIcon } from "@heroicons/react/24/outline";
import { GuestsObject } from "../type";
import { Context, useToursContext } from "@/components/contexts/tourDetails";
import { useTranslation } from "react-i18next";
import getLocalizedRoute from "@/utils/routes";
export interface GuestsInputProps {
fieldClassName?: string;
@ -93,7 +94,7 @@ const GuestsInput: FC<GuestsInputProps> = ({
<div className="pr-2 xl:pr-4">
<ButtonSubmit
className={`${!details && "opacity-40 pointer-events-none"}`}
href={`/tours/${details?.slug}-${details?.id}`}
href={`${getLocalizedRoute("tours")}/${details?.slug}-${details?.id}`}
/>
</div>
)}

3
src/app/[locale]/(client-components)/(HeroSearchForm2Mobile)/LocationInput.tsx

@ -1,6 +1,7 @@
"use client";
import { useToursContext } from "@/components/contexts/tourDetails";
import getLocalizedRoute from "@/utils/routes";
import { MapPinIcon, MagnifyingGlassIcon } from "@heroicons/react/24/outline";
import { useRouter } from "next/navigation";
import React, { useState, useEffect, useRef, FC } from "react";
@ -55,7 +56,7 @@ const LocationInput: FC<Props> = ({
return (
<div
className="cursor-pointer py-2 mb-1 flex items-center space-x-3 text-sm"
onClick={() => router.push(`/tours/${item?.slug}-${item?.id}`)}
onClick={() => router.push(getLocalizedRoute(`/tours/${item?.slug}-${item?.id}`))}
key={item.id}
>
<MapPinIcon className="w-5 h-5 text-neutral-500 dark:text-neutral-400" />

3
src/app/[locale]/(server-components)/SectionHero.tsx

@ -7,6 +7,7 @@ import HeroSearchForm from "../(client-components)/(HeroSearchForm)/HeroSearchFo
import Image from "next/image";
import ButtonPrimary from "@/shared/ButtonPrimary";
import { useTranslation } from "react-i18next"; // Import useTranslation
import getLocalizedRoute from "@/utils/routes";
export interface SectionHeroProps {
className?: string;
@ -43,7 +44,7 @@ const SectionHero: FC<SectionHeroProps> = ({ className = "" }) => {
<span className="text-base md:text-lg text-neutral-500 dark:text-neutral-400">
{t("planPilgrimage")}
</span>
<ButtonPrimary href="/tours" sizeClass="px-5 py-4 sm:px-7">
<ButtonPrimary href={getLocalizedRoute("tours")} sizeClass="px-5 py-4 sm:px-7">
{t("startJourney")}
</ButtonPrimary>
</div>

5
src/app/[locale]/about/SectionHero.tsx

@ -1,6 +1,7 @@
import Image, { StaticImageData } from "next/image";
import React, { FC, ReactNode } from "react";
import ButtonPrimary from "@/shared/ButtonPrimary";
import getLocalizedRoute from "@/utils/routes";
export interface SectionHeroProps {
className?: string;
@ -27,10 +28,10 @@ const SectionHero: FC<SectionHeroProps> = ({
<span className="block text-base xl:text-lg text-neutral-6000 dark:text-neutral-400">
{subHeading}
</span>
{!!btnText && <ButtonPrimary href="/login">{btnText}</ButtonPrimary>}
{!!btnText && <ButtonPrimary href={getLocalizedRoute("login")}>{btnText}</ButtonPrimary>}
</div>
<div className="flex-grow">
<Image className="w-full rtl:mr-10" src={rightImg} alt="" />
<Image className="w-full rtl:mr-10 rounded-3xl" src={rightImg} alt="" />
</div>
</div>
</div>

4
src/app/[locale]/about/page.tsx

@ -1,5 +1,5 @@
"use client"
import rightImg from "@/images/about-hero-right.png";
import rightImg from "@/images/about-hero-right.jpg";
import React, { FC } from "react";
import SectionFounder from "./SectionFounder";
import SectionStatistic from "./SectionStatistic";
@ -34,7 +34,7 @@ const PageAbout: FC<PageAboutProps> = () => {
<SectionClientSay />
</div>
<SectionStatistic />
{/* <SectionStatistic /> */}
{/* <SectionSubscribe2 /> */}
</div>

3
src/app/[locale]/add-listing/[[...stepIndex]]/PageAddListing1.tsx

@ -8,6 +8,7 @@ import ConfirmModal from "@/shared/popUp";
import axiosInstance from "@/components/api/axios";
import { useUserContext } from "@/components/contexts/userContext";
import { useRouter } from "next/navigation";
import getLocalizedRoute from "@/utils/routes";
export interface PageAddListing1Props {
ageLabel : string ;
@ -39,7 +40,7 @@ const PageAddListing1: FC<PageAddListing1Props> = ({
const [passengerAgeLable , setPassengerAgeLAble] = useState(ageLabel)
if (!Object.keys(user).length) {
router.replace("/signup");
router.replace(getLocalizedRoute("/signup"));
}
useEffect(() => {

2
src/app/[locale]/add-listing/[[...stepIndex]]/PageAddListing10.tsx

@ -29,7 +29,7 @@ const PageAddListing10: FC<PageAddListing10Props> = () => {
/>
</div>
<div className="flex items-center space-x-5 mt-8">
<ButtonSecondary href={"/add-listing/1" as Route}>
<ButtonSecondary href={"/add-listing/1"}>
<PencilSquareIcon className="h-5 w-5" />
<span className="ml-3">Edit</span>
</ButtonSecondary>

3
src/app/[locale]/add-listing/[[...stepIndex]]/page.tsx

@ -10,6 +10,7 @@ import axiosInstance from "@/components/api/axios";
import { useUserContext } from "@/components/contexts/userContext";
import { toast } from "react-toastify";
import ButtonSecondary from "@/shared/ButtonSecondary";
import getLocalizedRoute from "@/utils/routes";
export interface CommonLayoutProps {
children: React.ReactNode;
@ -95,7 +96,7 @@ const CommonLayout: FC<CommonLayoutProps> = ({ params }) => {
useEffect(() => {
if (redirecting) {
router.replace("/my-trips");
router.replace(getLocalizedRoute("/my-trips"));
toast.success("Purchased Successfully ");
}
}, [redirecting, router]);

3
src/app/[locale]/add-new-passenger/page.tsx

@ -10,6 +10,7 @@ import axiosInstance from "@/components/api/axios";
import { useRouter } from "next/navigation";
import { useUserContext } from "@/components/contexts/userContext";
import { PhoneNumberUtil } from "google-libphonenumber";
import getLocalizedRoute from "@/utils/routes";
export interface CommonLayoutProps {
params: {
@ -132,7 +133,7 @@ const CommonLayout: FC<CommonLayoutProps> = () => {
}
);
router.push("/passengers-list");
router.push(getLocalizedRoute("/passengers-list"));
} catch (error: any) {
console.error("Error saving passenger:", error);
setErrors((prevErrors) => ({ ...prevErrors, request: error.message }));

5
src/app/[locale]/blog/Card12.tsx

@ -6,6 +6,7 @@ import SocialsShare from "@/shared/SocialsShare";
import { DEMO_POSTS } from "@/data/posts";
import Link from "next/link";
import Image from "next/image";
import getLocalizedRoute from "@/utils/routes";
export interface Card12Props {
className?: string;
@ -21,7 +22,7 @@ const Card12: FC<Card12Props> = ({
return (
<div className={`nc-Card12 group relative flex flex-col ${className}`}>
<Link
href={`blog/${post?.slug}`}
href={`${getLocalizedRoute("blog")}/${post?.slug}`}
className="block flex-shrink-0 flex-grow relative w-full h-0 aspect-w-4 aspect-h-3 rounded-3xl overflow-hidden"
>
<Image
@ -46,7 +47,7 @@ const Card12: FC<Card12Props> = ({
<h2
className={`nc-card-title block font-semibold text-neutral-900 dark:text-neutral-100 transition-colors text-lg sm:text-2xl`}
>
<Link href={`blog/${post?.slug}`} className="line-clamp-2" title={post?.title}>
<Link href={`${getLocalizedRoute("blog")}/${post?.slug}`} className="line-clamp-2" title={post?.title}>
{post?.title}
</Link>
</h2>

5
src/app/[locale]/blog/Card13.tsx

@ -4,6 +4,7 @@ import { PostDataType } from "@/data/types";
import PostTypeFeaturedIcon from "@/components/PostTypeFeaturedIcon";
import Link from "next/link";
import Image from "next/image";
import getLocalizedRoute from "@/utils/routes";
export interface Card13Props {
className?: string;
@ -17,7 +18,7 @@ const Card13: FC<Card13Props> = ({ className = "", post }) => {
<div className={`nc-Card13 relative flex justify-between ${className}`} data-nc-id="Card13">
<div className="flex flex-col h-full py-2">
<h2 className={`nc-card-title block font-semibold text-base`}>
<Link href={`blog/${post?.slug}`} className="line-clamp-2" title={post?.title}>
<Link href={`${getLocalizedRoute("blog")}/${post?.slug}`} className="line-clamp-2" title={post?.title}>
{post?.title}
</Link>
</h2>
@ -33,7 +34,7 @@ const Card13: FC<Card13Props> = ({ className = "", post }) => {
</div>
<Link
href={`blog/${post?.slug}`}
href={`${getLocalizedRoute("blog")}/${post?.slug}`}
className={`block relative h-full flex-shrink-0 w-2/5 sm:w-1/3 ml-3 sm:ml-5`}
>
<Image

42
src/app/[locale]/blog/[...slug]/Survey.tsx

@ -1,42 +0,0 @@
"use client";
import { useState } from "react";
import { BiDislike, BiLike } from "react-icons/bi";
function Survey() {
const [checked, setChecked] = useState(false);
return (
<div className="max-w-screen-md mx-auto flex items-center gap-6 ">
{!checked ? (
<>
<h1 className="mb-2">Was this article helpfull ?</h1>
<div className="flex">
<div
onClick={() => {
setChecked(true);
}}
className="cursor-pointer hover:bg-green-200 nc-Tag flex items-center gap-1 bg-white text-sm text-neutral-600 dark:text-neutral-300 py-2 rounded-lg border border-neutral-100 md:px-4 dark:bg-neutral-700 dark:border-neutral-700 hover:border-neutral-200 dark:hover:border-neutral-6000 mr-2 mb-2"
>
<p>Yes</p>
<BiLike />
</div>
<div
onClick={() => {
setChecked(true);
}}
className="cursor-pointer hover:bg-red-300 nc-Tag flex items-center gap-1 bg-white text-sm text-neutral-600 dark:text-neutral-300 py-2 rounded-lg border border-neutral-100 md:px-4 dark:bg-neutral-700 dark:border-neutral-700 hover:border-neutral-200 dark:hover:border-neutral-6000 mr-2 mb-2"
>
<p>No</p>
<BiDislike />
</div>
</div>
</>
) : (
<h1>Thank you</h1>
)}
</div>
);
}
export default Survey;

26
src/app/[locale]/blog/[...slug]/page.tsx

@ -18,6 +18,7 @@ import { BiLike, BiDislike } from "react-icons/bi";
import Survey from "./Survey";
import axiosInstance from "@/components/api/axios";
import { useTranslation } from "react-i18next"; // Import useTranslation
import getLocalizedRoute from "@/utils/routes";
const Page = ({
params,
@ -47,7 +48,7 @@ const Page = ({
return (
<header className="container rounded-xl">
<div className="max-w-screen-md mx-auto space-y-5">
<Badge href="/blog" color="purple" name={t("traveler")} /> {/* Translate badge name */}
<Badge href={getLocalizedRoute("/blog")} color="purple" name={t("traveler")} /> {/* Translate badge name */}
<h1
className="text-neutral-900 font-semibold text-3xl md:text-4xl md:!leading-[120%] lg:text-4xl dark:text-neutral-100 max-w-4xl"
title={blog?.title}
@ -76,17 +77,21 @@ const Page = ({
const renderTags = () => {
return (
<div className="max-w-screen-md mx-auto flex flex-wrap">
<div className="max-w-screen-md mx-auto">
<h3 className="mb-4">
{t("tags")} :
</h3>
<div className=" flex flex-wrap">
{blog?.tags.map((item) => (
<a
key={item.id}
className="nc-Tag inline-block bg-white text-sm text-neutral-600 dark:text-neutral-300 py-2 rounded-lg border border-neutral-100 md:px-4 dark:bg-neutral-700 dark:border-neutral-700 hover:border-neutral-200 dark:hover:border-neutral-6000 mr-2 mb-2"
href={item.slug}
<div
key={item.id}
className="nc-Tag inline-block bg-white text-sm text-neutral-600 dark:text-neutral-300 py-2 rounded-lg border border-neutral-100 md:px-4 dark:bg-neutral-700 dark:border-neutral-700 hover:border-neutral-200 dark:hover:border-neutral-6000 mr-2 mb-2"
>
{item.name}
</a>
</div>
))}
</div>
</div>
);
};
@ -138,7 +143,7 @@ const Page = ({
key={post.id}
className="relative aspect-w-3 aspect-h-4 rounded-3xl overflow-hidden group"
>
<Link href={`blog/${post.slug}`} />
<Link href={getLocalizedRoute(`blog/${post.slug}`)} />
<Image
className="object-cover transform group-hover:scale-105 transition-transform duration-300"
src={post.cover}
@ -159,7 +164,7 @@ const Page = ({
</span>
</div>
</div>
<Link href={`blog/${post.slug}`} />
<Link href={getLocalizedRoute(`blog/${post.slug}`)} />
</div>
);
};
@ -172,7 +177,7 @@ const Page = ({
{renderContent()}
{renderTags()}
<div className="max-w-screen-md mx-auto border-b border-t border-neutral-100 dark:border-neutral-700"></div>
<Survey />
{/* <Survey /> */}
{/* Uncomment to render comments */}
{/* {renderCommentForm()} */}
{/* {renderCommentLists()} */}
@ -187,6 +192,7 @@ const Page = ({
})
.map((item, i) => i < 4 && renderPostRelated(item))}
</div>
{/* <SectionLatestPosts/> */}
</div>
</div>
</div>

3
src/app/[locale]/custom-history/page.tsx

@ -6,6 +6,7 @@ import { TrashIcon } from '@heroicons/react/24/outline'; // Import the Trash ico
import { FaWhatsapp } from "react-icons/fa";
import { useUserContext } from '@/components/contexts/userContext';
import { useRouter } from 'next/navigation';
import getLocalizedRoute from '@/utils/routes';
export interface PageAddListing10Props {}
@ -35,7 +36,7 @@ const PageAddListing10: FC<PageAddListing10Props> = () => {
console.error('Error fetching custom trip orders:', error);
});
} else {
router.replace("/signup");
router.replace(getLocalizedRoute("/signup"));
}
}, [user, router]);

6
src/app/[locale]/custom-trip/page.tsx

@ -11,6 +11,7 @@ import { useUserContext } from "@/components/contexts/userContext";
import { toast } from "react-toastify";
import NcInputNumber from "@/components/NcInputNumber";
import { useTranslation } from "react-i18next";
import getLocalizedRoute from "@/utils/routes";
interface City {
name: string;
@ -304,10 +305,10 @@ const CommonLayout: FC<CommonLayoutProps> = () => {
},
}).then((response) => {
console.log(response);
toast.success(t("successMessage"));
router.push(getLocalizedRoute(`/bills/${response.data.factor_id}`));
})
toast.success(t("successMessage"));
router.push("/custom-history");
} catch (error) {
console.error("Error sending trip details:", error);
}
@ -359,6 +360,7 @@ const CommonLayout: FC<CommonLayoutProps> = () => {
defaultValue={passengers.adults}
onChange={(value) => handleChangeData(value, "adults")}
max={20}
min={1}
label={t("adults")}
desc={t("adultsDesc")}
/>

2
src/app/[locale]/faq/Table.tsx

@ -19,7 +19,7 @@ const Table: React.FC<TableProps> = ({ faq, isActive, onClick }) => {
className="text-xl font-semibold flex justify-between items-center cursor-pointer"
onClick={onClick}
>
{faq.question}
{faq.translation}
<span >{isActive ? "-" : "+"}</span>
</h3>
<motion.div

96
src/app/[locale]/faq/page.tsx

@ -1,8 +1,9 @@
"use client";
import React, { useState } from "react";
import React, { useEffect, useState } from "react";
import Table from "./Table";
import { useTranslation } from "react-i18next"; // Import useTranslation
import axiosInstance from "@/components/api/axios";
interface FAQItem {
question: string;
@ -11,50 +12,51 @@ interface FAQItem {
const FAQ: React.FC = () => {
const { t } = useTranslation("FAQ"); // Initialize useTranslation with the "common" namespace
const [faqs , setFaqs] = useState([])
const [activeIndex, setActiveIndex] = useState<number | null>(null);
const faqs: FAQItem[] = [
{
question: t("faqQuestion1"), // Translate the question
answer: t("faqAnswer1"), // Translate the answer
},
{
question: t("faqQuestion2"),
answer: t("faqAnswer2"),
},
{
question: t("faqQuestion3"),
answer: t("faqAnswer3"),
},
{
question: t("faqQuestion4"),
answer: t("faqAnswer4"),
},
{
question: t("faqQuestion5"),
answer: t("faqAnswer5"),
},
{
question: t("faqQuestion6"),
answer: t("faqAnswer6"),
},
{
question: t("faqQuestion7"),
answer: t("faqAnswer7"),
},
{
question: t("faqQuestion8"),
answer: t("faqAnswer8"),
},
{
question: t("faqQuestion9"),
answer: t("faqAnswer9"),
},
{
question: t("faqQuestion10"),
answer: t("faqAnswer10"),
},
];
// const faqs: FAQItem[] = [
// {
// question: t("faqQuestion1"), // Translate the question
// answer: t("faqAnswer1"), // Translate the answer
// },
// {
// question: t("faqQuestion2"),
// answer: t("faqAnswer2"),
// },
// {
// question: t("faqQuestion3"),
// answer: t("faqAnswer3"),
// },
// {
// question: t("faqQuestion4"),
// answer: t("faqAnswer4"),
// },
// {
// question: t("faqQuestion5"),
// answer: t("faqAnswer5"),
// },
// {
// question: t("faqQuestion6"),
// answer: t("faqAnswer6"),
// },
// {
// question: t("faqQuestion7"),
// answer: t("faqAnswer7"),
// },
// {
// question: t("faqQuestion8"),
// answer: t("faqAnswer8"),
// },
// {
// question: t("faqQuestion9"),
// answer: t("faqAnswer9"),
// },
// {
// question: t("faqQuestion10"),
// answer: t("faqAnswer10"),
// },
// ];
const toggleFAQ = (index: number) => {
if (activeIndex === index) {
@ -64,6 +66,14 @@ const FAQ: React.FC = () => {
}
};
useEffect(()=>{
axiosInstance.get("api/home/faq/").then((res)=>{
console.log(res);
setFaqs(res.data.results)
})
} , [])
return (
<div className="container mx-auto py-12 max-w-6xl">
<h1 className="text-center text-3xl mb-5 font-bold">{t("faqTitle")}</h1> {/* Translate title */}

5
src/app/[locale]/forgot-password/page.tsx

@ -12,6 +12,7 @@ import "react-toastify/dist/ReactToastify.css";
import { PhoneNumberUtil } from "google-libphonenumber"; // Import libphonenumber
import { BiShow, BiHide } from "react-icons/bi";
import { useTranslation } from "react-i18next";
import getLocalizedRoute from "@/utils/routes";
export interface PageSignUpProps {}
@ -119,7 +120,7 @@ const PageSignUp: FC<PageSignUpProps> = () => {
};
setForm(formData);
router.push("/signup/methodes");
router.push(getLocalizedRoute("/signup/methodes"));
} catch (error: any) {
console.error("Error fetching data:", error);
if (error.response?.data?.errors?.length) {
@ -254,7 +255,7 @@ const PageSignUp: FC<PageSignUpProps> = () => {
{/* Link to Sign In */}
<p className="text-center text-sm text-neutral-500 mt-4">
{t("alreadyHaveAccount")}{" "}
<Link href="/signin" className="text-primary-600 hover:underline">
<Link href={getLocalizedRoute("/signin") }className="text-primary-600 hover:underline">
{t("signIn")}
</Link>
</p>

7
src/app/[locale]/login/page.tsx

@ -12,6 +12,7 @@ import "react-toastify/dist/ReactToastify.css";
import { PhoneNumberUtil } from "google-libphonenumber";
import { BiShow, BiHide } from "react-icons/bi";
import { useTranslation } from "react-i18next";
import getLocalizedRoute from "@/utils/routes";
export interface PageLoginProps {}
@ -31,7 +32,7 @@ const PageLogin: FC<PageLoginProps> = () => {
// Redirect to home if the user is already logged in
useEffect(() => {
if (Object.keys(user).length) {
router.replace("/");
router.replace(getLocalizedRoute("/"));
}
}, [user, router]);
@ -161,7 +162,7 @@ const PageLogin: FC<PageLoginProps> = () => {
<label className="block relative">
<span className="flex justify-between items-center text-neutral-800 dark:text-neutral-200">
{t("password")}
<Link href="/forgot-password" className="text-sm underline font-medium">
<Link href={getLocalizedRoute("/forgot-password")} className="text-sm underline font-medium">
{t("forgotPassword")}
</Link>
</span>
@ -195,7 +196,7 @@ const PageLogin: FC<PageLoginProps> = () => {
</form>
<span className="block text-center text-neutral-700 dark:text-neutral-300">
<Link href="/signup" className="text-bronze font-semibold">
<Link href={getLocalizedRoute("/signup")} className="text-bronze font-semibold">
{t("createAccount")}
</Link>
</span>

3
src/app/[locale]/page.tsx

@ -18,14 +18,13 @@ import axiosInstance from "@/components/api/axios";
import TourSuggestion from "@/components/TourSuggestion";
import SectionDowloadApp from "./(home)/SectionDowloadApp";
import "react-toastify/dist/ReactToastify.css";
import localizedRoutes from "@/utils/routes";
function PageHome() {
return (
<main className="nc-PageHome relative overflow-hidden">
{/* GLASSMOPHIN */}

5
src/app/[locale]/signup/methodes/page.tsx

@ -8,6 +8,7 @@ import { useEffect, useState, ChangeEvent } from "react";
import { FaWhatsapp } from "react-icons/fa";
import { MdOutlineTextsms } from "react-icons/md";
import { useTranslation } from "react-i18next";
import getLocalizedRoute from "@/utils/routes";
interface Form {
phoneNumber: string;
@ -78,7 +79,7 @@ function SelectMethods() {
if (response.status === 202) {
setForm((prev: Record<string, any>) => ({ ...prev, verification_method: selectedMethod }));
setLoading(false);
router.replace("/signup/otp-code");
router.replace(getLocalizedRoute("/signup/otp-code"));
}
}
// Handle Password Reset
@ -93,7 +94,7 @@ function SelectMethods() {
if (response.status === 202) {
setForm((prev: Record<string, any>) => ({ ...prev, verification_method: selectedMethod }));
setLoading(false);
router.replace("/signup/otp-code");
router.replace(getLocalizedRoute("/signup/otp-code"));
}
}
} catch (error: any) {

5
src/app/[locale]/signup/otp-code/page.tsx

@ -8,6 +8,7 @@ import { useRouter } from "next/navigation";
import { toast, ToastContainer } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";
import { useTranslation } from "react-i18next";
import getLocalizedRoute from "@/utils/routes";
export interface PageSignUpProps {}
@ -96,7 +97,7 @@ const PageSignUp: FC<PageSignUpProps> = () => {
verification_method: form.verification_method,
}));
setLoading(false);
router.replace("/signup/otp-code");
router.replace(getLocalizedRoute("/signup/otp-code"));
}
} else if (form.method === "reset") {
payload.password = form.password;
@ -112,7 +113,7 @@ const PageSignUp: FC<PageSignUpProps> = () => {
if (response.status === 202) {
setLoading(false);
router.replace("/signup/otp-code");
router.replace(getLocalizedRoute("/signup/otp-code"));
}
}
} catch (error: any) {

7
src/app/[locale]/signup/page.tsx

@ -12,6 +12,7 @@ import { ToastContainer, toast } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";
import { BiShow, BiHide } from "react-icons/bi";
import { useTranslation } from "react-i18next";
import getLocalizedRoute from "@/utils/routes";
export interface PageSignUpProps {}
@ -40,7 +41,7 @@ const PageSignUp: FC<PageSignUpProps> = () => {
// Redirect to home if the user is already logged in
useEffect(() => {
if (Object.keys(user).length) {
router.replace("/");
router.replace(getLocalizedRoute("/"));
}
}, [user, router]);
@ -86,7 +87,7 @@ const PageSignUp: FC<PageSignUpProps> = () => {
method: "register",
}
);
router.replace("/signup/otp-code");
router.replace(getLocalizedRoute("/signup/otp-code"));
} catch (error: any) {
if (error.response?.data?.errors?.length) {
error.response?.data?.errors?.map((err)=>{
@ -232,7 +233,7 @@ const PageSignUp: FC<PageSignUpProps> = () => {
{/* Signup Link */}
<span className="not-italic block text-center text-neutral-700 dark:text-neutral-300">
{t("alreadyHaveAccount")}{" "}
<Link href="/login" className="text-primary-600 font-semibold underline">
<Link href={getLocalizedRoute("/login")} className="text-primary-600 font-semibold underline">
{t("signIn")}
</Link>
</span>

5
src/app/[locale]/tours/Card.tsx

@ -8,6 +8,7 @@ import Badge from "@/shared/Badge";
import Link from "next/link";
import Image from "next/image";
import calender from "../../../images/Group.svg";
import getLocalizedRoute from "@/utils/routes";
export interface StayCard2Props {
className?: string;
@ -61,7 +62,7 @@ const StayCard2: FC<StayCard2Props> = ({
return (
<>
<Link
href={`/tours/${data?.slug}-${data?.id}`}
href={getLocalizedRoute(`/tours/${data?.slug}-${data?.id}`)}
className="relative w-full"
>
<div className="h-40 lg:h-56 sm:h-40 overflow-hidden rounded-xl">
@ -128,7 +129,7 @@ const StayCard2: FC<StayCard2Props> = ({
};
return (
<div className={`nc-StayCard2 group relative ${className}`}>
<div className={getLocalizedRoute(`nc-StayCard2 group relative ${className}`)}>
{renderSliderGallery()}
<Link href={`/tours/${data?.slug}-${data?.id}`}>{renderContent()}</Link>
</div>

18
src/app/[locale]/tours/SectionGridFilterCard.tsx

@ -37,14 +37,17 @@ const SectionGridFilterCard: FC<SectionGridFilterCardProps> = ({
useEffect(() => {
const country = searchParams.get("country");
if (searchParams.has("country") && country) {
setChecked({
[country]: true,
});
}
if (!country) return;
setChecked((prevChecked) => {
if (prevChecked[country]) {
return prevChecked; // No change, no re-render
}
return { ...prevChecked, [country]: true };
});
}, [searchParams]);
console.log(checked);
useEffect(() => {
if (!tours.results) return;
@ -65,7 +68,8 @@ const SectionGridFilterCard: FC<SectionGridFilterCardProps> = ({
setCountryTours(filteredTours);
}
}, [checked, countries, tours.results, filteredCountries]);
}, [checked, countries, tours.results]);
return (
<div

2
src/app/[locale]/tours/TabFilters.tsx

@ -116,7 +116,7 @@ const TabFilters = ({ data, onChangeCountry = (item) => {} }) => {
open ? "!border-primary-500 " : ""
}`}
>
<span>{t("Type of place")}</span>
<span>{t("tourSuggestion.heading")}</span>
<i className="las la-angle-down ml-2"></i>
</Popover.Button>
<Transition

3
src/app/[locale]/tours/[slug]/page.tsx

@ -15,6 +15,7 @@ import StartRating from "@/components/StartRating";
import { useTranslation } from "react-i18next"; // Import useTranslation
import Head from "next/head";
import Badge from "@/shared/Badge";
import getLocalizedRoute from "@/utils/routes";
// Define the type of `details`
interface TourDetails {
@ -189,7 +190,7 @@ const ListingStayDetailPage: FC = () => {
? ""
: "opacity-60 pointer-events-none"
}
href={`/add-listing/${id}`}
href={getLocalizedRoute(`/add-listing/${id}`)}
>
{t("reserve")} {/* Translate reserve button text */}
</ButtonPrimary>

3
src/components/Footer.tsx

@ -7,6 +7,7 @@ import SocialsList1 from "@/shared/SocialsList1";
import { CustomLink } from "@/data/types";
import FooterNav from "./FooterNav";
import Image from "next/image";
import getLocalizedRoute from "@/utils/routes";
export interface WidgetFooterMenu {
id: string;
@ -41,7 +42,7 @@ const Footer: React.FC = () => {
<li key={idx}>
<a
className="text-neutral-6000 dark:text-neutral-300 hover:text-black dark:hover:text-white"
href={item.href}
href={getLocalizedRoute(item.href)}
>
{item.label}
</a>

3
src/components/FooterNav.tsx

@ -13,6 +13,7 @@ import MenuBar from "@/shared/MenuBar";
import isInViewport from "@/utils/isInViewport";
import Link from "next/link";
import { usePathname } from "next/navigation";
import getLocalizedRoute from "@/utils/routes";
let WIN_PREV_POSITION = 0;
if (typeof window !== "undefined") {
@ -102,7 +103,7 @@ const FooterNav = () => {
return item.link ? (
<Link
key={index}
href={item.link}
href={getLocalizedRoute(item.link)}
className={`flex flex-col items-center justify-between text-neutral-500 dark:text-neutral-300/90 ${
isActive ? "text-neutral-900 dark:text-neutral-100" : ""
}`}

3
src/components/MobileFooterSticky.tsx

@ -6,6 +6,7 @@ import ModalReserveMobile from "./ModalReserveMobile";
import { useParams } from "next/navigation";
import { useToursContext } from "@/components/contexts/tourDetails";
import GuestsInput from "@/app/[locale]/tours/[slug]/GuestsInput";
import getLocalizedRoute from "@/utils/routes";
const MobileFooterSticky = () => {
const [startDate, setStartDate] = useState<Date | null>(
@ -60,7 +61,7 @@ const MobileFooterSticky = () => {
? ""
: "opacity-60 pointer-events-none"
}`}
href={`/add-listing/${id}`}
href={getLocalizedRoute(`/add-listing/${id}`)}
>
Reserve
</ButtonPrimary>

3
src/components/SearchCard.tsx

@ -4,6 +4,7 @@ import Image from "next/image";
import axiosInstance from "./api/axios";
import calender from "./../images/Group.svg";
import Link from "next/link";
import getLocalizedRoute from "@/utils/routes";
function SearchCard({ data }: { data: any }) {
const { details } = useToursContext();
@ -44,7 +45,7 @@ function SearchCard({ data }: { data: any }) {
const tripDuration = calculateDuration(data.started_at, data.ended_at);
return (
<Link href={`/tours/${data?.slug}-${data?.id}`} className="flex hover:bg-neutral-100 p-4 cursor-pointer rounded-3xl dark:hover:bg-neutral-800">
<Link href={getLocalizedRoute(`/tours/${data?.slug}-${data?.id}`)} className="flex hover:bg-neutral-100 p-4 cursor-pointer rounded-3xl dark:hover:bg-neutral-800">
<div className="h-30 inset-0 w-30 h-30 overflow-hidden rounded-xl relative">
<Image
className="max-h-16 object-cover"

3
src/components/SectionCustomTour.tsx

@ -6,6 +6,7 @@ import BackgroundImage from "@/images/Frame-412.webp";
import Image from "next/image";
import ButtonPrimary from "@/shared/ButtonPrimary";
import { useTranslation } from "react-i18next";
import getLocalizedRoute from "@/utils/routes";
const SectionDownloadApp = () => {
const { t } = useTranslation("common");
@ -38,7 +39,7 @@ const SectionDownloadApp = () => {
<p className="text-white text-lg mt-4 opacity-80">
{t("createPersonalizedTourLine2")}
</p>
<ButtonPrimary href="/custom-trip" className="mt-8 px-8 py-3 text-lg">
<ButtonPrimary href={getLocalizedRoute("/custom-trip")} className="mt-8 px-8 py-3 text-lg">
{t("customTour")}
</ButtonPrimary>
</div>

3
src/components/SectionGridFeaturePlaces.tsx

@ -14,6 +14,7 @@ import PrevBtn from "./PrevBtn";
import NextBtn from "./NextBtn";
import { useRouter } from "next/navigation";
import { useTranslation } from "react-i18next";
import getLocalizedRoute from "@/utils/routes";
export interface SectionGridFeaturePlacesProps {
stayListings?: StayDataType[];
@ -158,7 +159,7 @@ const SectionGridFeaturePlaces: FC<SectionGridFeaturePlacesProps> = ({
</MotionConfig>
<div className="flex mt-16">
<ButtonPrimary onClick={() => router.push("/tours")}>
<ButtonPrimary onClick={() => router.push(getLocalizedRoute("/tours"))}>
{t("showMore")}
</ButtonPrimary>
</div>

5
src/components/UserMenu.tsx

@ -6,6 +6,7 @@ import Avatar from "@/shared/Avatar";
import Link from "next/link";
import ButtonPrimary from "@/shared/ButtonPrimary";
import { useTranslation } from "react-i18next"; // Import useTranslation
import getLocalizedRoute from "@/utils/routes";
export interface UserMenuProps {
className?: string;
@ -27,10 +28,10 @@ const UserMenu: FC<UserMenuProps> = ({ className = "" }) => {
</Link>
) : (
<div className="flex items-center space-x-4 ">
<Link className="text-md" href="/login">
<Link className="text-md" href={getLocalizedRoute("/login")}>
{translation.logIn}
</Link>
<ButtonPrimary className="self-center" href="/signup">
<ButtonPrimary className="self-center" href={getLocalizedRoute("/signup")}>
{translation.signUp}
</ButtonPrimary>
</div>

3
src/components/contexts/userContext.tsx

@ -1,5 +1,6 @@
"use client";
import getLocalizedRoute from "@/utils/routes";
import { useRouter } from "next/navigation";
import React, { createContext, useContext, useState, ReactNode, useEffect } from "react";
@ -48,7 +49,7 @@ export const UserProvider: React.FC<UserProviderProps> = ({ children }) => {
const clerUser = () => {
setUser({});
localStorage.removeItem("user");
router.replace("/")
router.replace(getLocalizedRoute("/"))
};

5
src/components/listing-image-gallery/ListingImageGallery.tsx

@ -11,6 +11,7 @@ import { ArrowSmallLeftIcon } from "@heroicons/react/24/outline";
import { Dialog, Transition } from "@headlessui/react";
import LikeSaveBtns from "../LikeSaveBtns";
import { Route } from "next";
import getLocalizedRoute from "@/utils/routes";
const PHOTOS: string[] = [
"https://images.pexels.com/photos/6129967/pexels-photo-6129967.jpeg?auto=compress&cs=tinysrgb&dpr=3&h=750&w=1260",
@ -85,7 +86,7 @@ const ListingImageGallery: FC<Props> = ({
setLastViewedPhoto(photoId);
let params = new URLSearchParams(document.location.search);
params.delete("photoId");
router.push(`${thisPathname}/?${params.toString()}` as Route);
router.push(getLocalizedRoute(`${thisPathname}/?${params.toString()}`) as Route);
}}
/>
)}
@ -96,7 +97,7 @@ const ListingImageGallery: FC<Props> = ({
key={id}
onClick={() => {
const newPathname = getNewParam({ value: id });
router.push(`${thisPathname}/?${newPathname}` as Route);
router.push(getLocalizedRoute(`${thisPathname}/?${newPathname}`) as Route);
}}
ref={id === Number(lastViewedPhoto) ? lastViewedPhotoRef : null}
className="after:content group relative mb-5 block w-full cursor-zoom-in after:pointer-events-none after:absolute after:inset-0 after:rounded-lg after:shadow-highlight focus:outline-none"

8
src/data/navigation.ts

@ -172,23 +172,23 @@ export const NAVIGATION_DEMO: NavItemType[] = [
},
{
id: ncNanoId(),
href: "/tours",
href: "tours",
name: "text-all-tours",
type: "dropdown",
},
{
id: ncNanoId(),
href: "/blog",
href: "blog",
name: "text-blog",
},
{
id: ncNanoId(),
href: "/faq",
href: "faq",
name: "text-faq",
},
{
id: ncNanoId(),
href: "/about",
href: "about",
name: "text-about",
},
// {

BIN
src/images/about-hero-right.jpg

After

Width: 830  |  Height: 646  |  Size: 79 KiB

3
src/shared/Logo.tsx

@ -4,6 +4,7 @@ import logoLightImg from "@/images/logos/Frame 3.svg";
import logoDarkImg from "@/images/logos/Frame 3 Dark.svg";
import Link from "next/link";
import Image, { StaticImageData } from "next/image";
import getLocalizedRoute from "@/utils/routes";
export interface LogoProps {
img?: StaticImageData;
@ -18,7 +19,7 @@ const Logo: React.FC<LogoProps> = ({
}) => {
return (
<Link
href="/"
href={getLocalizedRoute("/")}
className={`ttnc-logo flex text-primary-6000 focus:outline-none focus:ring-0 mr-10 rtl:mr-0 rtl:ml-10 ${className}`}
>
{/* <LogoSvgLight /> */}

3
src/shared/Navigation/Navigation.tsx

@ -5,6 +5,7 @@ import ButtonPrimary from "../ButtonPrimary";
import ButtonSecondary from "../ButtonSecondary";
import Link from "next/link";
import { useTranslation } from "react-i18next";
import getLocalizedRoute from "@/utils/routes";
function Navigation() {
@ -14,7 +15,7 @@ function Navigation() {
{NAVIGATION_DEMO.map((item) => (
<NavigationItem key={item.id} menuItem={item} />
))}
<Link href="custom-trip" className="m-5 self-center border border-gray-300 p-1.5 rounded-full px-4 hover:bg-bronze hover:text-white">{t("customTrip")}</Link>
<Link href={getLocalizedRoute("custom-trip")} className="m-5 self-center border border-gray-300 p-1.5 rounded-full px-4 hover:bg-bronze hover:text-white">{t("customTrip")}</Link>
</ul>
);
}

6
src/shared/Navigation/NavigationItem.tsx

@ -10,7 +10,7 @@ import Link from "next/link";
import { usePathname } from "next/navigation";
import React, { FC, Fragment, useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import getLocalizedRoute from "@/utils/routes";
// <--- NavItemType --->
export interface MegamenuItem {
id: string;
@ -247,7 +247,7 @@ const NavigationItem: FC<NavigationItemWithRouterProps> = ({ menuItem }) => {
target={item.targetBlank ? "_blank" : undefined}
rel="noopener noreferrer"
className="flex items-center font-normal text-neutral-6000 dark:text-neutral-300 py-2 px-4 rounded-md hover:text-neutral-700 hover:bg-neutral-100 dark:hover:bg-neutral-800 dark:hover:text-neutral-200 "
href={`/tours?country=${item.name}`}
href={getLocalizedRoute(`/tours?country=${item.name}`)}
>
{item.name}
{item.type && (
@ -272,7 +272,7 @@ const NavigationItem: FC<NavigationItemWithRouterProps> = ({ menuItem }) => {
className={`inline-flex items-center text-sm xl:text-base ${
isActive ? "text-neutral-700 font-medium dark:text-neutral-200" : "text-neutral-500 font-normal dark:text-neutral-400"
} py-2 px-4 xl:px-5 rounded-full hover:text-neutral-900 hover:bg-neutral-100 dark:hover:bg-neutral-800 dark:hover:text-neutral-200`}
href={item.href || "/"}
href={getLocalizedRoute(item.href)}
>
{t(item.name)}
{item.type && (

19
src/utils/routes.ts

@ -0,0 +1,19 @@
import Cookies from 'js-cookie';
// Define the type for the routes object
interface Routes {
[key: string]: string;
}
// Function to get the localized route
function getLocalizedRoute(routeKey: string,): string {
// Get the locale from cookies
const locale: string = Cookies.get('locale') || 'en'; // Default to 'en' if locale is not set
// Check if the routeKey exists in the routes object
return `/${locale}/${routeKey}`;
}
export default getLocalizedRoute;

5
yarn.lock

@ -663,6 +663,11 @@ concat-map@0.0.1:
resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz"
integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==
cookie@^1.0.2:
version "1.0.2"
resolved "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz"
integrity sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==
copy-to-clipboard@^3.3.1:
version "3.3.3"
resolved "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz"

Loading…
Cancel
Save