sina_sajjadi
2 months ago
27 changed files with 730 additions and 341 deletions
-
8next.config.js
-
147package-lock.json
-
1package.json
-
BINpublic/favicon.ico
-
1src/app/(account-pages)/(components)/Nav.tsx
-
33src/app/(account-pages)/account-password/page.tsx
-
79src/app/(account-pages)/account/page.tsx
-
2src/app/(account-pages)/my-trips/page.tsx
-
14src/app/(account-pages)/passengers-list/PassengerTable.tsx
-
270src/app/(account-pages)/passengers-list/[id]/page.tsx
-
8src/app/(account-pages)/passengers-list/page.tsx
-
1src/app/(client-components)/(Header)/MainNav1.tsx
-
2src/app/(client-components)/(HeroSearchForm)/GuestsInput.tsx
-
8src/app/(listing-detail)/(components)/MobileFooterSticky.tsx
-
54src/app/add-listing/[[...stepIndex]]/page.tsx
-
6src/app/layout.tsx
-
134src/app/page.tsx
-
4src/app/tours/SectionGridFilterCard.tsx
-
33src/app/tours/[slug]/page.tsx
-
2src/app/tours/page.tsx
-
73src/components/CardCategory3.tsx
-
6src/components/TourSuggestion.tsx
-
82src/components/contexts/tourDetails.tsx
-
8src/hooks/i18n.js.ts
-
6src/shared/Logo.tsx
-
2src/shared/Navigation/NavigationItem.tsx
-
87yarn.lock
@ -1,33 +0,0 @@ |
|||
import React from "react"; |
|||
import Label from "@/components/Label"; |
|||
import ButtonPrimary from "@/shared/ButtonPrimary"; |
|||
import Input from "@/shared/Input"; |
|||
|
|||
const AccountPass = () => { |
|||
return ( |
|||
<div className="space-y-6 sm:space-y-8"> |
|||
{/* HEADING */} |
|||
<h2 className="text-3xl font-semibold">Update your password</h2> |
|||
<div className="w-14 border-b border-neutral-200 dark:border-neutral-700"></div> |
|||
<div className=" max-w-xl space-y-6"> |
|||
<div> |
|||
<Label>Current password</Label> |
|||
<Input type="password" className="mt-1.5" /> |
|||
</div> |
|||
<div> |
|||
<Label>New password</Label> |
|||
<Input type="password" className="mt-1.5" /> |
|||
</div> |
|||
<div> |
|||
<Label>Confirm password</Label> |
|||
<Input type="password" className="mt-1.5" /> |
|||
</div> |
|||
<div className="pt-2"> |
|||
<ButtonPrimary>Update password</ButtonPrimary> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
); |
|||
}; |
|||
|
|||
export default AccountPass; |
@ -0,0 +1,270 @@ |
|||
"use client"; |
|||
|
|||
import React, { useEffect, useState } from "react"; |
|||
import { FC } from "react"; |
|||
import ButtonPrimary from "@/shared/ButtonPrimary"; |
|||
import Input from "@/shared/Input"; |
|||
import FormItem from "@/app/add-listing/FormItem"; |
|||
import getImageURL from "@/components/api/getImageURL"; |
|||
import axiosInstance from "@/components/api/axios"; |
|||
import { useRouter } from "next/navigation"; |
|||
import { useUserContext } from "@/components/contexts/userContext"; |
|||
import { toast } from "react-toastify"; // Import toast
|
|||
|
|||
export interface CommonLayoutProps { |
|||
params: { |
|||
id: string; |
|||
}; |
|||
} |
|||
|
|||
const EditPassenger: FC<CommonLayoutProps> = ({ params }) => { |
|||
const { user } = useUserContext(); |
|||
const router = useRouter(); |
|||
|
|||
const [passenger, setPassenger] = useState({ |
|||
name: "", |
|||
passport: "", |
|||
number: "", |
|||
date: "", |
|||
image: "", |
|||
}); |
|||
|
|||
const [originalPassenger, setOriginalPassenger] = useState({ |
|||
name: "", |
|||
passport: "", |
|||
number: "", |
|||
date: "", |
|||
image: "", |
|||
}); |
|||
|
|||
const [loading, setLoading] = useState(false); |
|||
|
|||
useEffect(() => { |
|||
if (Object.keys(user).length) { |
|||
axiosInstance |
|||
.get(`/api/account/passengers/${params.id}/`, { |
|||
headers: { |
|||
Authorization: `token ${user.token}`, |
|||
"Content-Type": "application/json", |
|||
"X-CSRFToken": "oli8in4JPf6taFVzg7tsY2g9Xpmox45yOBp1LrgU20tUFL5K4VkOrx7quXvOLUwW", |
|||
}, |
|||
}) |
|||
.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); |
|||
}); |
|||
} |
|||
}, [user]); |
|||
|
|||
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("Image uploaded successfully!"); |
|||
} catch (error) { |
|||
toast.error("Error uploading image."); |
|||
} finally { |
|||
setLoading(false); |
|||
} |
|||
} |
|||
}; |
|||
|
|||
const validateForm = () => { |
|||
let formIsValid = true; |
|||
|
|||
if (!passenger.name) { |
|||
formIsValid = false; |
|||
toast.error("Full Name is required."); |
|||
} |
|||
|
|||
if (!passenger.passport) { |
|||
formIsValid = false; |
|||
toast.error("Passport Number is required."); |
|||
} else if (!/^\d+$/.test(passenger.passport)) { |
|||
formIsValid = false; |
|||
toast.error("Passport Number must be numeric."); |
|||
} |
|||
|
|||
if (!passenger.date) { |
|||
formIsValid = false; |
|||
toast.error("Date of Birth is required."); |
|||
} |
|||
|
|||
if (!passenger.number) { |
|||
formIsValid = false; |
|||
toast.error("Phone Number is required."); |
|||
} else if (!/^\d+$/.test(passenger.number)) { |
|||
formIsValid = false; |
|||
toast.error("Phone Number must be numeric."); |
|||
} |
|||
|
|||
if (!passenger.image) { |
|||
formIsValid = false; |
|||
toast.error("Passport image is required."); |
|||
} |
|||
|
|||
return formIsValid; |
|||
}; |
|||
|
|||
const handleSavePassenger = async () => { |
|||
if (!validateForm()) return; |
|||
|
|||
const updatedFields: Partial<typeof passenger> = {}; |
|||
|
|||
// Only add fields that were changed
|
|||
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; |
|||
} |
|||
|
|||
if (Object.keys(updatedFields).length === 0) { |
|||
toast.info("No changes to update."); |
|||
return; |
|||
} |
|||
|
|||
try { |
|||
const response = await axiosInstance.patch( |
|||
`https://aqila.nwhco.ir/api/account/passengers/${params.id}/`, |
|||
updatedFields, |
|||
{ |
|||
headers: { |
|||
Authorization: `token ${user.token}`, |
|||
"Content-Type": "application/json", |
|||
"X-CSRFToken": "oli8in4JPf6taFVzg7tsY2g9Xpmox45yOBp1LrgU20tUFL5K4VkOrx7quXvOLUwW", |
|||
}, |
|||
} |
|||
); |
|||
|
|||
if (response.status === 200) { |
|||
toast.success("Passenger details updated successfully!"); |
|||
router.push("/passengers-list"); |
|||
} |
|||
} catch (error) { |
|||
toast.error("Error saving passenger details."); |
|||
} |
|||
}; |
|||
|
|||
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">Passenger Information</h2> |
|||
<div className="w-14 border-b border-neutral-200 dark:border-neutral-700"></div> |
|||
<div className="space-y-8"> |
|||
<FormItem label="Full Name" desc=""> |
|||
<Input |
|||
required |
|||
value={passenger.name} |
|||
onChange={(e) => |
|||
setPassenger((prev) => ({ |
|||
...prev, |
|||
name: e.target.value, |
|||
})) |
|||
} |
|||
placeholder="Full Name" |
|||
/> |
|||
</FormItem> |
|||
|
|||
<FormItem label="Passport Number" desc=""> |
|||
<Input |
|||
required |
|||
value={passenger.passport} |
|||
onChange={(e) => |
|||
setPassenger((prev) => ({ |
|||
...prev, |
|||
passport: e.target.value, |
|||
})) |
|||
} |
|||
type="text" // Changed from 'number' to 'text'
|
|||
placeholder="Passport Number" |
|||
/> |
|||
</FormItem> |
|||
|
|||
<FormItem label="Date of Birth" desc=""> |
|||
<Input |
|||
required |
|||
value={passenger.date} |
|||
onChange={(e) => |
|||
setPassenger((prev) => ({ |
|||
...prev, |
|||
date: e.target.value, |
|||
})) |
|||
} |
|||
type="date" |
|||
placeholder="Date of Birth" |
|||
/> |
|||
</FormItem> |
|||
|
|||
<FormItem label="Phone Number" desc=""> |
|||
<Input |
|||
required |
|||
value={passenger.number} // Remove non-numeric characters
|
|||
onChange={(e) => |
|||
setPassenger((prev) => ({ |
|||
...prev, |
|||
number: e.target.value.replace(/\D/g, ""), // Ensure only numeric input
|
|||
})) |
|||
} |
|||
type="text" // Keep as 'text' to prevent unwanted behavior
|
|||
placeholder="Phone Number" |
|||
/> |
|||
</FormItem> |
|||
|
|||
<FormItem label="Upload Passport Image Here" desc=""> |
|||
<Input |
|||
required |
|||
onChange={handleFileChange} |
|||
type="file" |
|||
placeholder="Passport" |
|||
/> |
|||
{loading && <p>Loading ...</p>} |
|||
</FormItem> |
|||
</div> |
|||
</> |
|||
</div> |
|||
|
|||
<div className="flex justify-end space-x-5"> |
|||
<ButtonPrimary |
|||
onClick={(e) => { |
|||
e.preventDefault(); |
|||
handleSavePassenger(); |
|||
}} |
|||
> |
|||
Continue |
|||
</ButtonPrimary> |
|||
</div> |
|||
</form> |
|||
</div> |
|||
</div> |
|||
); |
|||
}; |
|||
|
|||
export default EditPassenger; |
134
src/app/page.tsx
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
73
src/components/CardCategory3.tsx
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -0,0 +1,8 @@ |
|||
const NextI18Next = require('next-i18next').default; |
|||
|
|||
const i18n = new NextI18Next({ |
|||
defaultLanguage: 'en', |
|||
otherLanguages: ['en'], |
|||
}); |
|||
|
|||
module.exports = i18n; |
Write
Preview
Loading…
Cancel
Save
Reference in new issue