Browse Source
feat(localization): add new translations for navigation and form fields in multiple languages
main
feat(localization): add new translations for navigation and form fields in multiple languages
main
sina_sajjadi
2 weeks ago
26 changed files with 490 additions and 1084 deletions
-
11public/locales/ar/common.json
-
4public/locales/ar/form.json
-
6public/locales/ar/navigation.json
-
11public/locales/en/common.json
-
3public/locales/en/form.json
-
6public/locales/en/navigation.json
-
11public/locales/id/common.json
-
4public/locales/id/form.json
-
6public/locales/id/navigation.json
-
11public/locales/ru/common.json
-
7public/locales/ru/navigation.json
-
17src/app/[locale]/(account-pages)/bills/BillCard.tsx
-
181src/app/[locale]/(account-pages)/bills/[slug]/page.tsx
-
2src/app/[locale]/(account-pages)/bills/page.tsx
-
289src/app/[locale]/(account-pages)/passengers-list/page.tsx
-
10src/app/[locale]/(client-components)/(HeroSearchForm2Mobile)/HeroSearchForm2Mobile.tsx
-
603src/app/[locale]/(stay-listings)/TabFilters.tsx
-
6src/app/[locale]/add-listing/[[...stepIndex]]/PageAddListing1.tsx
-
4src/app/[locale]/custom-trip/page.tsx
-
4src/app/[locale]/layout.tsx
-
30src/app/[locale]/tours/TabFilters.tsx
-
22src/app/[locale]/tours/[slug]/page.tsx
-
6src/components/Footer.tsx
-
2src/components/contexts/tourDetails.tsx
-
16src/shared/Navigation/NavMobile.tsx
-
2src/shared/SocialsList1.tsx
@ -1,269 +1,68 @@ |
|||||
"use client"; |
|
||||
|
"use client" |
||||
|
|
||||
import React, { useEffect, useState } from "react"; |
import React, { useEffect, useState } from "react"; |
||||
import { FC } from "react"; |
|
||||
import ButtonPrimary from "@/shared/ButtonPrimary"; |
|
||||
import Input from "@/shared/Input"; |
|
||||
import FormItem from "@/app/[locale]/add-listing/FormItem"; |
|
||||
import getImageURL from "@/components/api/getImageURL"; |
|
||||
|
import PassengerTable from "./PassengerTable"; |
||||
|
import { IoPersonAddOutline } from "react-icons/io5"; |
||||
import axiosInstance from "@/components/api/axios"; |
import axiosInstance from "@/components/api/axios"; |
||||
import { useRouter } from "next/navigation"; |
|
||||
|
import Link from "next/link"; |
||||
import { useUserContext } from "@/components/contexts/userContext"; |
import { useUserContext } from "@/components/contexts/userContext"; |
||||
import { toast } from "react-toastify"; |
|
||||
|
import { useRouter } from "next/navigation"; |
||||
import { useTranslation } from "react-i18next"; |
import { useTranslation } from "react-i18next"; |
||||
|
|
||||
export interface CommonLayoutProps { |
|
||||
params: { |
|
||||
id: string; |
|
||||
}; |
|
||||
} |
|
||||
|
|
||||
const EditPassenger: FC<CommonLayoutProps> = ({ params }) => { |
|
||||
const { user } = useUserContext(); |
|
||||
const router = useRouter(); |
|
||||
const { t } = useTranslation("form"); |
|
||||
|
|
||||
const [passenger, setPassenger] = useState({ |
|
||||
name: "", |
|
||||
passport: "", |
|
||||
number: "", |
|
||||
date: "", |
|
||||
image: "", |
|
||||
}); |
|
||||
|
|
||||
const [originalPassenger, setOriginalPassenger] = useState({ |
|
||||
name: "", |
|
||||
passport: "", |
|
||||
number: "", |
|
||||
date: "", |
|
||||
image: "", |
|
||||
}); |
|
||||
|
|
||||
const [loading, setLoading] = useState(false); |
|
||||
|
|
||||
// Fetch passenger data on component mount
|
|
||||
useEffect(() => { |
|
||||
if (Object.keys(user).length) { |
|
||||
axiosInstance |
|
||||
.get(`/api/account/passengers/${params.id}/`, { |
|
||||
headers: { |
|
||||
Authorization: `token ${user.token}`, |
|
||||
}, |
|
||||
}) |
|
||||
.then((response) => { |
|
||||
const passengerData = { |
|
||||
name: response.data.fullname, |
|
||||
passport: response.data.passport_number, |
|
||||
date: response.data.birthdate, |
|
||||
number: response.data.phone_number.replace(/\D/g, ""), |
|
||||
image: response.data.passport_image, |
|
||||
}; |
|
||||
setPassenger(passengerData); |
|
||||
setOriginalPassenger(passengerData); // Save original data for comparison
|
|
||||
}) |
|
||||
.catch((error) => { |
|
||||
toast.error(error.message || t("errorOccurred")); |
|
||||
}); |
|
||||
} |
|
||||
}, [user, params.id]); |
|
||||
|
|
||||
// Handle file change for uploading passport image
|
|
||||
const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => { |
|
||||
setLoading(true); |
|
||||
const file = e.target.files?.[0]; |
|
||||
if (file) { |
|
||||
try { |
|
||||
const image = await getImageURL(file); |
|
||||
setPassenger((prev) => ({ ...prev, image: image.url })); |
|
||||
toast.success(t("imageUploaded")); |
|
||||
} catch (error) { |
|
||||
toast.error(t("imageUploadError")); |
|
||||
} finally { |
|
||||
setLoading(false); |
|
||||
} |
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
// Validate form inputs before saving
|
|
||||
const validateForm = () => { |
|
||||
let formIsValid = true; |
|
||||
|
|
||||
if (!passenger.name) { |
|
||||
formIsValid = false; |
|
||||
toast.error(t("fullNameRequired")); |
|
||||
} |
|
||||
|
|
||||
if (!passenger.passport) { |
|
||||
formIsValid = false; |
|
||||
toast.error(t("passportRequired")); |
|
||||
} else if (!/^\d+$/.test(passenger.passport)) { |
|
||||
formIsValid = false; |
|
||||
toast.error(t("passportNumeric")); |
|
||||
} |
|
||||
|
|
||||
if (!passenger.date) { |
|
||||
formIsValid = false; |
|
||||
toast.error(t("dobRequired")); |
|
||||
} |
|
||||
|
|
||||
if (!passenger.number) { |
|
||||
formIsValid = false; |
|
||||
toast.error(t("phoneRequired")); |
|
||||
} else if (!/^\d+$/.test(passenger.number)) { |
|
||||
formIsValid = false; |
|
||||
toast.error(t("phoneNumeric")); |
|
||||
} |
|
||||
|
|
||||
if (!passenger.image) { |
|
||||
formIsValid = false; |
|
||||
toast.error(t("passportImageRequired")); |
|
||||
} |
|
||||
|
|
||||
return formIsValid; |
|
||||
}; |
|
||||
|
|
||||
// Save updated passenger details
|
|
||||
const handleSavePassenger = async () => { |
|
||||
if (!validateForm()) return; |
|
||||
|
|
||||
const updatedFields: Partial<{ |
|
||||
fullname: string; |
|
||||
passport_number: string; |
|
||||
|
interface data { |
||||
birthdate: string; |
birthdate: string; |
||||
phone_number: string; |
|
||||
|
fullname: string; |
||||
|
id: number; |
||||
passport_image: string; |
passport_image: string; |
||||
}> = {}; |
|
||||
|
|
||||
// Only include changed fields
|
|
||||
if (passenger.name !== originalPassenger.name) { |
|
||||
updatedFields.fullname = passenger.name; |
|
||||
} |
|
||||
if (passenger.passport !== originalPassenger.passport) { |
|
||||
updatedFields.passport_number = passenger.passport; |
|
||||
} |
|
||||
if (passenger.date !== originalPassenger.date) { |
|
||||
updatedFields.birthdate = passenger.date; |
|
||||
} |
|
||||
if (passenger.number !== originalPassenger.number) { |
|
||||
updatedFields.phone_number = passenger.number; |
|
||||
} |
|
||||
if (passenger.image !== originalPassenger.image) { |
|
||||
updatedFields.passport_image = passenger.image; |
|
||||
} |
|
||||
|
phone_number: string; |
||||
|
passport_number: string; |
||||
|
|
||||
if (Object.keys(updatedFields).length === 0) { |
|
||||
toast.info(t("noChanges")); |
|
||||
return; |
|
||||
} |
} |
||||
|
|
||||
try { |
|
||||
const response = await axiosInstance.patch( |
|
||||
`/api/account/passengers/${params.id}/`, |
|
||||
updatedFields, |
|
||||
{ |
|
||||
headers: { |
|
||||
Authorization: `token ${user.token}`, |
|
||||
"Content-Type": "application/json", |
|
||||
}, |
|
||||
} |
|
||||
); |
|
||||
|
const PassengersList = () => { |
||||
|
const [passengers , setPassenger ] = useState([]) |
||||
|
const {user} = useUserContext() |
||||
|
const router = useRouter() |
||||
|
const {t} = useTranslation("form") |
||||
|
|
||||
if (response.status === 200) { |
|
||||
toast.success(t("detailsUpdated")); |
|
||||
router.push("/passengers-list"); |
|
||||
} |
|
||||
} catch (error) { |
|
||||
toast.error(t("updateFailed")); |
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
return ( |
|
||||
<div className="nc-PageAddListing1 px-4 max-w-3xl mx-auto pb-24 pt-14 sm:py-24 lg:pb-32"> |
|
||||
<div className="space-y-11"> |
|
||||
<form> |
|
||||
<div className="listingSection__wrap"> |
|
||||
<h2 className="text-2xl font-semibold">{t("editPassengerInfo")}</h2> |
|
||||
<div className="w-14 border-b border-neutral-200 dark:border-neutral-700"></div> |
|
||||
<div className="space-y-8"> |
|
||||
<FormItem label={t("fullName")} desc=""> |
|
||||
<Input |
|
||||
required |
|
||||
value={passenger.name} |
|
||||
onChange={(e) => |
|
||||
setPassenger((prev) => ({ ...prev, name: e.target.value })) |
|
||||
|
useEffect(() => { |
||||
|
if (!Object.keys(user).length) { |
||||
|
router.replace("/signup"); |
||||
} |
} |
||||
placeholder={t("enterFullName")} |
|
||||
/> |
|
||||
</FormItem> |
|
||||
|
}, [user, router]); |
||||
|
|
||||
<FormItem label={t("passportNumber")} desc=""> |
|
||||
<Input |
|
||||
required |
|
||||
value={passenger.passport} |
|
||||
onChange={(e) => |
|
||||
setPassenger((prev) => ({ |
|
||||
...prev, |
|
||||
passport: e.target.value, |
|
||||
})) |
|
||||
|
useEffect(()=>{ |
||||
|
axiosInstance.get("/api/account/passengers/" ,{ |
||||
|
headers :{ |
||||
|
Authorization : `token ${user.token}` |
||||
} |
} |
||||
type="text" |
|
||||
placeholder={t("enterPassportNumber")} |
|
||||
/> |
|
||||
</FormItem> |
|
||||
|
}) |
||||
|
.then((response)=>{ |
||||
|
setPassenger(response.data.results); |
||||
|
|
||||
<FormItem label={t("dateOfBirth")} desc=""> |
|
||||
<Input |
|
||||
required |
|
||||
value={passenger.date} |
|
||||
onChange={(e) => |
|
||||
setPassenger((prev) => ({ ...prev, date: e.target.value })) |
|
||||
} |
|
||||
type="date" |
|
||||
placeholder={t("dobPlaceholder")} |
|
||||
/> |
|
||||
</FormItem> |
|
||||
|
}).catch((error)=>{ |
||||
|
console.error(error); |
||||
|
|
||||
<FormItem label={t("phoneNumber")} desc=""> |
|
||||
<Input |
|
||||
required |
|
||||
value={passenger.number} |
|
||||
onChange={(e) => |
|
||||
setPassenger((prev) => ({ |
|
||||
...prev, |
|
||||
number: e.target.value.replace(/\D/g, ""), |
|
||||
})) |
|
||||
} |
|
||||
type="text" |
|
||||
placeholder={t("enterPhoneNumber")} |
|
||||
/> |
|
||||
</FormItem> |
|
||||
|
}) |
||||
|
} , []) |
||||
|
|
||||
<FormItem label={t("passportImage")} desc=""> |
|
||||
<Input |
|
||||
required |
|
||||
onChange={handleFileChange} |
|
||||
type="file" |
|
||||
placeholder={t("uploadImage")} |
|
||||
/> |
|
||||
{loading && <p>{t("loading")}</p>} |
|
||||
</FormItem> |
|
||||
</div> |
|
||||
</div> |
|
||||
|
|
||||
<div className="flex justify-end space-x-5"> |
|
||||
<ButtonPrimary |
|
||||
onClick={(e) => { |
|
||||
e.preventDefault(); |
|
||||
handleSavePassenger(); |
|
||||
}} |
|
||||
> |
|
||||
{t("saveChanges")} |
|
||||
</ButtonPrimary> |
|
||||
</div> |
|
||||
</form> |
|
||||
</div> |
|
||||
|
return ( |
||||
|
<div className="flex flex-col items-start space-y-6 sm:space-y-8"> |
||||
|
{/* Add New Passenger Section */} |
||||
|
<Link href={"/add-new-passenger"} className="flex items-center space-x-2 text-orange-500 cursor-pointer hover:text-orange-600"> |
||||
|
<IoPersonAddOutline className="text-xl" /> {/* Adjust icon size */} |
||||
|
<p className="text-sm font-medium">{t("addNewPassenger")}</p> |
||||
|
</Link> |
||||
|
|
||||
|
{/* Passenger Table */} |
||||
|
{passengers.map((item : data)=>( |
||||
|
|
||||
|
<PassengerTable key={item.id} data={item} /> |
||||
|
))} |
||||
</div> |
</div> |
||||
); |
); |
||||
}; |
}; |
||||
|
|
||||
export default EditPassenger; |
|
||||
|
export default PassengersList; |
Write
Preview
Loading…
Cancel
Save
Reference in new issue