Skip to content

Commit

Permalink
Merge pull request #8 from lGnyte/accommodations
Browse files Browse the repository at this point in the history
Add the editing of an accommodation
  • Loading branch information
lGnyte authored May 26, 2024
2 parents d21f1ec + 75c20c0 commit 2cf590a
Show file tree
Hide file tree
Showing 2 changed files with 249 additions and 2 deletions.
246 changes: 246 additions & 0 deletions src/app/edit_accommodation/[accommodationId]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
'use client';
import { UserContext } from '@/lib/context';
import { db, storage } from '@/lib/firebase';
import { doc, getDoc, DocumentData, setDoc } from 'firebase/firestore';
import { ref, uploadBytesResumable, getDownloadURL } from 'firebase/storage';
import { NextPage } from 'next';
import { ChangeEvent, FormEvent, useContext, useEffect, useState } from 'react';
import ConfirmationModal from '@/components/AsyncConfirmationModal';
import { createHash } from 'crypto';
import toast from 'react-hot-toast';

interface EditAccommodationProps {
params: {
accommodationId: string;
};
}

const EditAccommodation: NextPage<EditAccommodationProps> = ({ params }) => {
const [title, setTitle] = useState('');
const [location, setLocation] = useState('');
const [description, setDescription] = useState('');
const [photos, setPhotos] = useState([] as File[]);
const [photosUrls, setPhotosUrls] = useState([] as string[]);
const [numberOfRooms, setNumberOfRooms] = useState(0);
const [price, setPrice] = useState(0);
const [showModal, setShowModal] = useState(false);
const [accommodation, setAccommodation] = useState({} as DocumentData);
const [receivedData, setData] = useState(false);


const { user } = useContext(UserContext);

useEffect(() => {
(async () => {
const accommodationRef = await getDoc(doc(db, "accommodations", params.accommodationId));
if (accommodationRef.exists()) {
const accommodationData = accommodationRef.data();
if (accommodationData.uid == user.uid) {
setAccommodation(accommodationRef.data());
} else {
window.location.href = '/';
}
setData(true);
} else {
window.location.href = '/';
}
})();
if (accommodation) {
setTitle(accommodation.title);
setLocation(accommodation.location);
setDescription(accommodation.description);
setNumberOfRooms(accommodation.numberOfRooms);
if (accommodation.photos) {
setPhotosUrls(accommodation.photos);
}
setPrice(accommodation.price);
}
}, [receivedData]);


const handleFormSubmit = (e: FormEvent) => {
e.preventDefault();
setShowModal(true);
};

const handlePhotoUpload = (e: ChangeEvent<HTMLInputElement>) => {
if (e.target != null && e.target.files != null) {
const files = [];
for (let i = 0; i < e.target.files.length; i++) {
files.push(e.target.files[i]);
}
setPhotos(files);
}
};

// Will only remove the link from the object at the time
const handleRemovePhoto = async (photoUrl: string) => {
const photosWithRemoved = photosUrls.filter(url => url != photoUrl);
try {
const documentReference = doc(db, "accommodations", params.accommodationId)
const document = await getDoc(documentReference)
if (document.exists()) {
const documentData = document.data()
documentData.photos = photosWithRemoved;
await setDoc(documentReference, documentData);
setPhotosUrls(photosWithRemoved);
toast.success("Successfully removed photo!");
} else {
toast.error("The document requested to modify does not exists!");
}
} catch(e) {
toast.error("Could not remove photo! Error: "+e);
}
}

const handleSubmit = async (e: FormEvent) => {
e.preventDefault();

// Generate a unique ID and add the location data to Firestore
try {
let images = [];

for(let i = 0; i < photos.length; i++) {
const buffer = await photos[i].arrayBuffer();
const name = photos[i].name;
const hashName = createHash('sha256')
.update(new Uint8Array(buffer))
.digest('hex');
const extension = name.split('.').pop();
const storageUri = `accommodations/${hashName}.${extension}`;
const storageRef = ref(storage, storageUri);
if (buffer !== undefined) {
await uploadBytesResumable(storageRef, buffer);
images.push(await getDownloadURL(storageRef));
}
}

await setDoc(doc(db, "accommodations", params.accommodationId), {
uid: user.uid,
title,
location,
description,
numberOfRooms,
price,
photos: images, // set the photos url
});

// Reset the form
setPhotosUrls(photosUrls.concat(images));
setPhotos([]);
setShowModal(false);

toast.success("Successfully modified accommodation!")
} catch (e) {
toast.error("Could not create accommodation, contact Admin! Error: "+e);
}
};

return (
<div className="min-h-screen bg-gray-100 flex items-center justify-center p-6">
<div className="bg-white p-6 rounded-lg shadow-lg w-full max-w-lg">
<h2 className="text-2xl font-bold mb-4">Edit Accommodation</h2>
<form onSubmit={handleFormSubmit}>
<div className="mb-4">
<label className="block text-gray-700">Title</label>
<input
type="text"
value={title}
onChange={(e) => setTitle(e.target.value)}
className="mt-1 p-2 border border-gray-300 rounded w-full"
required
/>
</div>
<div className="mb-4">
<label className="block text-gray-700">
Location
<span className="text-gray-500 text-sm ml-2">
(e.g., Brasov, Brasov, Romania)
</span>
</label>
<input
type="text"
value={location}
onChange={(e) => setLocation(e.target.value)}
className="mt-1 p-2 border border-gray-300 rounded w-full"
required
/>
</div>
<div className="mb-4">
<label className="block text-gray-700">Description</label>
<textarea
value={description}
onChange={(e) => setDescription(e.target.value)}
className="mt-1 p-2 border border-gray-300 rounded w-full"
rows={4}
required
/>
</div>
{ photosUrls.length > 0 ?
<div className="mb-4">
<label className="block text-gray-700">Existing Photos</label>
<div className="flex flex-wrap gap-2">
{photosUrls.map((photo, index) => (
<div key={index} className="relative">
<img src={photo} alt={`photo-${index}`} className="w-24 h-24 object-cover rounded" />
<button
type="button"
className="absolute top-0 right-0 bg-red-500 text-white p-1 rounded-full"
onClick={() => handleRemovePhoto(photo)}
>
&times;
</button>
</div>
))}
</div>
</div>
: null}
<div className="mb-4">
<label className="block text-gray-700">Upload Photos</label>
<input
type="file"
onChange={handlePhotoUpload}
className="mt-1 p-2 border border-gray-300 rounded w-full"
multiple
/>
</div>
<div className="mb-4">
<label className="block text-gray-700">Number of Rooms</label>
<input
type="number"
value={numberOfRooms}
onChange={(e) => setNumberOfRooms(Number(e.target.value))}
className="mt-1 p-2 border border-gray-300 rounded w-full"
required
/>
</div>
<div className="mb-4">
<label className="block text-gray-700">Price per Room</label>
<input
type="number"
value={price}
onChange={(e) => setPrice(Number(e.target.value))}
className="mt-1 p-2 border border-gray-300 rounded w-full"
required
/>
</div>
<button
type="submit"
className="bg-blue-500 text-white p-2 rounded w-full"
>
Update
</button>
</form>
</div>
<ConfirmationModal
show={showModal}
onClose={() => setShowModal(false)}
onConfirm={handleSubmit}
title="Confirm Update"
description="Are you sure you want to update this accommodation?"
/>
</div>
);
};

export default EditAccommodation;
5 changes: 3 additions & 2 deletions src/app/my_accommodations/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use client';
import { useContext, useEffect, useState } from 'react';
import Link from 'next/link';
import { DocumentData, collection, deleteDoc, doc, getDocs} from 'firebase/firestore';
import { db } from '@/lib/firebase';
import { UserContext } from '@/lib/context';
Expand Down Expand Up @@ -50,11 +51,11 @@ const MyAccommodations: React.FC = () => {
<p className="text-gray-600">{value.at(1).price} lei</p>
</div>
<div className="flex items-center space-x-4">
<a href={`/edit_accommodation/${value.at(0)}`} className="text-blue-500 hover:text-blue-700">
<Link href={`/edit_accommodation/${value.at(0)}`} className="text-blue-500 hover:text-blue-700">
<svg className="h-6 w-6" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11 4H7.2C6.0799 4 5.51984 4 5.09202 4.21799C4.71569 4.40974 4.40973 4.7157 4.21799 5.09202C4 5.51985 4 6.0799 4 7.2V16.8C4 17.9201 4 18.4802 4.21799 18.908C4.40973 19.2843 4.71569 19.5903 5.09202 19.782C5.51984 20 6.0799 20 7.2 20H16.8C17.9201 20 18.4802 20 18.908 19.782C19.2843 19.5903 19.5903 19.2843 19.782 18.908C20 18.4802 20 17.9201 20 16.8V12.5M15.5 5.5L18.3284 8.32843M10.7627 10.2373L17.411 3.58902C18.192 2.80797 19.4584 2.80797 20.2394 3.58902C21.0205 4.37007 21.0205 5.6364 20.2394 6.41745L13.3774 13.2794C12.6158 14.0411 12.235 14.4219 11.8012 14.7247C11.4162 14.9936 11.0009 15.2162 10.564 15.3882C10.0717 15.582 9.54378 15.6885 8.48793 15.9016L8 16L8.04745 15.6678C8.21536 14.4925 8.29932 13.9048 8.49029 13.3561C8.65975 12.8692 8.89125 12.4063 9.17906 11.9786C9.50341 11.4966 9.92319 11.0768 10.7627 10.2373Z" stroke="#000000" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
</a>
</Link>
<button className="text-red-500 hover:text-red-700" onClick={() => {setSelectedAccommodationId(value.at(0)); setShowModal(true)}}>
<svg className="h-6 w-6" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><g id="SVGRepo_bgCarrier" strokeWidth="0"></g><g id="SVGRepo_tracerCarrier" strokeLinecap="round" strokeLinejoin="round"></g><g id="SVGRepo_iconCarrier">
<path d="M18 6L17.1991 18.0129C17.129 19.065 17.0939 19.5911 16.8667 19.99C16.6666 20.3412 16.3648 20.6235 16.0011 20.7998C15.588 21 15.0607 21 14.0062 21H9.99377C8.93927 21 8.41202 21 7.99889 20.7998C7.63517 20.6235 7.33339 20.3412 7.13332 19.99C6.90607 19.5911 6.871 19.065 6.80086 18.0129L6 6M4 6H20M16 6L15.7294 5.18807C15.4671 4.40125 15.3359 4.00784 15.0927 3.71698C14.8779 3.46013 14.6021 3.26132 14.2905 3.13878C13.9376 3 13.523 3 12.6936 3H11.3064C10.477 3 10.0624 3 9.70951 3.13878C9.39792 3.26132 9.12208 3.46013 8.90729 3.71698C8.66405 4.00784 8.53292 4.40125 8.27064 5.18807L8 6M14 10V17M10 10V17" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
Expand Down

0 comments on commit 2cf590a

Please sign in to comment.